diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 254499a7f..4f84c6c01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,6 +129,73 @@ jobs: cd docs && make SPHINXOPTS="--keep-going" html touch build/html/.nojekyll + - name: Fetch pyodide wheel for JupyterLite + # A plain `pip wheel`/`build` here would produce a native + # macOS/Linux wheel with the compiled _fknm_c/_frne_c extensions -- + # useless in Pyodide (WASM). release.yml already cross-compiles a + # genuine wasm32 wheel via cibuildwheel (CIBW_PLATFORM=pyodide) and + # attaches it to each GitHub Release (PyPI rejects the pyodide_* + # platform tag), so reuse that rather than rebuilding here. This + # means the "Try it Now" experience tracks the latest release, not + # bleeding-edge main -- deliberate, since a full Pyodide/Emscripten + # rebuild on every docs push would be slow. + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p docs/lite/pypi + gh release download --repo "$GITHUB_REPOSITORY" --pattern '*pyodide*' --dir docs/lite/pypi --clobber + + - name: Sync notebooks into JupyterLite content + # Copies *.ipynb plus any local asset directories (e.g. + # docs/notebooks/figs/, referenced by relative path from markdown + # cells) -- but not the Makefile-generated *.py script conversions + # or other loose files. ik_benchmark.ipynb is excluded deliberately: + # it benchmarks numeric IK solver performance and uses the + # websocket-based Swift visualizer, neither of which is meaningful + # or functional inside the Pyodide sandbox -- it stays Colab-only. + run: | + mkdir -p docs/lite/files + rsync -a --exclude '.ipynb_checkpoints' --exclude '__pycache__' \ + --exclude 'Untitled*.ipynb' --exclude '*.py' --exclude 'Makefile' \ + --exclude 'ik_benchmark.ipynb' \ + docs/notebooks/ docs/lite/files/ + + - name: Adapt staged notebooks for JupyterLite + run: | + python - <<'PY' + import json + from pathlib import Path + + root = Path("docs/lite/files") + for nb_path in root.rglob("*.ipynb"): + with nb_path.open("r", encoding="utf-8") as f: + nb = json.load(f) + + meta = nb.setdefault("metadata", {}) + meta["kernelspec"] = { + "name": "python", + "display_name": "Python (Pyodide)", + "language": "python", + } + meta.setdefault("language_info", {}) + meta["language_info"]["name"] = "python" + + with nb_path.open("w", encoding="utf-8") as f: + json.dump(nb, f, indent=1) + f.write("\n") + PY + + - name: Build JupyterLite site + # jupyterlite-pyodide-kernel is pinned deliberately: newer versions + # bundle a newer Pyodide runtime than the cp312 wasm wheel attached + # to releases targets, which manifests as a silent hang in the + # browser (no error, just a kernel that never responds) rather than + # a build-time failure. See toolbox-infrastructure.md for the full + # version-skew story before bumping this. + run: | + pip install jupyterlite-core "jupyterlite-pyodide-kernel==0.6.1" jupyter-server + cd docs/lite && jupyter lite build --output-dir ../build/html/lite + - name: Upload Pages artifact uses: actions/upload-pages-artifact@v5 with: diff --git a/Makefile b/Makefile index 7fc5a4194..e690d062f 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ dist: .FORCE $(MAKE) test python -m build -PYODIDE_BUILD ?= cp313-* +PYODIDE_BUILD ?= cp312-* wheel-pyodide: .FORCE @echo "Building Pyodide wheel with selector $(PYODIDE_BUILD)" @@ -61,7 +61,7 @@ wheel-pyodide: .FORCE @$(MAKE) wheel-pyodide-check wheel-pyodide-check: .FORCE - @wheel=$$(ls -1 dist/*cp313-cp313*wasm32*.whl 2>/dev/null | head -n 1); \ + @wheel=$$(ls -1 dist/*cp312-cp312*wasm32*.whl 2>/dev/null | head -n 1); \ if [ -z "$$wheel" ]; then \ wheel=$$(ls -1 dist/*wasm32*.whl 2>/dev/null | head -n 1); \ fi; \ @@ -72,15 +72,15 @@ wheel-pyodide-check: .FORCE name=$$(basename "$$wheel"); \ echo "Built wheel: $$name"; \ echo "Path: $$wheel"; \ - if ! echo "$$name" | grep -Eq "cp313-cp313-.*wasm32[.]whl$$"; then \ - echo "Wheel tag check failed: expected cp313-cp313 and wasm32 in filename."; \ + if ! echo "$$name" | grep -Eq "cp312-cp312-.*wasm32[.]whl$$"; then \ + echo "Wheel tag check failed: expected cp312-cp312 and wasm32 in filename."; \ exit 1; \ fi; \ if ! echo "$$name" | grep -Eq "pyemscripten_[0-9]+_[0-9]+" && ! echo "$$name" | grep -Eq "pyodide_[0-9]+_[0-9]+"; then \ echo "Wheel tag check failed: expected pyemscripten__ or pyodide__ tag in filename."; \ exit 1; \ fi; \ - echo "Wheel tags look compatible with current cp313 runtime contract." + echo "Wheel tags look compatible with current cp312 runtime contract." clean: .FORCE diff --git a/README.md b/README.md index 823ce8cf7..d32b7ce0d 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,60 @@ # Robotics Toolbox for Python +
+ +
+ Robotics without the cruft +
+ A high-productivity framework for robotics research and education. +

+ +[![JupyterLite](https://img.shields.io/badge/Try_it_Now-JupyterLite-orange?style=for-the-badge&logo=jupyter)](https://petercorke.github.io/robotics-toolbox-python/lite/lab?path=robotics.ipynb) + [![PyPI version](https://img.shields.io/pypi/v/roboticstoolbox-python?style=for-the-badge&color=blue)](https://pypi.org/project/roboticstoolbox-python/) + [![Documentation](https://img.shields.io/badge/Docs-View_Online-blue?style=for-the-badge)](https://petercorke.github.io/robotics-toolbox-python) + +

+ GitHub • + Wiki • + Changelog • + Installation +

+
+ +--- + +### Status & Ecosystem + [![A Python Robotics Package](https://raw.githubusercontent.com/petercorke/robotics-toolbox-python/main/.github/svg/py_collection.min.svg)](https://github.com/petercorke/robotics-toolbox-python) -[![Powered by Spatial Maths](https://raw.githubusercontent.com/petercorke/spatialmath-python/master/.github/svg/sm_powered.min.svg)](https://github.com/petercorke/spatialmath-python) [![QUT Centre for Robotics Open Source](https://github.com/qcr/qcr.github.io/raw/master/misc/badge.svg)](https://qcr.github.io) - -[![PyPI version](https://badge.fury.io/py/roboticstoolbox-python.svg)](https://badge.fury.io/py/roboticstoolbox-python) -[![Anaconda version](https://anaconda.org/conda-forge/roboticstoolbox-python/badges/version.svg)](https://anaconda.org/conda-forge/roboticstoolbox-python) -![PyPI - Python Version](https://img.shields.io/pypi/pyversions/roboticstoolbox-python.svg) - [![Build Status](https://github.com/petercorke/robotics-toolbox-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/petercorke/robotics-toolbox-python/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/petercorke/robotics-toolbox-python/graph/badge.svg?token=0rqN39PDEO)](https://codecov.io/gh/petercorke/robotics-toolbox-python) -[![PyPI - Downloads](https://img.shields.io/pypi/dw/roboticstoolbox-python)](https://pypistats.org/packages/roboticstoolbox-python) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/roboticstoolbox-python.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![PyPI - Downloads](https://img.shields.io/pypi/dw/roboticstoolbox-python)](https://pypistats.org/packages/roboticstoolbox-python) +[![Anaconda version](https://anaconda.org/conda-forge/roboticstoolbox-python/badges/version.svg)](https://anaconda.org/conda-forge/roboticstoolbox-python) - - - - - -
- -A Python implementation of the Robotics Toolbox for MATLAB® - -
+### Powered by + +[![Powered by Spatial Maths](https://raw.githubusercontent.com/petercorke/spatialmath-python/master/.github/svg/sm_powered.min.svg)](https://github.com/petercorke/spatialmath-python) ## Contents + + - [Synopsis](#synopsis) - [Getting going](#getting-going) - [Tutorials](#tutorials) - [Code Examples](#code-examples) - [Toolbox Research Applications](#toolbox-research-applications) -- [Toolbox ICRA Paper and Citation Info](#toolbox-icra-paper-and-citation-info) +- [References](#references) - [Using the Toolbox in your Open Source Code?](#using-the-toolbox-in-your-open-source-code) - [Common Issues and Solutions](#common-issues-and-solutions) +- [Build a JupyterLite/Pyodide Wasm wheel](#build-a-jupyterlitepyodide-wasm-wheel)
@@ -54,7 +68,7 @@ mybinder.org), and documentation (sphinx). The Toolbox provides tools for representing the kinematics and dynamics of serial-link manipulators - you can easily create your own in Denavit-Hartenberg -form, import a URDF file, or use over 30 supplied models for well-known +form, import a URDF file, or use over 50 supplied models for well-known contemporary robots from Franka-Emika, Kinova, Universal Robotics, Rethink as well as classical robots such as the Puma 560 and the Stanford arm. @@ -163,7 +177,7 @@ cd robotics-toolbox-python pip install -e . ``` -To generate a Wasm wheel that will run in the browser see the [instructions here](#build-a-jupyterlitepyodide-wasm-wheel-cp313). +To generate a Wasm wheel that will run in the browser see the [instructions here](#build-a-jupyterlitepyodide-wasm-wheel). ## Tutorials @@ -315,9 +329,25 @@ The [`notebooks`](https://github.com/petercorke/robotics-toolbox-python/tree/mai
-## Toolbox ICRA Paper and Citation Info +## References + +### Key papers + +- P. Corke, "A computer tool for simulation and analysis: the Robotics Toolbox for MATLAB," Proc. National Conf. Australian Robot Association, pp. 319–330, Melbourne, July 1995. [[PDF]](http://www.petercorke.com/RTB/ARA95.pdf) +- P. Corke, "A robotics toolbox for MATLAB," IEEE Robotics and Automation Magazine, 3(1):24–32, Sept. 1996. [[IEEE Xplore]](https://ieeexplore.ieee.org/document/486658) +- P. Corke, "A simple and systematic approach to assigning Denavit-Hartenberg parameters," IEEE Transactions on Robotics, 23(3):590–594, 2007. [[IEEE Xplore]](https://ieeexplore.ieee.org/document/4252158) — introduces the Elementary Transform Sequence (ETS) notation used throughout the Toolbox. +- J. Haviland and P. Corke, "A systematic approach to computing the manipulator Jacobian and Hessian using the elementary transform sequence," arXiv preprint, 2020. [[arXiv]](https://arxiv.org/abs/2010.08696) +- P. Corke and J. Haviland, "Not your grandmother's toolbox – the Robotics Toolbox reinvented for Python," Proc. ICRA 2021. [[IEEE Xplore]](https://ieeexplore.ieee.org/document/9561366) [[PDF]](https://bit.ly/3ChcyNp) + +### Talks + +- [Peter Corke – The Robotics Toolbox: 30 Years Old and Still Going Strong](https://www.youtube.com/watch?v=U37NMe7anXc&list=PL1pxneANaikCP5jwrw91UBkrCklkQXlyr&index=1) — a retrospective on the Toolbox's history and motivation, a good starting point before diving into the API. -Check out our ICRA 2021 paper on [IEEE Xplore](https://ieeexplore.ieee.org/document/9561366) or get the PDF from [Peter's website](https://bit.ly/3ChcyNp). +### Related book + +The Toolbox is a companion to Peter Corke's textbook [*Robotics, Vision & Control*](https://petercorke.com/books/robotics-vision-control-all-versions/) (Springer) — many docstrings and examples reference specific figures/sections from the book. + +### Citing the Toolbox If the toolbox helped you in your research, please cite @@ -366,7 +396,7 @@ See the common issues with fixes [here](https://github.com/petercorke/robotics-t ### Using the Toolbox with Windows? -Graphical visualisation via swift is currently not supported under Windows. However there is a hotfix, by changing in ```SwiftRoute.py``` +Graphical visualisation via Swift is currently not supported under Windows. However there is a hotfix, by changing in ```SwiftRoute.py``` ```self.path[9:]``` to ```self.path[10:]``` @@ -422,27 +452,62 @@ K. He, R. Newbury, T. Tran, J. Haviland, B. Burgess-Limerick, D. Kulić, P. Cork
-## Build a JupyterLite/Pyodide Wasm wheel (cp313) +## Build a JupyterLite/Pyodide Wasm wheel + +[Pyodide](https://pyodide.org/) is a full CPython distribution compiled to +WebAssembly, which is what lets the "Try it Now" JupyterLite deployment above +run this toolbox entirely client-side, no server required. Each Pyodide +release embeds one specific CPython version, and a wasm wheel is tagged with +the CPython version it was built for (`cp312`, `cp313`, ...) -— a wheel only +loads if its tag matches the CPython embedded in the Pyodide runtime actually +running. JupyterLite doesn't bundle Pyodide directly either: the +`jupyterlite-pyodide-kernel` package pulls in a specific Pyodide version per +its own release, so bumping that one package can silently change which wheel +tag your deployment now needs -— this is the single sharpest edge in this +whole pipeline. This repo's live deployment currently pins +`jupyterlite-pyodide-kernel==0.6.1` (see `.github/workflows/ci.yml`), which +embeds Pyodide 0.27.6 / CPython 3.12 — hence `cp312` below. + +Also note Pyodide's own version numbering changed in 2026: releases up to +`0.29.x` used an independent `0.x` scheme, but from Pyodide `314.0.0` onward +the version number tracks the embedded CPython version directly (`314` = +Python 3.14). "Current" no longer means a `0.x` version. + +### Use the published wheel (recommended) + +PyPI rejects the `pyodide_*` platform tag, so Wasm wheels can't be published +there -— instead, every GitHub release attaches ready-built wheels (for each +supported CPython version) as release assets. Download the one matching your +deployment's CPython version, e.g.: + +```shell script +gh release download --repo petercorke/robotics-toolbox-python --pattern '*cp312*pyodide*' +``` + +This is exactly what `ci.yml`'s `docs-build` job does to populate the live +"Try it Now" site — most people should do this rather than building locally. -This project includes a reproducible wasm wheel build target aligned to current -JupyterLite runtimes based on Python 3.13. +### Build locally -Build using cibuildwheel's Pyodide platform: +Only needed to test an unreleased change, or to target a different +CPython/Pyodide pin than the current release. Uses cibuildwheel's Pyodide +platform: ```shell script make wheel-pyodide ``` -Optionally pin the Pyodide runtime family to match your JupyterLite deployment: +Optionally pin the Pyodide runtime to match a different JupyterLite +deployment than this project's own: ```shell script -PYODIDE_VERSION=0.28.3 make wheel-pyodide +PYODIDE_VERSION=0.27.6 make wheel-pyodide ``` The target writes to `dist/` and runs `make wheel-pyodide-check`, which validates the wheel filename contains: -- `cp313-cp313` +- `cp312-cp312` - `wasm32` - `pyemscripten__` or `pyodide__` @@ -452,4 +517,14 @@ To inspect the produced artifact path: ls -1 dist/*wasm32*.whl ``` +### References + +- [Pyodide release notes](https://github.com/pyodide/pyodide/releases) — + confirms what CPython version a given Pyodide tag embeds, including the + 2026 versioning-scheme change. +- [jupyterlite-pyodide-kernel changelog](https://github.com/jupyterlite/pyodide-kernel/blob/main/CHANGELOG.md) — + the authoritative source for which Pyodide version a given kernel package + release bundles. +- [cibuildwheel Pyodide platform docs](https://cibuildwheel.pypa.io/en/stable/platforms/#pyodide-webassembly). +
\ No newline at end of file diff --git a/docs/lite/jupyter-lite.json b/docs/lite/jupyter-lite.json new file mode 100644 index 000000000..7b5b32e0e --- /dev/null +++ b/docs/lite/jupyter-lite.json @@ -0,0 +1,6 @@ +{ + "jupyter-lite-schema-version": 0, + "jupyter-config-data": { + "appUrl": "./lab/index.html?path=robotics.ipynb" + } +} diff --git a/docs/lite/jupyter_lite_config.json b/docs/lite/jupyter_lite_config.json new file mode 100644 index 000000000..dd19fabe5 --- /dev/null +++ b/docs/lite/jupyter_lite_config.json @@ -0,0 +1,7 @@ +{ + "PyodideAddon": { + "pipliteUrls": [ + "pypi/all.json" + ] + } +} diff --git a/docs/notebooks/branched-robot.ipynb b/docs/notebooks/branched-robot.ipynb index 397cffab7..37ccc1c82 100644 --- a/docs/notebooks/branched-robot.ipynb +++ b/docs/notebooks/branched-robot.ipynb @@ -6,6 +6,15 @@ "metadata": {}, "outputs": [], "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", "import roboticstoolbox as rtb\n", "from IPython.core.display import SVG" ] @@ -26,25 +35,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "┌───┬────────┬────────┬───────┬────────────────┐\n", - "│id │ link │ parent │ joint │ ETS │\n", - "├───┼────────┼────────┼───────┼────────────────┤\n", - "│ 0\u001b[0m │ link0\u001b[0m │ -\u001b[0m │ \u001b[0m │ Rz(q0)\u001b[0m │\n", - "│ 1\u001b[0m │ link1\u001b[0m │ link0\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q1)\u001b[0m │\n", - "│ 2\u001b[0m │ link2a\u001b[0m │ link1\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q2)\u001b[0m │\n", - "│ 3\u001b[0m │ link3a\u001b[0m │ link2a\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q3)\u001b[0m │\n", - "│ 4\u001b[0m │ link2b\u001b[0m │ link1\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q4)\u001b[0m │\n", - "│ 5\u001b[0m │ link3b\u001b[0m │ link2b\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q5)\u001b[0m │\n", - "│ 6\u001b[0m │ \u001b[38;5;4m@eea\u001b[0m │ link3a\u001b[0m │ None\u001b[0m │ tz(1)\u001b[0m │\n", - "│ 7\u001b[0m │ \u001b[38;5;4m@eeb\u001b[0m │ link3b\u001b[0m │ None\u001b[0m │ tz(1)\u001b[0m │\n", - "└───┴────────┴────────┴───────┴────────────────┘\n", + "\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502id \u2502 link \u2502 parent \u2502 joint \u2502 ETS \u2502\n", + "\u251c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 0\u001b[0m \u2502 link0\u001b[0m \u2502 -\u001b[0m \u2502 \u001b[0m \u2502 Rz(q0)\u001b[0m \u2502\n", + "\u2502 1\u001b[0m \u2502 link1\u001b[0m \u2502 link0\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q1)\u001b[0m \u2502\n", + "\u2502 2\u001b[0m \u2502 link2a\u001b[0m \u2502 link1\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q2)\u001b[0m \u2502\n", + "\u2502 3\u001b[0m \u2502 link3a\u001b[0m \u2502 link2a\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q3)\u001b[0m \u2502\n", + "\u2502 4\u001b[0m \u2502 link2b\u001b[0m \u2502 link1\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q4)\u001b[0m \u2502\n", + "\u2502 5\u001b[0m \u2502 link3b\u001b[0m \u2502 link2b\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q5)\u001b[0m \u2502\n", + "\u2502 6\u001b[0m \u2502 \u001b[38;5;4m@eea\u001b[0m \u2502 link3a\u001b[0m \u2502 None\u001b[0m \u2502 tz(1)\u001b[0m \u2502\n", + "\u2502 7\u001b[0m \u2502 \u001b[38;5;4m@eeb\u001b[0m \u2502 link3b\u001b[0m \u2502 None\u001b[0m \u2502 tz(1)\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n", - "┌─────┬─────┬─────┬──────┬─────┬──────┬─────┐\n", - "│name │ q0 │ q1 │ q2 │ q3 │ q4 │ q5 │\n", - "├─────┼─────┼─────┼──────┼─────┼──────┼─────┤\n", - "│ qz\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qy\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 45°\u001b[0m │ 0°\u001b[0m │ -45°\u001b[0m │ 0°\u001b[0m │\n", - "└─────┴─────┴─────┴──────┴─────┴──────┴─────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502name \u2502 q0 \u2502 q1 \u2502 q2 \u2502 q3 \u2502 q4 \u2502 q5 \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 qz\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qy\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 -45\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -259,15 +268,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "┌───┬───────┬────────┬───────┬────────────────┐\n", - "│id │ link │ parent │ joint │ ETS │\n", - "├───┼───────┼────────┼───────┼────────────────┤\n", - "│ 0\u001b[0m │ link0\u001b[0m │ -\u001b[0m │ \u001b[0m │ Rz(q0)\u001b[0m │\n", - "│ 1\u001b[0m │ link1\u001b[0m │ link0\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q1)\u001b[0m │\n", - "│ 2\u001b[0m │ link2\u001b[0m │ link1\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q4)\u001b[0m │\n", - "│ 3\u001b[0m │ link3\u001b[0m │ link2\u001b[0m │ None\u001b[0m │ tx(1) * Rz(q5)\u001b[0m │\n", - "│ 4\u001b[0m │ \u001b[38;5;4m@ee\u001b[0m │ link3\u001b[0m │ None\u001b[0m │ tz(1)\u001b[0m │\n", - "└───┴───────┴────────┴───────┴────────────────┘\n", + "\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502id \u2502 link \u2502 parent \u2502 joint \u2502 ETS \u2502\n", + "\u251c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 0\u001b[0m \u2502 link0\u001b[0m \u2502 -\u001b[0m \u2502 \u001b[0m \u2502 Rz(q0)\u001b[0m \u2502\n", + "\u2502 1\u001b[0m \u2502 link1\u001b[0m \u2502 link0\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q1)\u001b[0m \u2502\n", + "\u2502 2\u001b[0m \u2502 link2\u001b[0m \u2502 link1\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q4)\u001b[0m \u2502\n", + "\u2502 3\u001b[0m \u2502 link3\u001b[0m \u2502 link2\u001b[0m \u2502 None\u001b[0m \u2502 tx(1) * Rz(q5)\u001b[0m \u2502\n", + "\u2502 4\u001b[0m \u2502 \u001b[38;5;4m@ee\u001b[0m \u2502 link3\u001b[0m \u2502 None\u001b[0m \u2502 tz(1)\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -459,7 +468,7 @@ { "data": { "image/svg+xml": [ - "GBASEBASEbase_footprintbase_footprintBASE->base_footprintbase_linkbase_linkbase_footprint->base_linktz(0.051)base_bellow_linkbase_bellow_linkbase_link->base_bellow_linktx(-0.29) * tz(0.8)base_laser_linkbase_laser_linkbase_link->base_laser_linktx(0.275) * tz(0.252)fl_caster_rotation_linkfl_caster_rotation_linkbase_link->fl_caster_rotation_linktx(0.2246) * ty(0.2246) * tz(0.0282) * Rz(q0)fr_caster_rotation_linkfr_caster_rotation_linkbase_link->fr_caster_rotation_linktx(0.2246) * ty(-0.2246) * tz(0.0282) * Rz(q3)bl_caster_rotation_linkbl_caster_rotation_linkbase_link->bl_caster_rotation_linktx(-0.2246) * ty(0.2246) * tz(0.0282) * Rz(q6)br_caster_rotation_linkbr_caster_rotation_linkbase_link->br_caster_rotation_linktx(-0.2246) * ty(-0.2246) * tz(0.0282) * Rz(q9)torso_lift_linktorso_lift_linkbase_link->torso_lift_linktx(-0.05) * tz(0.739675) * tz(q12)torso_lift_motor_screw_linktorso_lift_motor_screw_linkbase_link->torso_lift_motor_screw_linktx(-0.15) * tz(0.7) * Rz(q44)fl_caster_l_wheel_linkfl_caster_l_wheel_linkfl_caster_rotation_link->fl_caster_l_wheel_linkty(0.049) * Ry(q1)fl_caster_r_wheel_linkfl_caster_r_wheel_linkfl_caster_rotation_link->fl_caster_r_wheel_linkty(-0.049) * Ry(q2)fr_caster_l_wheel_linkfr_caster_l_wheel_linkfr_caster_rotation_link->fr_caster_l_wheel_linkty(0.049) * Ry(q4)fr_caster_r_wheel_linkfr_caster_r_wheel_linkfr_caster_rotation_link->fr_caster_r_wheel_linkty(-0.049) * Ry(q5)bl_caster_l_wheel_linkbl_caster_l_wheel_linkbl_caster_rotation_link->bl_caster_l_wheel_linkty(0.049) * Ry(q7)bl_caster_r_wheel_linkbl_caster_r_wheel_linkbl_caster_rotation_link->bl_caster_r_wheel_linkty(-0.049) * Ry(q8)br_caster_l_wheel_linkbr_caster_l_wheel_linkbr_caster_rotation_link->br_caster_l_wheel_linkty(0.049) * Ry(q10)br_caster_r_wheel_linkbr_caster_r_wheel_linkbr_caster_rotation_link->br_caster_r_wheel_linkty(-0.049) * Ry(q11)l_torso_lift_side_plate_linkl_torso_lift_side_plate_linktorso_lift_link->l_torso_lift_side_plate_linktx(0.0535) * ty(0.209285) * tz(0.176625)r_torso_lift_side_plate_linkr_torso_lift_side_plate_linktorso_lift_link->r_torso_lift_side_plate_linktx(0.0535) * ty(-0.209285) * tz(0.176625)imu_linkimu_linktorso_lift_link->imu_linktx(-0.02977) * ty(-0.1497) * tz(0.164) * Ry(180°)head_pan_linkhead_pan_linktorso_lift_link->head_pan_linktx(-0.01707) * tz(0.38145) * Rz(q13)laser_tilt_mount_linklaser_tilt_mount_linktorso_lift_link->laser_tilt_mount_linktx(0.09893) * tz(0.227) * Ry(q15)r_shoulder_pan_linkr_shoulder_pan_linktorso_lift_link->r_shoulder_pan_linkty(-0.188) * Rz(q16)l_shoulder_pan_linkl_shoulder_pan_linktorso_lift_link->l_shoulder_pan_linkty(0.188) * Rz(q30)head_tilt_linkhead_tilt_linkhead_pan_link->head_tilt_linktx(0.068) * Ry(q14)head_plate_framehead_plate_framehead_tilt_link->head_plate_frametx(0.0232) * tz(0.0645)sensor_mount_linksensor_mount_linkhead_plate_frame->sensor_mount_linkprojector_wg6802418_frameprojector_wg6802418_framehead_plate_frame->projector_wg6802418_framety(0.11) * tz(0.0546)high_def_framehigh_def_framesensor_mount_link->high_def_frametx(0.046457) * ty(-0.11) * tz(0.0546)double_stereo_linkdouble_stereo_linksensor_mount_link->double_stereo_linkhigh_def_optical_framehigh_def_optical_framehigh_def_frame->high_def_optical_frameRz(-90°) * Rx(-90°)wide_stereo_linkwide_stereo_linkdouble_stereo_link->wide_stereo_linktx(0.045) * ty(0.03) * tz(0.0501)narrow_stereo_linknarrow_stereo_linkdouble_stereo_link->narrow_stereo_linktx(0.045) * ty(0.06) * tz(0.0501)wide_stereo_optical_framewide_stereo_optical_framewide_stereo_link->wide_stereo_optical_frameRz(-90°) * Rx(-90°)wide_stereo_l_stereo_camera_framewide_stereo_l_stereo_camera_framewide_stereo_link->wide_stereo_l_stereo_camera_framewide_stereo_l_stereo_camera_optical_framewide_stereo_l_stereo_camera_optical_framewide_stereo_l_stereo_camera_frame->wide_stereo_l_stereo_camera_optical_frameRz(-90°) * Rx(-90°)wide_stereo_r_stereo_camera_framewide_stereo_r_stereo_camera_framewide_stereo_l_stereo_camera_frame->wide_stereo_r_stereo_camera_framety(-0.09)wide_stereo_r_stereo_camera_optical_framewide_stereo_r_stereo_camera_optical_framewide_stereo_r_stereo_camera_frame->wide_stereo_r_stereo_camera_optical_frameRz(-90°) * Rx(-90°)narrow_stereo_optical_framenarrow_stereo_optical_framenarrow_stereo_link->narrow_stereo_optical_frameRz(-90°) * Rx(-90°)narrow_stereo_l_stereo_camera_framenarrow_stereo_l_stereo_camera_framenarrow_stereo_link->narrow_stereo_l_stereo_camera_framenarrow_stereo_l_stereo_camera_optical_framenarrow_stereo_l_stereo_camera_optical_framenarrow_stereo_l_stereo_camera_frame->narrow_stereo_l_stereo_camera_optical_frameRz(-90°) * Rx(-90°)narrow_stereo_r_stereo_camera_framenarrow_stereo_r_stereo_camera_framenarrow_stereo_l_stereo_camera_frame->narrow_stereo_r_stereo_camera_framety(-0.09)narrow_stereo_r_stereo_camera_optical_framenarrow_stereo_r_stereo_camera_optical_framenarrow_stereo_r_stereo_camera_frame->narrow_stereo_r_stereo_camera_optical_frameRz(-90°) * Rx(-90°)projector_wg6802418_child_frameprojector_wg6802418_child_frameprojector_wg6802418_frame->projector_wg6802418_child_frameRy(-90°)laser_tilt_linklaser_tilt_linklaser_tilt_mount_link->laser_tilt_linktz(0.03)r_shoulder_lift_linkr_shoulder_lift_linkr_shoulder_pan_link->r_shoulder_lift_linktx(0.1) * Ry(q17)r_upper_arm_roll_linkr_upper_arm_roll_linkr_shoulder_lift_link->r_upper_arm_roll_linkRx(q18)r_upper_arm_linkr_upper_arm_linkr_upper_arm_roll_link->r_upper_arm_linkr_elbow_flex_linkr_elbow_flex_linkr_upper_arm_link->r_elbow_flex_linktx(0.4) * Ry(q19)r_forearm_roll_linkr_forearm_roll_linkr_elbow_flex_link->r_forearm_roll_linkRx(q20)r_forearm_linkr_forearm_linkr_forearm_roll_link->r_forearm_linkr_forearm_cam_framer_forearm_cam_framer_forearm_roll_link->r_forearm_cam_frametx(0.135) * tz(0.044) * Ry(-32.25°) * Rx(90°)r_wrist_flex_linkr_wrist_flex_linkr_forearm_link->r_wrist_flex_linktx(0.321) * Ry(q21)r_wrist_roll_linkr_wrist_roll_linkr_wrist_flex_link->r_wrist_roll_linkRx(q22)r_gripper_palm_linkr_gripper_palm_linkr_wrist_roll_link->r_gripper_palm_linkr_gripper_led_framer_gripper_led_framer_gripper_palm_link->r_gripper_led_frametx(0.0513) * tz(0.0244)r_gripper_motor_accelerometer_linkr_gripper_motor_accelerometer_linkr_gripper_palm_link->r_gripper_motor_accelerometer_linkr_gripper_tool_framer_gripper_tool_framer_gripper_palm_link->r_gripper_tool_frametx(0.18)r_gripper_motor_slider_linkr_gripper_motor_slider_linkr_gripper_palm_link->r_gripper_motor_slider_linktx(0.16828) * tx(q23)r_gripper_l_finger_linkr_gripper_l_finger_linkr_gripper_palm_link->r_gripper_l_finger_linktx(0.07691) * ty(0.01) * Rz(q25)r_gripper_r_finger_linkr_gripper_r_finger_linkr_gripper_palm_link->r_gripper_r_finger_linktx(0.07691) * ty(-0.01) * Rz(-q27)r_gripper_motor_screw_linkr_gripper_motor_screw_linkr_gripper_motor_slider_link->r_gripper_motor_screw_linkRy(q24)r_gripper_l_finger_tip_linkr_gripper_l_finger_tip_linkr_gripper_l_finger_link->r_gripper_l_finger_tip_linktx(0.09137) * ty(0.00495) * Rz(-q26)r_gripper_r_finger_tip_linkr_gripper_r_finger_tip_linkr_gripper_r_finger_link->r_gripper_r_finger_tip_linktx(0.09137) * ty(-0.00495) * Rz(q28)r_gripper_l_finger_tip_framer_gripper_l_finger_tip_framer_gripper_r_finger_tip_link->r_gripper_l_finger_tip_framety(q29)r_forearm_cam_optical_framer_forearm_cam_optical_framer_forearm_cam_frame->r_forearm_cam_optical_frameRz(-90°) * Rx(-90°)l_shoulder_lift_linkl_shoulder_lift_linkl_shoulder_pan_link->l_shoulder_lift_linktx(0.1) * Ry(q31)l_upper_arm_roll_linkl_upper_arm_roll_linkl_shoulder_lift_link->l_upper_arm_roll_linkRx(q32)l_upper_arm_linkl_upper_arm_linkl_upper_arm_roll_link->l_upper_arm_linkl_elbow_flex_linkl_elbow_flex_linkl_upper_arm_link->l_elbow_flex_linktx(0.4) * Ry(q33)l_forearm_roll_linkl_forearm_roll_linkl_elbow_flex_link->l_forearm_roll_linkRx(q34)l_forearm_linkl_forearm_linkl_forearm_roll_link->l_forearm_linkl_forearm_cam_framel_forearm_cam_framel_forearm_roll_link->l_forearm_cam_frametx(0.135) * tz(0.044) * Ry(-32.25°) * Rx(-90°)l_wrist_flex_linkl_wrist_flex_linkl_forearm_link->l_wrist_flex_linktx(0.321) * Ry(q35)l_wrist_roll_linkl_wrist_roll_linkl_wrist_flex_link->l_wrist_roll_linkRx(q36)l_gripper_palm_linkl_gripper_palm_linkl_wrist_roll_link->l_gripper_palm_linkl_gripper_led_framel_gripper_led_framel_gripper_palm_link->l_gripper_led_frametx(0.0513) * tz(0.0244)l_gripper_motor_accelerometer_linkl_gripper_motor_accelerometer_linkl_gripper_palm_link->l_gripper_motor_accelerometer_linkl_gripper_tool_framel_gripper_tool_framel_gripper_palm_link->l_gripper_tool_frametx(0.18)l_gripper_motor_slider_linkl_gripper_motor_slider_linkl_gripper_palm_link->l_gripper_motor_slider_linktx(0.16828) * tx(q37)l_gripper_l_finger_linkl_gripper_l_finger_linkl_gripper_palm_link->l_gripper_l_finger_linktx(0.07691) * ty(0.01) * Rz(q39)l_gripper_r_finger_linkl_gripper_r_finger_linkl_gripper_palm_link->l_gripper_r_finger_linktx(0.07691) * ty(-0.01) * Rz(-q41)l_gripper_motor_screw_linkl_gripper_motor_screw_linkl_gripper_motor_slider_link->l_gripper_motor_screw_linkRy(q38)l_gripper_l_finger_tip_linkl_gripper_l_finger_tip_linkl_gripper_l_finger_link->l_gripper_l_finger_tip_linktx(0.09137) * ty(0.00495) * Rz(-q40)l_gripper_r_finger_tip_linkl_gripper_r_finger_tip_linkl_gripper_r_finger_link->l_gripper_r_finger_tip_linktx(0.09137) * ty(-0.00495) * Rz(q42)l_gripper_l_finger_tip_framel_gripper_l_finger_tip_framel_gripper_r_finger_tip_link->l_gripper_l_finger_tip_framety(q43)l_forearm_cam_optical_framel_forearm_cam_optical_framel_forearm_cam_frame->l_forearm_cam_optical_frameRz(-90°) * Rx(-90°)" + "GBASEBASEbase_footprintbase_footprintBASE->base_footprintbase_linkbase_linkbase_footprint->base_linktz(0.051)base_bellow_linkbase_bellow_linkbase_link->base_bellow_linktx(-0.29) * tz(0.8)base_laser_linkbase_laser_linkbase_link->base_laser_linktx(0.275) * tz(0.252)fl_caster_rotation_linkfl_caster_rotation_linkbase_link->fl_caster_rotation_linktx(0.2246) * ty(0.2246) * tz(0.0282) * Rz(q0)fr_caster_rotation_linkfr_caster_rotation_linkbase_link->fr_caster_rotation_linktx(0.2246) * ty(-0.2246) * tz(0.0282) * Rz(q3)bl_caster_rotation_linkbl_caster_rotation_linkbase_link->bl_caster_rotation_linktx(-0.2246) * ty(0.2246) * tz(0.0282) * Rz(q6)br_caster_rotation_linkbr_caster_rotation_linkbase_link->br_caster_rotation_linktx(-0.2246) * ty(-0.2246) * tz(0.0282) * Rz(q9)torso_lift_linktorso_lift_linkbase_link->torso_lift_linktx(-0.05) * tz(0.739675) * tz(q12)torso_lift_motor_screw_linktorso_lift_motor_screw_linkbase_link->torso_lift_motor_screw_linktx(-0.15) * tz(0.7) * Rz(q44)fl_caster_l_wheel_linkfl_caster_l_wheel_linkfl_caster_rotation_link->fl_caster_l_wheel_linkty(0.049) * Ry(q1)fl_caster_r_wheel_linkfl_caster_r_wheel_linkfl_caster_rotation_link->fl_caster_r_wheel_linkty(-0.049) * Ry(q2)fr_caster_l_wheel_linkfr_caster_l_wheel_linkfr_caster_rotation_link->fr_caster_l_wheel_linkty(0.049) * Ry(q4)fr_caster_r_wheel_linkfr_caster_r_wheel_linkfr_caster_rotation_link->fr_caster_r_wheel_linkty(-0.049) * Ry(q5)bl_caster_l_wheel_linkbl_caster_l_wheel_linkbl_caster_rotation_link->bl_caster_l_wheel_linkty(0.049) * Ry(q7)bl_caster_r_wheel_linkbl_caster_r_wheel_linkbl_caster_rotation_link->bl_caster_r_wheel_linkty(-0.049) * Ry(q8)br_caster_l_wheel_linkbr_caster_l_wheel_linkbr_caster_rotation_link->br_caster_l_wheel_linkty(0.049) * Ry(q10)br_caster_r_wheel_linkbr_caster_r_wheel_linkbr_caster_rotation_link->br_caster_r_wheel_linkty(-0.049) * Ry(q11)l_torso_lift_side_plate_linkl_torso_lift_side_plate_linktorso_lift_link->l_torso_lift_side_plate_linktx(0.0535) * ty(0.209285) * tz(0.176625)r_torso_lift_side_plate_linkr_torso_lift_side_plate_linktorso_lift_link->r_torso_lift_side_plate_linktx(0.0535) * ty(-0.209285) * tz(0.176625)imu_linkimu_linktorso_lift_link->imu_linktx(-0.02977) * ty(-0.1497) * tz(0.164) * Ry(180\u00b0)head_pan_linkhead_pan_linktorso_lift_link->head_pan_linktx(-0.01707) * tz(0.38145) * Rz(q13)laser_tilt_mount_linklaser_tilt_mount_linktorso_lift_link->laser_tilt_mount_linktx(0.09893) * tz(0.227) * Ry(q15)r_shoulder_pan_linkr_shoulder_pan_linktorso_lift_link->r_shoulder_pan_linkty(-0.188) * Rz(q16)l_shoulder_pan_linkl_shoulder_pan_linktorso_lift_link->l_shoulder_pan_linkty(0.188) * Rz(q30)head_tilt_linkhead_tilt_linkhead_pan_link->head_tilt_linktx(0.068) * Ry(q14)head_plate_framehead_plate_framehead_tilt_link->head_plate_frametx(0.0232) * tz(0.0645)sensor_mount_linksensor_mount_linkhead_plate_frame->sensor_mount_linkprojector_wg6802418_frameprojector_wg6802418_framehead_plate_frame->projector_wg6802418_framety(0.11) * tz(0.0546)high_def_framehigh_def_framesensor_mount_link->high_def_frametx(0.046457) * ty(-0.11) * tz(0.0546)double_stereo_linkdouble_stereo_linksensor_mount_link->double_stereo_linkhigh_def_optical_framehigh_def_optical_framehigh_def_frame->high_def_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)wide_stereo_linkwide_stereo_linkdouble_stereo_link->wide_stereo_linktx(0.045) * ty(0.03) * tz(0.0501)narrow_stereo_linknarrow_stereo_linkdouble_stereo_link->narrow_stereo_linktx(0.045) * ty(0.06) * tz(0.0501)wide_stereo_optical_framewide_stereo_optical_framewide_stereo_link->wide_stereo_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)wide_stereo_l_stereo_camera_framewide_stereo_l_stereo_camera_framewide_stereo_link->wide_stereo_l_stereo_camera_framewide_stereo_l_stereo_camera_optical_framewide_stereo_l_stereo_camera_optical_framewide_stereo_l_stereo_camera_frame->wide_stereo_l_stereo_camera_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)wide_stereo_r_stereo_camera_framewide_stereo_r_stereo_camera_framewide_stereo_l_stereo_camera_frame->wide_stereo_r_stereo_camera_framety(-0.09)wide_stereo_r_stereo_camera_optical_framewide_stereo_r_stereo_camera_optical_framewide_stereo_r_stereo_camera_frame->wide_stereo_r_stereo_camera_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)narrow_stereo_optical_framenarrow_stereo_optical_framenarrow_stereo_link->narrow_stereo_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)narrow_stereo_l_stereo_camera_framenarrow_stereo_l_stereo_camera_framenarrow_stereo_link->narrow_stereo_l_stereo_camera_framenarrow_stereo_l_stereo_camera_optical_framenarrow_stereo_l_stereo_camera_optical_framenarrow_stereo_l_stereo_camera_frame->narrow_stereo_l_stereo_camera_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)narrow_stereo_r_stereo_camera_framenarrow_stereo_r_stereo_camera_framenarrow_stereo_l_stereo_camera_frame->narrow_stereo_r_stereo_camera_framety(-0.09)narrow_stereo_r_stereo_camera_optical_framenarrow_stereo_r_stereo_camera_optical_framenarrow_stereo_r_stereo_camera_frame->narrow_stereo_r_stereo_camera_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)projector_wg6802418_child_frameprojector_wg6802418_child_frameprojector_wg6802418_frame->projector_wg6802418_child_frameRy(-90\u00b0)laser_tilt_linklaser_tilt_linklaser_tilt_mount_link->laser_tilt_linktz(0.03)r_shoulder_lift_linkr_shoulder_lift_linkr_shoulder_pan_link->r_shoulder_lift_linktx(0.1) * Ry(q17)r_upper_arm_roll_linkr_upper_arm_roll_linkr_shoulder_lift_link->r_upper_arm_roll_linkRx(q18)r_upper_arm_linkr_upper_arm_linkr_upper_arm_roll_link->r_upper_arm_linkr_elbow_flex_linkr_elbow_flex_linkr_upper_arm_link->r_elbow_flex_linktx(0.4) * Ry(q19)r_forearm_roll_linkr_forearm_roll_linkr_elbow_flex_link->r_forearm_roll_linkRx(q20)r_forearm_linkr_forearm_linkr_forearm_roll_link->r_forearm_linkr_forearm_cam_framer_forearm_cam_framer_forearm_roll_link->r_forearm_cam_frametx(0.135) * tz(0.044) * Ry(-32.25\u00b0) * Rx(90\u00b0)r_wrist_flex_linkr_wrist_flex_linkr_forearm_link->r_wrist_flex_linktx(0.321) * Ry(q21)r_wrist_roll_linkr_wrist_roll_linkr_wrist_flex_link->r_wrist_roll_linkRx(q22)r_gripper_palm_linkr_gripper_palm_linkr_wrist_roll_link->r_gripper_palm_linkr_gripper_led_framer_gripper_led_framer_gripper_palm_link->r_gripper_led_frametx(0.0513) * tz(0.0244)r_gripper_motor_accelerometer_linkr_gripper_motor_accelerometer_linkr_gripper_palm_link->r_gripper_motor_accelerometer_linkr_gripper_tool_framer_gripper_tool_framer_gripper_palm_link->r_gripper_tool_frametx(0.18)r_gripper_motor_slider_linkr_gripper_motor_slider_linkr_gripper_palm_link->r_gripper_motor_slider_linktx(0.16828) * tx(q23)r_gripper_l_finger_linkr_gripper_l_finger_linkr_gripper_palm_link->r_gripper_l_finger_linktx(0.07691) * ty(0.01) * Rz(q25)r_gripper_r_finger_linkr_gripper_r_finger_linkr_gripper_palm_link->r_gripper_r_finger_linktx(0.07691) * ty(-0.01) * Rz(-q27)r_gripper_motor_screw_linkr_gripper_motor_screw_linkr_gripper_motor_slider_link->r_gripper_motor_screw_linkRy(q24)r_gripper_l_finger_tip_linkr_gripper_l_finger_tip_linkr_gripper_l_finger_link->r_gripper_l_finger_tip_linktx(0.09137) * ty(0.00495) * Rz(-q26)r_gripper_r_finger_tip_linkr_gripper_r_finger_tip_linkr_gripper_r_finger_link->r_gripper_r_finger_tip_linktx(0.09137) * ty(-0.00495) * Rz(q28)r_gripper_l_finger_tip_framer_gripper_l_finger_tip_framer_gripper_r_finger_tip_link->r_gripper_l_finger_tip_framety(q29)r_forearm_cam_optical_framer_forearm_cam_optical_framer_forearm_cam_frame->r_forearm_cam_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)l_shoulder_lift_linkl_shoulder_lift_linkl_shoulder_pan_link->l_shoulder_lift_linktx(0.1) * Ry(q31)l_upper_arm_roll_linkl_upper_arm_roll_linkl_shoulder_lift_link->l_upper_arm_roll_linkRx(q32)l_upper_arm_linkl_upper_arm_linkl_upper_arm_roll_link->l_upper_arm_linkl_elbow_flex_linkl_elbow_flex_linkl_upper_arm_link->l_elbow_flex_linktx(0.4) * Ry(q33)l_forearm_roll_linkl_forearm_roll_linkl_elbow_flex_link->l_forearm_roll_linkRx(q34)l_forearm_linkl_forearm_linkl_forearm_roll_link->l_forearm_linkl_forearm_cam_framel_forearm_cam_framel_forearm_roll_link->l_forearm_cam_frametx(0.135) * tz(0.044) * Ry(-32.25\u00b0) * Rx(-90\u00b0)l_wrist_flex_linkl_wrist_flex_linkl_forearm_link->l_wrist_flex_linktx(0.321) * Ry(q35)l_wrist_roll_linkl_wrist_roll_linkl_wrist_flex_link->l_wrist_roll_linkRx(q36)l_gripper_palm_linkl_gripper_palm_linkl_wrist_roll_link->l_gripper_palm_linkl_gripper_led_framel_gripper_led_framel_gripper_palm_link->l_gripper_led_frametx(0.0513) * tz(0.0244)l_gripper_motor_accelerometer_linkl_gripper_motor_accelerometer_linkl_gripper_palm_link->l_gripper_motor_accelerometer_linkl_gripper_tool_framel_gripper_tool_framel_gripper_palm_link->l_gripper_tool_frametx(0.18)l_gripper_motor_slider_linkl_gripper_motor_slider_linkl_gripper_palm_link->l_gripper_motor_slider_linktx(0.16828) * tx(q37)l_gripper_l_finger_linkl_gripper_l_finger_linkl_gripper_palm_link->l_gripper_l_finger_linktx(0.07691) * ty(0.01) * Rz(q39)l_gripper_r_finger_linkl_gripper_r_finger_linkl_gripper_palm_link->l_gripper_r_finger_linktx(0.07691) * ty(-0.01) * Rz(-q41)l_gripper_motor_screw_linkl_gripper_motor_screw_linkl_gripper_motor_slider_link->l_gripper_motor_screw_linkRy(q38)l_gripper_l_finger_tip_linkl_gripper_l_finger_tip_linkl_gripper_l_finger_link->l_gripper_l_finger_tip_linktx(0.09137) * ty(0.00495) * Rz(-q40)l_gripper_r_finger_tip_linkl_gripper_r_finger_tip_linkl_gripper_r_finger_link->l_gripper_r_finger_tip_linktx(0.09137) * ty(-0.00495) * Rz(q42)l_gripper_l_finger_tip_framel_gripper_l_finger_tip_framel_gripper_r_finger_tip_link->l_gripper_l_finger_tip_framety(q43)l_forearm_cam_optical_framel_forearm_cam_optical_framel_forearm_cam_frame->l_forearm_cam_optical_frameRz(-90\u00b0) * Rx(-90\u00b0)" ], "text/plain": [ "" diff --git a/docs/notebooks/dynamics.ipynb b/docs/notebooks/dynamics.ipynb index 90541567e..1a3ebf5f5 100644 --- a/docs/notebooks/dynamics.ipynb +++ b/docs/notebooks/dynamics.ipynb @@ -8,6 +8,15 @@ }, "outputs": [], "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", "import numpy as np\n", "import roboticstoolbox as rtb\n", "from spatialmath import *\n", @@ -71,7 +80,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -1168,7 +1177,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -2192,7 +2201,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -3416,7 +3425,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -4405,7 +4414,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", diff --git a/docs/notebooks/ets.ipynb b/docs/notebooks/ets.ipynb index 6f720626d..4b8113733 100644 --- a/docs/notebooks/ets.ipynb +++ b/docs/notebooks/ets.ipynb @@ -46,6 +46,15 @@ } ], "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", "import roboticstoolbox as rtb\n", "import numpy as np\n", "from spatialmath import SE3\n", @@ -85,7 +94,7 @@ { "data": { "text/plain": [ - "Rx(45°)" + "Rx(45\u00b0)" ] }, "execution_count": 2, @@ -1317,17 +1326,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "┌──────┬────────┬────────────────────────────────────────────────┐\n", - "│ link │ parent │ ETS │\n", - "├──────┼────────┼────────────────────────────────────────────────┤\n", - "│link0\u001b[0m │ -\u001b[0m │ tz(0.672) * Rz(q0)\u001b[0m │\n", - "│link1\u001b[0m │ link0\u001b[0m │ ty(0.2337) * Ry(q1)\u001b[0m │\n", - "│link2\u001b[0m │ link1\u001b[0m │ tz(0.4318) * tx(0.0203) * ty(-0.0837) * Ry(q2)\u001b[0m │\n", - "│link3\u001b[0m │ link2\u001b[0m │ tz(0.4318) * Rz(q3)\u001b[0m │\n", - "│link4\u001b[0m │ link3\u001b[0m │ Ry(q4)\u001b[0m │\n", - "│link5\u001b[0m │ link4\u001b[0m │ Rz(q5)\u001b[0m │\n", - "│ \u001b[38;5;4mee\u001b[0m │ link5\u001b[0m │ tx(0.2)\u001b[0m │\n", - "└──────┴────────┴────────────────────────────────────────────────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502 link \u2502 parent \u2502 ETS \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502link0\u001b[0m \u2502 -\u001b[0m \u2502 tz(0.672) * Rz(q0)\u001b[0m \u2502\n", + "\u2502link1\u001b[0m \u2502 link0\u001b[0m \u2502 ty(0.2337) * Ry(q1)\u001b[0m \u2502\n", + "\u2502link2\u001b[0m \u2502 link1\u001b[0m \u2502 tz(0.4318) * tx(0.0203) * ty(-0.0837) * Ry(q2)\u001b[0m \u2502\n", + "\u2502link3\u001b[0m \u2502 link2\u001b[0m \u2502 tz(0.4318) * Rz(q3)\u001b[0m \u2502\n", + "\u2502link4\u001b[0m \u2502 link3\u001b[0m \u2502 Ry(q4)\u001b[0m \u2502\n", + "\u2502link5\u001b[0m \u2502 link4\u001b[0m \u2502 Rz(q5)\u001b[0m \u2502\n", + "\u2502 \u001b[38;5;4mee\u001b[0m \u2502 link5\u001b[0m \u2502 tx(0.2)\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -1364,25 +1373,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "┏━━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓\n", - "┃θⱼ ┃ dⱼ ┃ aⱼ ┃ ⍺ⱼ ┃ q⁻ ┃ q⁺ ┃\n", - "┣━━━━╋━━━━━━━━━╋━━━━━━━━╋━━━━━━━━╋━━━━━━━━━╋━━━━━━━━┫\n", - "┃ q1\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃ -160.0°\u001b[0m ┃ 160.0°\u001b[0m ┃\n", - "┃ q2\u001b[0m ┃ 0\u001b[0m ┃ 0.4318\u001b[0m ┃ 0.0°\u001b[0m ┃ -110.0°\u001b[0m ┃ 110.0°\u001b[0m ┃\n", - "┃ q3\u001b[0m ┃ 0.15005\u001b[0m ┃ 0.0203\u001b[0m ┃ -90.0°\u001b[0m ┃ -135.0°\u001b[0m ┃ 135.0°\u001b[0m ┃\n", - "┃ q4\u001b[0m ┃ 0.4318\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃ -266.0°\u001b[0m ┃ 266.0°\u001b[0m ┃\n", - "┃ q5\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ -90.0°\u001b[0m ┃ -100.0°\u001b[0m ┃ 100.0°\u001b[0m ┃\n", - "┃ q6\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ 0.0°\u001b[0m ┃ -266.0°\u001b[0m ┃ 266.0°\u001b[0m ┃\n", - "┗━━━━┻━━━━━━━━━┻━━━━━━━━┻━━━━━━━━┻━━━━━━━━━┻━━━━━━━━┛\n", + "\u250f\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n", + "\u2503\u03b8\u2c7c \u2503 d\u2c7c \u2503 a\u2c7c \u2503 \u237a\u2c7c \u2503 q\u207b \u2503 q\u207a \u2503\n", + "\u2523\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n", + "\u2503 q1\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -160.0\u00b0\u001b[0m \u2503 160.0\u00b0\u001b[0m \u2503\n", + "\u2503 q2\u001b[0m \u2503 0\u001b[0m \u2503 0.4318\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 -110.0\u00b0\u001b[0m \u2503 110.0\u00b0\u001b[0m \u2503\n", + "\u2503 q3\u001b[0m \u2503 0.15005\u001b[0m \u2503 0.0203\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -135.0\u00b0\u001b[0m \u2503 135.0\u00b0\u001b[0m \u2503\n", + "\u2503 q4\u001b[0m \u2503 0.4318\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -266.0\u00b0\u001b[0m \u2503 266.0\u00b0\u001b[0m \u2503\n", + "\u2503 q5\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -100.0\u00b0\u001b[0m \u2503 100.0\u00b0\u001b[0m \u2503\n", + "\u2503 q6\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 -266.0\u00b0\u001b[0m \u2503 266.0\u00b0\u001b[0m \u2503\n", + "\u2517\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n", "\n", - "┌─────┬─────┬──────┬───────┬─────┬──────┬─────┐\n", - "│name │ q0 │ q1 │ q2 │ q3 │ q4 │ q5 │\n", - "├─────┼─────┼──────┼───────┼─────┼──────┼─────┤\n", - "│ qz\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qr\u001b[0m │ 0°\u001b[0m │ 90°\u001b[0m │ -90°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qs\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ -90°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qn\u001b[0m │ 0°\u001b[0m │ 45°\u001b[0m │ 180°\u001b[0m │ 0°\u001b[0m │ 45°\u001b[0m │ 0°\u001b[0m │\n", - "└─────┴─────┴──────┴───────┴─────┴──────┴─────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502name \u2502 q0 \u2502 q1 \u2502 q2 \u2502 q3 \u2502 q4 \u2502 q5 \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 qz\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qr\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 90\u00b0\u001b[0m \u2502 -90\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qs\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 -90\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qn\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502 180\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -1401,7 +1410,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Rx(90°) * Rz(q0) * tx(0.4318) * Rz(q1) * tx(0.0203) * Rx(-90°) * Rz(q2) * tz(0.15005) * Rx(90°) * Rz(q3) * tz(0.4318) * Rx(-90°) * Rz(q4) * Rz(q5)\n" + "Rx(90\u00b0) * Rz(q0) * tx(0.4318) * Rz(q1) * tx(0.0203) * Rx(-90\u00b0) * Rz(q2) * tz(0.15005) * Rx(90\u00b0) * Rz(q3) * tz(0.4318) * Rx(-90\u00b0) * Rz(q4) * Rz(q5)\n" ] } ], @@ -1425,22 +1434,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "┏━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓\n", - "┃ θⱼ ┃ dⱼ ┃ aⱼ ┃ ⍺ⱼ ┃ q⁻ ┃ q⁺ ┃\n", - "┣━━━━━━━╋━━━━━━━╋━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━┫\n", - "┃ q1\u001b[0m ┃ 0.412\u001b[0m ┃ 0\u001b[0m ┃ -90.0°\u001b[0m ┃ -170.0°\u001b[0m ┃ 170.0°\u001b[0m ┃\n", - "┃ q2\u001b[0m ┃ 0.154\u001b[0m ┃ 0.0\u001b[0m ┃ 90.0°\u001b[0m ┃ -170.0°\u001b[0m ┃ 170.0°\u001b[0m ┃\n", - "┃-90.0°\u001b[0m ┃ q3\u001b[0m ┃ 0.0203\u001b[0m ┃ 0.0°\u001b[0m ┃ 0.30479999999999996\u001b[0m ┃ 1.27\u001b[0m ┃\n", - "┃ q4\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ -90.0°\u001b[0m ┃ -170.0°\u001b[0m ┃ 170.0°\u001b[0m ┃\n", - "┃ q5\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃ -90.0°\u001b[0m ┃ 90.0°\u001b[0m ┃\n", - "┃ q6\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ 0.0°\u001b[0m ┃ -170.0°\u001b[0m ┃ 170.0°\u001b[0m ┃\n", - "┗━━━━━━━┻━━━━━━━┻━━━━━━━━┻━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━┛\n", + "\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n", + "\u2503 \u03b8\u2c7c \u2503 d\u2c7c \u2503 a\u2c7c \u2503 \u237a\u2c7c \u2503 q\u207b \u2503 q\u207a \u2503\n", + "\u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n", + "\u2503 q1\u001b[0m \u2503 0.412\u001b[0m \u2503 0\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -170.0\u00b0\u001b[0m \u2503 170.0\u00b0\u001b[0m \u2503\n", + "\u2503 q2\u001b[0m \u2503 0.154\u001b[0m \u2503 0.0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -170.0\u00b0\u001b[0m \u2503 170.0\u00b0\u001b[0m \u2503\n", + "\u2503-90.0\u00b0\u001b[0m \u2503 q3\u001b[0m \u2503 0.0203\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 0.30479999999999996\u001b[0m \u2503 1.27\u001b[0m \u2503\n", + "\u2503 q4\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -170.0\u00b0\u001b[0m \u2503 170.0\u00b0\u001b[0m \u2503\n", + "\u2503 q5\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503\n", + "\u2503 q6\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 -170.0\u00b0\u001b[0m \u2503 170.0\u00b0\u001b[0m \u2503\n", + "\u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n", "\n", - "┌─────┬─────┬─────┬────┬─────┬─────┬─────┐\n", - "│name │ q0 │ q1 │ q2 │ q3 │ q4 │ q5 │\n", - "├─────┼─────┼─────┼────┼─────┼─────┼─────┤\n", - "│ qz\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "└─────┴─────┴─────┴────┴─────┴─────┴─────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502name \u2502 q0 \u2502 q1 \u2502 q2 \u2502 q3 \u2502 q4 \u2502 q5 \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 qz\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -1459,7 +1468,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Rx(-90°) * Rz(q0) * tz(0.412) * Rx(90°) * Rz(q1) * tz(0.154) * tx(0.0203) * tz(-1.5707963267948966) * tz(q2) * Rx(-90°) * Rz(q3) * Rx(90°) * Rz(q4) * Rz(q5)\n" + "Rx(-90\u00b0) * Rz(q0) * tz(0.412) * Rx(90\u00b0) * Rz(q1) * tz(0.154) * tx(0.0203) * tz(-1.5707963267948966) * tz(q2) * Rx(-90\u00b0) * Rz(q3) * Rx(90\u00b0) * Rz(q4) * Rz(q5)\n" ] } ], diff --git a/docs/notebooks/figs/RobToolBox_RoundLogoB.png b/docs/notebooks/figs/RobToolBox_RoundLogoB.png new file mode 100644 index 000000000..e122f0846 Binary files /dev/null and b/docs/notebooks/figs/RobToolBox_RoundLogoB.png differ diff --git a/docs/notebooks/graphics-test.ipynb b/docs/notebooks/graphics-test.ipynb index 81e29183e..0fc379c88 100644 --- a/docs/notebooks/graphics-test.ipynb +++ b/docs/notebooks/graphics-test.ipynb @@ -28,6 +28,15 @@ } ], "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", "from roboticstoolbox import *\n", "\n", "%matplotlib inline\n", diff --git a/docs/notebooks/icra2021.ipynb b/docs/notebooks/icra2021.ipynb index d76514481..54c67a116 100644 --- a/docs/notebooks/icra2021.ipynb +++ b/docs/notebooks/icra2021.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Not your grandmother’s toolbox– the Robotics Toolbox reinvented for python\n", + "# Not your grandmother\u2019s toolbox\u2013 the Robotics Toolbox reinvented for python\n", "### Peter Corke and Jesse Haviland\n", "\n", "This is the code for the examples in the paper published at ICRA2021.\n" @@ -34,6 +34,15 @@ } ], "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", "from math import pi\n", "import numpy as np\n", "\n", @@ -180,7 +189,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -1220,13 +1229,13 @@ "data": { "text/plain": [ "DHRobot: my IRB140, 3 joints (RRR), dynamics, standard DH parameters\n", - "┏━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━┓\n", - "┃θⱼ ┃ dⱼ ┃ aⱼ ┃ ⍺ⱼ ┃\n", - "┣━━━━╋━━━━━━━╋━━━━━━╋━━━━━━━━┫\n", - "┃ q1\u001b[0m ┃ 0.352\u001b[0m ┃ 0.07\u001b[0m ┃ -90.0°\u001b[0m ┃\n", - "┃ q2\u001b[0m ┃ 0\u001b[0m ┃ 0.36\u001b[0m ┃ 0.0°\u001b[0m ┃\n", - "┃ q3\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃\n", - "┗━━━━┻━━━━━━━┻━━━━━━┻━━━━━━━━┛" + "\u250f\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n", + "\u2503\u03b8\u2c7c \u2503 d\u2c7c \u2503 a\u2c7c \u2503 \u237a\u2c7c \u2503\n", + "\u2523\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n", + "\u2503 q1\u001b[0m \u2503 0.352\u001b[0m \u2503 0.07\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503\n", + "\u2503 q2\u001b[0m \u2503 0\u001b[0m \u2503 0.36\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503\n", + "\u2503 q3\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503\n", + "\u2517\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b" ] }, "execution_count": 10, @@ -1254,25 +1263,25 @@ "data": { "text/plain": [ "DHRobot: Puma 560 (by Unimation), 6 joints (RRRRRR), dynamics, geometry, standard DH parameters\n", - "┏━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓\n", - "┃θⱼ ┃ dⱼ ┃ aⱼ ┃ ⍺ⱼ ┃ q⁻ ┃ q⁺ ┃\n", - "┣━━━━╋━━━━━━━━╋━━━━━━━━╋━━━━━━━━╋━━━━━━━━━╋━━━━━━━━┫\n", - "┃ q1\u001b[0m ┃ 0.6718\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃ -160.0°\u001b[0m ┃ 160.0°\u001b[0m ┃\n", - "┃ q2\u001b[0m ┃ 0\u001b[0m ┃ 0.4318\u001b[0m ┃ 0.0°\u001b[0m ┃ -110.0°\u001b[0m ┃ 110.0°\u001b[0m ┃\n", - "┃ q3\u001b[0m ┃ 0.15\u001b[0m ┃ 0.0203\u001b[0m ┃ -90.0°\u001b[0m ┃ -135.0°\u001b[0m ┃ 135.0°\u001b[0m ┃\n", - "┃ q4\u001b[0m ┃ 0.4318\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃ -266.0°\u001b[0m ┃ 266.0°\u001b[0m ┃\n", - "┃ q5\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ -90.0°\u001b[0m ┃ -100.0°\u001b[0m ┃ 100.0°\u001b[0m ┃\n", - "┃ q6\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ 0.0°\u001b[0m ┃ -266.0°\u001b[0m ┃ 266.0°\u001b[0m ┃\n", - "┗━━━━┻━━━━━━━━┻━━━━━━━━┻━━━━━━━━┻━━━━━━━━━┻━━━━━━━━┛\n", - "\n", - "┌─────┬─────┬──────┬───────┬─────┬──────┬─────┐\n", - "│name │ q0 │ q1 │ q2 │ q3 │ q4 │ q5 │\n", - "├─────┼─────┼──────┼───────┼─────┼──────┼─────┤\n", - "│ qz\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qr\u001b[0m │ 0°\u001b[0m │ 90°\u001b[0m │ -90°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qs\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ -90°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qn\u001b[0m │ 0°\u001b[0m │ 45°\u001b[0m │ 180°\u001b[0m │ 0°\u001b[0m │ 45°\u001b[0m │ 0°\u001b[0m │\n", - "└─────┴─────┴──────┴───────┴─────┴──────┴─────┘" + "\u250f\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n", + "\u2503\u03b8\u2c7c \u2503 d\u2c7c \u2503 a\u2c7c \u2503 \u237a\u2c7c \u2503 q\u207b \u2503 q\u207a \u2503\n", + "\u2523\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n", + "\u2503 q1\u001b[0m \u2503 0.6718\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -160.0\u00b0\u001b[0m \u2503 160.0\u00b0\u001b[0m \u2503\n", + "\u2503 q2\u001b[0m \u2503 0\u001b[0m \u2503 0.4318\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 -110.0\u00b0\u001b[0m \u2503 110.0\u00b0\u001b[0m \u2503\n", + "\u2503 q3\u001b[0m \u2503 0.15\u001b[0m \u2503 0.0203\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -135.0\u00b0\u001b[0m \u2503 135.0\u00b0\u001b[0m \u2503\n", + "\u2503 q4\u001b[0m \u2503 0.4318\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -266.0\u00b0\u001b[0m \u2503 266.0\u00b0\u001b[0m \u2503\n", + "\u2503 q5\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -100.0\u00b0\u001b[0m \u2503 100.0\u00b0\u001b[0m \u2503\n", + "\u2503 q6\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 -266.0\u00b0\u001b[0m \u2503 266.0\u00b0\u001b[0m \u2503\n", + "\u2517\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n", + "\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502name \u2502 q0 \u2502 q1 \u2502 q2 \u2502 q3 \u2502 q4 \u2502 q5 \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 qz\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qr\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 90\u00b0\u001b[0m \u2502 -90\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qs\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 -90\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qn\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502 180\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2518" ] }, "execution_count": 11, @@ -1347,7 +1356,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -2359,7 +2368,7 @@ { "data": { "text/plain": [ - "tz(0.672) ⊕ Rz(q0) ⊕ ty(-0.2337) ⊕ Ry(q1) ⊕ tz(0.4318) ⊕ tx(0.0203) ⊕ ty(0.0837) ⊕ Ry(q2) ⊕ tz(0.4318) ⊕ Rz(q3) ⊕ Ry(q4) ⊕ Rz(q5)" + "tz(0.672) \u2295 Rz(q0) \u2295 ty(-0.2337) \u2295 Ry(q1) \u2295 tz(0.4318) \u2295 tx(0.0203) \u2295 ty(0.0837) \u2295 Ry(q2) \u2295 tz(0.4318) \u2295 Rz(q3) \u2295 Ry(q4) \u2295 Rz(q5)" ] }, "execution_count": 18, @@ -2394,16 +2403,16 @@ "output_type": "stream", "text": [ "ERobot: noname, 6 joints (RRRRRR)\n", - "┌───┬────────┬───────┬────────┬───────────────────────────────────────────────────────────────────┐\n", - "│id │ link │ joint │ parent │ ETS │\n", - "├───┼────────┼───────┼────────┼───────────────────────────────────────────────────────────────────┤\n", - "│ 1\u001b[0m │ link0\u001b[0m │ 0\u001b[0m │ BASE\u001b[0m │ {link0} = {BASE} ⊕ tz(0.672) ⊕ Rz(q0)\u001b[0m │\n", - "│ 2\u001b[0m │ link1\u001b[0m │ 1\u001b[0m │ link0\u001b[0m │ {link1} = {link0} ⊕ ty(-0.2337) ⊕ Ry(q1)\u001b[0m │\n", - "│ 3\u001b[0m │ link2\u001b[0m │ 2\u001b[0m │ link1\u001b[0m │ {link2} = {link1} ⊕ tz(0.4318) ⊕ tx(0.0203) ⊕ ty(0.0837) ⊕ Ry(q2)\u001b[0m │\n", - "│ 4\u001b[0m │ link3\u001b[0m │ 3\u001b[0m │ link2\u001b[0m │ {link3} = {link2} ⊕ tz(0.4318) ⊕ Rz(q3)\u001b[0m │\n", - "│ 5\u001b[0m │ link4\u001b[0m │ 4\u001b[0m │ link3\u001b[0m │ {link4} = {link3} ⊕ Ry(q4)\u001b[0m │\n", - "│ 6\u001b[0m │ @link5\u001b[0m │ 5\u001b[0m │ link4\u001b[0m │ {link5} = {link4} ⊕ Rz(q5)\u001b[0m │\n", - "└───┴────────┴───────┴────────┴───────────────────────────────────────────────────────────────────┘\n", + "\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502id \u2502 link \u2502 joint \u2502 parent \u2502 ETS \u2502\n", + "\u251c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 1\u001b[0m \u2502 link0\u001b[0m \u2502 0\u001b[0m \u2502 BASE\u001b[0m \u2502 {link0} = {BASE} \u2295 tz(0.672) \u2295 Rz(q0)\u001b[0m \u2502\n", + "\u2502 2\u001b[0m \u2502 link1\u001b[0m \u2502 1\u001b[0m \u2502 link0\u001b[0m \u2502 {link1} = {link0} \u2295 ty(-0.2337) \u2295 Ry(q1)\u001b[0m \u2502\n", + "\u2502 3\u001b[0m \u2502 link2\u001b[0m \u2502 2\u001b[0m \u2502 link1\u001b[0m \u2502 {link2} = {link1} \u2295 tz(0.4318) \u2295 tx(0.0203) \u2295 ty(0.0837) \u2295 Ry(q2)\u001b[0m \u2502\n", + "\u2502 4\u001b[0m \u2502 link3\u001b[0m \u2502 3\u001b[0m \u2502 link2\u001b[0m \u2502 {link3} = {link2} \u2295 tz(0.4318) \u2295 Rz(q3)\u001b[0m \u2502\n", + "\u2502 5\u001b[0m \u2502 link4\u001b[0m \u2502 4\u001b[0m \u2502 link3\u001b[0m \u2502 {link4} = {link3} \u2295 Ry(q4)\u001b[0m \u2502\n", + "\u2502 6\u001b[0m \u2502 @link5\u001b[0m \u2502 5\u001b[0m \u2502 link4\u001b[0m \u2502 {link5} = {link4} \u2295 Rz(q5)\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -2425,26 +2434,26 @@ "output_type": "stream", "text": [ "ERobot: panda (by Franka Emika), 7 joints (RRRRRRR), geometry, collision\n", - "┌───┬──────────────┬───────┬─────────────┬─────────────────────────────────────────────────────────────────────────────┐\n", - "│id │ link │ joint │ parent │ ETS │\n", - "├───┼──────────────┼───────┼─────────────┼─────────────────────────────────────────────────────────────────────────────┤\n", - "│ 1\u001b[0m │ \u001b[38;5;4mpanda_link0\u001b[0m │ \u001b[0m │ BASE\u001b[0m │ {panda_link0} = {BASE}\u001b[0m │\n", - "│ 2\u001b[0m │ panda_link1\u001b[0m │ 0\u001b[0m │ panda_link0\u001b[0m │ {panda_link1} = {panda_link0} ⊕ tz(0.333) ⊕ Rz(q0)\u001b[0m │\n", - "│ 3\u001b[0m │ panda_link2\u001b[0m │ 1\u001b[0m │ panda_link1\u001b[0m │ {panda_link2} = {panda_link1} ⊕ Rx(-90°) ⊕ Rz(q1)\u001b[0m │\n", - "│ 4\u001b[0m │ panda_link3\u001b[0m │ 2\u001b[0m │ panda_link2\u001b[0m │ {panda_link3} = {panda_link2} ⊕ ty(-0.316) ⊕ Rx(90°) ⊕ Rz(q2)\u001b[0m │\n", - "│ 5\u001b[0m │ panda_link4\u001b[0m │ 3\u001b[0m │ panda_link3\u001b[0m │ {panda_link4} = {panda_link3} ⊕ tx(0.0825) ⊕ Rx(90°) ⊕ Rz(q3)\u001b[0m │\n", - "│ 6\u001b[0m │ panda_link5\u001b[0m │ 4\u001b[0m │ panda_link4\u001b[0m │ {panda_link5} = {panda_link4} ⊕ tx(-0.0825) ⊕ ty(0.384) ⊕ Rx(-90°) ⊕ Rz(q4)\u001b[0m │\n", - "│ 7\u001b[0m │ panda_link6\u001b[0m │ 5\u001b[0m │ panda_link5\u001b[0m │ {panda_link6} = {panda_link5} ⊕ Rx(90°) ⊕ Rz(q5)\u001b[0m │\n", - "│ 8\u001b[0m │ panda_link7\u001b[0m │ 6\u001b[0m │ panda_link6\u001b[0m │ {panda_link7} = {panda_link6} ⊕ tx(0.088) ⊕ Rx(90°) ⊕ Rz(q6)\u001b[0m │\n", - "│ 9\u001b[0m │ \u001b[38;5;4m@panda_link8\u001b[0m │ \u001b[0m │ panda_link7\u001b[0m │ {panda_link8} = {panda_link7} ⊕ tz(0.107)\u001b[0m │\n", - "└───┴──────────────┴───────┴─────────────┴─────────────────────────────────────────────────────────────────────────────┘\n", + "\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502id \u2502 link \u2502 joint \u2502 parent \u2502 ETS \u2502\n", + "\u251c\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 1\u001b[0m \u2502 \u001b[38;5;4mpanda_link0\u001b[0m \u2502 \u001b[0m \u2502 BASE\u001b[0m \u2502 {panda_link0} = {BASE}\u001b[0m \u2502\n", + "\u2502 2\u001b[0m \u2502 panda_link1\u001b[0m \u2502 0\u001b[0m \u2502 panda_link0\u001b[0m \u2502 {panda_link1} = {panda_link0} \u2295 tz(0.333) \u2295 Rz(q0)\u001b[0m \u2502\n", + "\u2502 3\u001b[0m \u2502 panda_link2\u001b[0m \u2502 1\u001b[0m \u2502 panda_link1\u001b[0m \u2502 {panda_link2} = {panda_link1} \u2295 Rx(-90\u00b0) \u2295 Rz(q1)\u001b[0m \u2502\n", + "\u2502 4\u001b[0m \u2502 panda_link3\u001b[0m \u2502 2\u001b[0m \u2502 panda_link2\u001b[0m \u2502 {panda_link3} = {panda_link2} \u2295 ty(-0.316) \u2295 Rx(90\u00b0) \u2295 Rz(q2)\u001b[0m \u2502\n", + "\u2502 5\u001b[0m \u2502 panda_link4\u001b[0m \u2502 3\u001b[0m \u2502 panda_link3\u001b[0m \u2502 {panda_link4} = {panda_link3} \u2295 tx(0.0825) \u2295 Rx(90\u00b0) \u2295 Rz(q3)\u001b[0m \u2502\n", + "\u2502 6\u001b[0m \u2502 panda_link5\u001b[0m \u2502 4\u001b[0m \u2502 panda_link4\u001b[0m \u2502 {panda_link5} = {panda_link4} \u2295 tx(-0.0825) \u2295 ty(0.384) \u2295 Rx(-90\u00b0) \u2295 Rz(q4)\u001b[0m \u2502\n", + "\u2502 7\u001b[0m \u2502 panda_link6\u001b[0m \u2502 5\u001b[0m \u2502 panda_link5\u001b[0m \u2502 {panda_link6} = {panda_link5} \u2295 Rx(90\u00b0) \u2295 Rz(q5)\u001b[0m \u2502\n", + "\u2502 8\u001b[0m \u2502 panda_link7\u001b[0m \u2502 6\u001b[0m \u2502 panda_link6\u001b[0m \u2502 {panda_link7} = {panda_link6} \u2295 tx(0.088) \u2295 Rx(90\u00b0) \u2295 Rz(q6)\u001b[0m \u2502\n", + "\u2502 9\u001b[0m \u2502 \u001b[38;5;4m@panda_link8\u001b[0m \u2502 \u001b[0m \u2502 panda_link7\u001b[0m \u2502 {panda_link8} = {panda_link7} \u2295 tz(0.107)\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n", - "┌─────┬─────┬────────┬─────┬───────┬─────┬───────┬──────┐\n", - "│name │ q0 │ q1 │ q2 │ q3 │ q4 │ q5 │ q6 │\n", - "├─────┼─────┼────────┼─────┼───────┼─────┼───────┼──────┤\n", - "│ qz\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qr\u001b[0m │ 0°\u001b[0m │ -17.2°\u001b[0m │ 0°\u001b[0m │ -126°\u001b[0m │ 0°\u001b[0m │ 115°\u001b[0m │ 45°\u001b[0m │\n", - "└─────┴─────┴────────┴─────┴───────┴─────┴───────┴──────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502name \u2502 q0 \u2502 q1 \u2502 q2 \u2502 q3 \u2502 q4 \u2502 q5 \u2502 q6 \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 qz\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qr\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 -17.2\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 -126\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 115\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -2481,7 +2490,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -3556,9 +3565,9 @@ { "data": { "text/plain": [ - "array([[cos(ψ)*cos(ϴ), sin(φ)*sin(ϴ)*cos(ψ) - sin(ψ)*cos(φ), sin(φ)*sin(ψ) + sin(ϴ)*cos(φ)*cos(ψ)],\n", - " [sin(ψ)*cos(ϴ), sin(φ)*sin(ψ)*sin(ϴ) + cos(φ)*cos(ψ), -sin(φ)*cos(ψ) + sin(ψ)*sin(ϴ)*cos(φ)],\n", - " [-sin(ϴ), sin(φ)*cos(ϴ), cos(φ)*cos(ϴ)]], dtype=object)" + "array([[cos(\u03c8)*cos(\u03f4), sin(\u03c6)*sin(\u03f4)*cos(\u03c8) - sin(\u03c8)*cos(\u03c6), sin(\u03c6)*sin(\u03c8) + sin(\u03f4)*cos(\u03c6)*cos(\u03c8)],\n", + " [sin(\u03c8)*cos(\u03f4), sin(\u03c6)*sin(\u03c8)*sin(\u03f4) + cos(\u03c6)*cos(\u03c8), -sin(\u03c6)*cos(\u03c8) + sin(\u03c8)*sin(\u03f4)*cos(\u03c6)],\n", + " [-sin(\u03f4), sin(\u03c6)*cos(\u03f4), cos(\u03c6)*cos(\u03f4)]], dtype=object)" ] }, "execution_count": 30, @@ -3569,7 +3578,7 @@ "source": [ "import spatialmath.base.symbolic as sym\n", "\n", - "phi, theta, psi = sym.symbol(\"φ, ϴ, ψ\")\n", + "phi, theta, psi = sym.symbol(\"\u03c6, \u03f4, \u03c8\")\n", "rpy2r(phi, theta, psi)" ] }, diff --git a/docs/notebooks/ik_benchmark.ipynb b/docs/notebooks/ik_benchmark.ipynb index fd239657e..65379fc78 100644 --- a/docs/notebooks/ik_benchmark.ipynb +++ b/docs/notebooks/ik_benchmark.ipynb @@ -14,44 +14,17 @@ " %pip install qpsolvers[quadprog]\n", " COLAB = True\n", "except:\n", - " pass" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Numerical Inverse Kinematics Benchmark\n", - "\n", - "
\n", - "\n", - "In this Notebook we benchmark the performance of each numerical IK solver we provide with the toolbox.\n", - "\n", - "There are several ways to use IK solvers with the Toolbox:\n", - "\n", - "* The **fast** methods which are implemented in C++ and wrapped in Python. These methods are available from ``robot`` and ``ets`` classes and start with ``ik_``. For example ``ik_LM`` is a fast C++ based solver.\n", + " pass\n", "\n", - "* The **slow** methods which are implemented in Python. These methods are available from ``robot`` and ``ets`` classes and start with ``ikine_``. For example ``ikine_LM`` is a slow Python based solver.\n", - "\n", - "* The **slow** class-based operation which is implemented in Python. These classes inherit from the ``IKSolver`` class and start with ``IK_``. For example ``IK_LM`` is a class which provides a slow Python based solver.\n", - "\n", - "### Contents\n", - "\n", - "[1.0 Fast IK Methods](#1)\n", + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", "\n", - "[2.0 Python IK Methods](#2)\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", "\n", - "[3.0 Python Class Based IK Methods](#3)\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ "# We will do the imports required for this notebook here\n", "\n", "# numpy provides import array and linear algebra utilities\n", @@ -81,6 +54,35 @@ "warnings.filterwarnings(\"ignore\")" ] }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Numerical Inverse Kinematics Benchmark\n", + "\n", + "
\n", + "\n", + "In this Notebook we benchmark the performance of each numerical IK solver we provide with the toolbox.\n", + "\n", + "There are several ways to use IK solvers with the Toolbox:\n", + "\n", + "* The **fast** methods which are implemented in C++ and wrapped in Python. These methods are available from ``robot`` and ``ets`` classes and start with ``ik_``. For example ``ik_LM`` is a fast C++ based solver.\n", + "\n", + "* The **slow** methods which are implemented in Python. These methods are available from ``robot`` and ``ets`` classes and start with ``ikine_``. For example ``ikine_LM`` is a slow Python based solver.\n", + "\n", + "* The **slow** class-based operation which is implemented in Python. These classes inherit from the ``IKSolver`` class and start with ``IK_``. For example ``IK_LM`` is a class which provides a slow Python based solver.\n", + "\n", + "### Contents\n", + "\n", + "[1.0 Fast IK Methods](#1)\n", + "\n", + "[2.0 Python IK Methods](#2)\n", + "\n", + "[3.0 Python Class Based IK Methods](#3)\n", + "\n" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -224,15 +226,15 @@ "\n", "Numerical Inverse Kinematics Methods Times Compared over 10000 problems\n", "\n", - "┌───────────────────────────┬────────────────┬────────────────────────────────┐\n", - "│ Method │ Total Time (s) │ Average Time per Solution (μs) │\n", - "├───────────────────────────┼────────────────┼────────────────────────────────┤\n", - "│Newton Raphson (pinv=True)\u001b[0m │ 1.8708\u001b[0m │ 187.0795\u001b[0m │\n", - "│ Gauss Newton (pinv=True)\u001b[0m │ 2.474\u001b[0m │ 247.3969\u001b[0m │\n", - "│ LM Wampler 1e-4\u001b[0m │ 0.6079\u001b[0m │ 60.7928\u001b[0m │\n", - "│ LM Chan 0.1\u001b[0m │ 0.2724\u001b[0m │ 27.2353\u001b[0m │\n", - "│ LM Sugihara 0.0001\u001b[0m │ 0.3546\u001b[0m │ 35.4575\u001b[0m │\n", - "└───────────────────────────┴────────────────┴────────────────────────────────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502 Method \u2502 Total Time (s) \u2502 Average Time per Solution (\u03bcs) \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502Newton Raphson (pinv=True)\u001b[0m \u2502 1.8708\u001b[0m \u2502 187.0795\u001b[0m \u2502\n", + "\u2502 Gauss Newton (pinv=True)\u001b[0m \u2502 2.474\u001b[0m \u2502 247.3969\u001b[0m \u2502\n", + "\u2502 LM Wampler 1e-4\u001b[0m \u2502 0.6079\u001b[0m \u2502 60.7928\u001b[0m \u2502\n", + "\u2502 LM Chan 0.1\u001b[0m \u2502 0.2724\u001b[0m \u2502 27.2353\u001b[0m \u2502\n", + "\u2502 LM Sugihara 0.0001\u001b[0m \u2502 0.3546\u001b[0m \u2502 35.4575\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -257,7 +259,7 @@ "table = ANSITable(\n", " \"Method\",\n", " \"Total Time (s)\",\n", - " \"Average Time per Solution (μs)\",\n", + " \"Average Time per Solution (\u03bcs)\",\n", " border=\"thin\",\n", ")\n", "\n", diff --git a/docs/notebooks/index.ipynb b/docs/notebooks/index.ipynb new file mode 100644 index 000000000..e0fca379b --- /dev/null +++ b/docs/notebooks/index.ipynb @@ -0,0 +1,38 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
Robotics Toolbox for Python
\n", + "\n", + "

\n", + "
Try the Robotics Toolbox for Python in your browser
\n", + "

\n", + "\n", + "This is a JupyterLite deployment -- everything runs in your browser via Pyodide, no installation required.\n", + "\n", + "Notebooks:\n", + "\n", + "- [Robotics Toolbox tour](robotics.ipynb)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/notebooks/iros2020-workshop.ipynb b/docs/notebooks/iros2020-workshop.ipynb index 2f241585b..99629d52b 100644 --- a/docs/notebooks/iros2020-workshop.ipynb +++ b/docs/notebooks/iros2020-workshop.ipynb @@ -22,6 +22,15 @@ "metadata": {}, "outputs": [], "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", "import numpy as np\n", "\n", "import roboticstoolbox as rtb\n", @@ -70,23 +79,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "┏━━━┳━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓\n", - "┃θⱼ ┃ dⱼ ┃ aⱼ ┃ ⍺ⱼ ┃\n", - "┣━━━╋━━━━━━━━━╋━━━━━━━━╋━━━━━━━━┫\n", - "┃q1 ┃ 0.672 ┃ 0 ┃ 90.0° ┃\n", - "┃q2 ┃ 0 ┃ 0.4318 ┃ 0.0° ┃\n", - "┃q3 ┃ 0.15005 ┃ 0.0203 ┃ -90.0° ┃\n", - "┃q4 ┃ 0.4318 ┃ 0 ┃ 90.0° ┃\n", - "┃q5 ┃ 0 ┃ 0 ┃ -90.0° ┃\n", - "┃q6 ┃ 0 ┃ 0 ┃ 0.0° ┃\n", - "┗━━━┻━━━━━━━━━┻━━━━━━━━┻━━━━━━━━┛\n", + "\u250f\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n", + "\u2503\u03b8\u2c7c \u2503 d\u2c7c \u2503 a\u2c7c \u2503 \u237a\u2c7c \u2503\n", + "\u2523\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n", + "\u2503q1 \u2503 0.672 \u2503 0 \u2503 90.0\u00b0 \u2503\n", + "\u2503q2 \u2503 0 \u2503 0.4318 \u2503 0.0\u00b0 \u2503\n", + "\u2503q3 \u2503 0.15005 \u2503 0.0203 \u2503 -90.0\u00b0 \u2503\n", + "\u2503q4 \u2503 0.4318 \u2503 0 \u2503 90.0\u00b0 \u2503\n", + "\u2503q5 \u2503 0 \u2503 0 \u2503 -90.0\u00b0 \u2503\n", + "\u2503q6 \u2503 0 \u2503 0 \u2503 0.0\u00b0 \u2503\n", + "\u2517\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n", "\n", - "┌───┬────────────────────────────┐\n", - "│qz │ 0°, 0°, 0°, 0°, 0°, 0° │\n", - "│qr │ 0°, 90°, -90°, 0°, 0°, 0° │\n", - "│qs │ 0°, 0°, -90°, 0°, 0°, 0° │\n", - "│qn │ 0°, 45°, 180°, 0°, 45°, 0° │\n", - "└───┴────────────────────────────┘\n", + "\u250c\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502qz \u2502 0\u00b0, 0\u00b0, 0\u00b0, 0\u00b0, 0\u00b0, 0\u00b0 \u2502\n", + "\u2502qr \u2502 0\u00b0, 90\u00b0, -90\u00b0, 0\u00b0, 0\u00b0, 0\u00b0 \u2502\n", + "\u2502qs \u2502 0\u00b0, 0\u00b0, -90\u00b0, 0\u00b0, 0\u00b0, 0\u00b0 \u2502\n", + "\u2502qn \u2502 0\u00b0, 45\u00b0, 180\u00b0, 0\u00b0, 45\u00b0, 0\u00b0 \u2502\n", + "\u2514\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -122,7 +131,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", diff --git a/docs/notebooks/kinematics.ipynb b/docs/notebooks/kinematics.ipynb index 779c7cdf4..b37706d13 100644 --- a/docs/notebooks/kinematics.ipynb +++ b/docs/notebooks/kinematics.ipynb @@ -6,6 +6,15 @@ "metadata": {}, "outputs": [], "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", "import numpy as np\n", "import roboticstoolbox as rtb\n", "from spatialmath import *\n", @@ -54,25 +63,25 @@ "output_type": "stream", "text": [ "DHRobot: Puma 560 (by Unimation), 6 joints (RRRRRR), dynamics, geometry, standard DH parameters\n", - "┏━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━┳━━━━━━━━┓\n", - "┃θⱼ ┃ dⱼ ┃ aⱼ ┃ ⍺ⱼ ┃ q⁻ ┃ q⁺ ┃\n", - "┣━━━━╋━━━━━━━━╋━━━━━━━━╋━━━━━━━━╋━━━━━━━━━╋━━━━━━━━┫\n", - "┃ q1\u001b[0m ┃ 0.6718\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃ -160.0°\u001b[0m ┃ 160.0°\u001b[0m ┃\n", - "┃ q2\u001b[0m ┃ 0\u001b[0m ┃ 0.4318\u001b[0m ┃ 0.0°\u001b[0m ┃ -110.0°\u001b[0m ┃ 110.0°\u001b[0m ┃\n", - "┃ q3\u001b[0m ┃ 0.15\u001b[0m ┃ 0.0203\u001b[0m ┃ -90.0°\u001b[0m ┃ -135.0°\u001b[0m ┃ 135.0°\u001b[0m ┃\n", - "┃ q4\u001b[0m ┃ 0.4318\u001b[0m ┃ 0\u001b[0m ┃ 90.0°\u001b[0m ┃ -266.0°\u001b[0m ┃ 266.0°\u001b[0m ┃\n", - "┃ q5\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ -90.0°\u001b[0m ┃ -100.0°\u001b[0m ┃ 100.0°\u001b[0m ┃\n", - "┃ q6\u001b[0m ┃ 0\u001b[0m ┃ 0\u001b[0m ┃ 0.0°\u001b[0m ┃ -266.0°\u001b[0m ┃ 266.0°\u001b[0m ┃\n", - "┗━━━━┻━━━━━━━━┻━━━━━━━━┻━━━━━━━━┻━━━━━━━━━┻━━━━━━━━┛\n", + "\u250f\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n", + "\u2503\u03b8\u2c7c \u2503 d\u2c7c \u2503 a\u2c7c \u2503 \u237a\u2c7c \u2503 q\u207b \u2503 q\u207a \u2503\n", + "\u2523\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u254b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252b\n", + "\u2503 q1\u001b[0m \u2503 0.6718\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -160.0\u00b0\u001b[0m \u2503 160.0\u00b0\u001b[0m \u2503\n", + "\u2503 q2\u001b[0m \u2503 0\u001b[0m \u2503 0.4318\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 -110.0\u00b0\u001b[0m \u2503 110.0\u00b0\u001b[0m \u2503\n", + "\u2503 q3\u001b[0m \u2503 0.15\u001b[0m \u2503 0.0203\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -135.0\u00b0\u001b[0m \u2503 135.0\u00b0\u001b[0m \u2503\n", + "\u2503 q4\u001b[0m \u2503 0.4318\u001b[0m \u2503 0\u001b[0m \u2503 90.0\u00b0\u001b[0m \u2503 -266.0\u00b0\u001b[0m \u2503 266.0\u00b0\u001b[0m \u2503\n", + "\u2503 q5\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 -90.0\u00b0\u001b[0m \u2503 -100.0\u00b0\u001b[0m \u2503 100.0\u00b0\u001b[0m \u2503\n", + "\u2503 q6\u001b[0m \u2503 0\u001b[0m \u2503 0\u001b[0m \u2503 0.0\u00b0\u001b[0m \u2503 -266.0\u00b0\u001b[0m \u2503 266.0\u00b0\u001b[0m \u2503\n", + "\u2517\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u253b\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251b\n", "\n", - "┌─────┬─────┬──────┬───────┬─────┬──────┬─────┐\n", - "│name │ q0 │ q1 │ q2 │ q3 │ q4 │ q5 │\n", - "├─────┼─────┼──────┼───────┼─────┼──────┼─────┤\n", - "│ qz\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qr\u001b[0m │ 0°\u001b[0m │ 90°\u001b[0m │ -90°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qs\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ -90°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │ 0°\u001b[0m │\n", - "│ qn\u001b[0m │ 0°\u001b[0m │ 45°\u001b[0m │ 180°\u001b[0m │ 0°\u001b[0m │ 45°\u001b[0m │ 0°\u001b[0m │\n", - "└─────┴─────┴──────┴───────┴─────┴──────┴─────┘\n", + "\u250c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2510\n", + "\u2502name \u2502 q0 \u2502 q1 \u2502 q2 \u2502 q3 \u2502 q4 \u2502 q5 \u2502\n", + "\u251c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2524\n", + "\u2502 qz\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qr\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 90\u00b0\u001b[0m \u2502 -90\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qs\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 -90\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2502 qn\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502 180\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502 45\u00b0\u001b[0m \u2502 0\u00b0\u001b[0m \u2502\n", + "\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2518\n", "\n" ] } @@ -85,7 +94,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The first table shows the kinematic parameters, and from the column titles we can see clearly that this is expressed in terms of standard Denavit-Hartenberg parameters. The first column shows that the joint variables qi are rotations since they are in the θ column. Joint limits are also shown. Joint flip (motion in the opposite sense) would be indicated by the joint variable being shown as for example like `-q3`, and joint offsets by being shown as for example like `q2 + 45°`.\n", + "The first table shows the kinematic parameters, and from the column titles we can see clearly that this is expressed in terms of standard Denavit-Hartenberg parameters. The first column shows that the joint variables qi are rotations since they are in the \u03b8 column. Joint limits are also shown. Joint flip (motion in the opposite sense) would be indicated by the joint variable being shown as for example like `-q3`, and joint offsets by being shown as for example like `q2 + 45\u00b0`.\n", "\n", "The second table shows some named joint configurations. For example `p560.qr` is " ] @@ -149,7 +158,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -1431,7 +1440,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -2391,7 +2400,7 @@ " } else {\n", " alert(\n", " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Please try Chrome, Safari or Firefox \u2265 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.'\n", " );\n", @@ -3342,7 +3351,7 @@ " - have it's arm on the left or right of its waist axis, \n", " - the elbow could be up or down, and\n", " - the wrist can flipped or not flipped. For a two-finger gripper a rotation of \n", - " 180° about the gripper axis leaves the fingers in the same configuration.\n", + " 180\u00b0 about the gripper axis leaves the fingers in the same configuration.\n", "\n", "Most industrial robots have a _spherical wrist_ which means that the last three joint axes intersect at a single point in the middle of the wrist mechanism. We can test for this condition" ] diff --git a/docs/notebooks/robotics.ipynb b/docs/notebooks/robotics.ipynb new file mode 100644 index 000000000..7c3e2ef29 --- /dev/null +++ b/docs/notebooks/robotics.ipynb @@ -0,0 +1,1089 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
Robotics Toolbox for Python
\n", + "\n", + "

\n", + "
\ud83e\udd16\ud83d\ude80 Robotics without the cruft
\n", + "

\n", + "\n", + "\n", + "(c) Peter Corke 2026" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23fc395e", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " import piplite\n", + " await piplite.install([\"roboticstoolbox-python\"])\n", + "except ImportError:\n", + " pass # not running in Pyodide (e.g. local Jupyter) -- assume it's already pip-installed\n", + "\n", + "import roboticstoolbox\n", + "print(\"roboticstoolbox version:\", roboticstoolbox.__version__)\n", + "\n", + "# import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "np.set_printoptions(linewidth=100, formatter={'float': lambda x: f\"{x:8.3g}\" if abs(x) > 1e-10 else f\"{0:8.3g}\"})\n", + "\n", + "import math" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "# Mobile robotics" + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "## Mobile robot kinematics" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "We can create a vehicle with bicycle kinematics, with a wheelbase of 2m." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "from roboticstoolbox import Bicycle, VehicleIcon\n", + "\n", + "veh = Bicycle(L=2, animation=VehicleIcon(\"redcar\", scale=2), workspace=10)\n", + "print(veh)" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "We can find the maximum path curvature it can achieve, which is the reciprocal of the minimum turning radius." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "print(veh.curvature_max)" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "The initial state of the vehicle $(x,y,\\theta)$, where $\\theta$ is the heading angle, can be set at contruction time but defaults to zero" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "print(veh.x)" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "The state derivative $(\\dot{x}, \\dot{y}, \\dot{\\theta})$ is a function of current state and the inputs $(v, \\gamma)$ where $\\gamma$ is the angle of the steered wheel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "xd = veh.deriv(u=(1, 0.2), x= [0,0,0])\n", + "print(xd)" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "which indicates motion in the x-direction and a change of heading angle.\n", + "\n", + "The model supports simple animation of motion directed by a `control` function. In this case the control sets a constant velocity and a steered wheel angle of 0.5 rad for $1 """ - # this is the path to the icons within the github repo - path = ( - "https://github.com/petercorke/robotics-toolbox-python/raw/master/roboticstoolbox/blocks/Icons/" - + text.lower() - + ".png" - ) + # icons are copied from src/roboticstoolbox/blocks/Icons into _static/ + # by html_static_path in conf.py, and served from there at build time -- + # avoids depending on a live network fetch (and a hardcoded branch/path) + # for every docs build. The relative prefix accounts for how deeply + # nested the current page is, matching Sphinx's own pathto() semantics + # for _static references, so this keeps working if these pages ever + # move into a subdirectory. + env = inliner.document.settings.env + depth = env.docname.count("/") + prefix = "../" * depth + path = f"{prefix}_static/{text.lower()}.png" html_node = nodes.raw(text=html.format(text, path), format="html") return [html_node], [] diff --git a/docs/source/intro.rst b/docs/source/intro.rst index 9f651fd1e..4ed90ef8c 100644 --- a/docs/source/intro.rst +++ b/docs/source/intro.rst @@ -2,241 +2,20 @@ Introduction ************ +The Robotics Toolbox for Python (RTB-P) provides tools for the kinematics, +dynamics, motion planning and control of both arm-type (serial-link +manipulator) and mobile (wheeled) robots. -Introduction -============ - -This is a modified version of a paper accepted to ICRA2021 [corke21a]_. - -The Robotics Toolbox for MATLAB® (RTB-M) was created around 1991 to support -Peter Corke’s PhD research and was first published in 1995-6 [Corke95]_ -[Corke96]_. It has evolved over 25 years to track changes and improvements to -the MATLAB language and ecosystem, such as the addition of structures, objects, -lists (cell arrays) and strings, myriad of other improvements to the language, -new graphics and new tools such as IDE, debugger, notebooks (LiveScripts), apps -and continuous integration. An adverse consequence is that many poor (in -retrospect) early design decisions hinder development. - -Over time additional functionality was added, in particular for vision, and two -major refactorings led to the current state of three toolboxes: Robotics Toolbox -for MATLAB and Machine Vision Toolbox for MATLAB (1999) both of which are built -on the Spatial Math Toolbox for MATLAB (SMTB-M) in 2019. - -The code was formally open sourced to support its use for the third edition of -John Craig’s book [Craig2005]_. It was hosted on ftp sites, personal web -servers, Google code and currently GitHub and maintained under a succession of -version control tools including rcs, cvs, svn and git. A support forum on -Google Groups was established in 2008 and as of 2020 has over 1400 members. - - -A Python version -================ - -The imperative for a Python version has long existed and the first port was -started in 2008 but ultimately failed for lack of ongoing resources to complete -a sufficient subset of functionality. Subsequent attempts have all met the same -fate. - -The design goals of this version can be summarised as new functionality: - -* A superset of the MATLAB Toolbox functionality -* Build on the Spatial Math Toolbox for Python [SMTB-P]_ which provides objects to - represent rotations as SO(2) and SE(3) matrices as well as unit-quaternions; - rigid-body motions as SE(2) and SE(3) matrices or twists in - se(2) and se(3); and Featherstone’s spatial vectors [Featherstone87]_. -* Support models expressed using Denavit-Hartenberg notation (standard and - modified), elementary transform sequences [Corke07]_ [Haviland20]_, and URDF-style - rigid-body trees. Support branched, but not closed-loop or parallel, robots -* Collision checking - -and improved software engineering: - -* Use Python 3 (3.10 and greater) -* Utilize WebGL and Javascript graphics technologies -* Documentation in ReStructured Text using Sphinx and delivered via GitHub pages. -* Hosted on GitHub with continuous integration using GitHub actions -* High code-quality metrics for test coverage and automated code review and security analysis -* As few dependencies as possible, in particular being able to work with ROS but not be dependent on ROS. This sidesteps ROS constraints on operating system and Python versions. -* Modular approach to interfacing to different graphics libraries, simulators and physical robots. -* Support Python notebooks which allows publication of static notebooks (for example via GitHub) and interactive online notebooks (`MyBinder.org `_). -* Use of UniCode characters to make console output easier to read - - -Spatial math layer -================== - -Robotics and computer vision require us to describe position, orientation and -pose in 3D space. Mobile robotics has the same requirement, but generally for 2D -space. We therefore need tools to represent quantities such as rigid-body -transformations (matrices :math:`\in \SE{n}` or twists :math:`\in \se{n}`), -rotations (matrices :math:`\in \SO{n}` or :math:`\so{n}`, Euler or roll-pitch-yaw -angles, or unit quaternions :math:`\in \mathrm{S}^3`). Such capability is amongst the oldest in -RTB-M and the equivalent functionality exists in RTB-P which makes use of the -Spatial Maths Toolbox for Python (SMTB-P) [SMTB-P]_. For example: - -.. runblock:: pycon - - >>> from spatialmath.base import * - >>> T = transl(0.5, 0.0, 0.0) @ rpy2tr(0.1, 0.2, 0.3, order='xyz') @ trotx(-90, 'deg') - >>> print(T) - -There is strong similarity to the equivalent MATLAB case apart from the use of -the ``@`` operator, the use of keyword arguments instead of keyword-value pairs, -and the format of the printed array. All the *classic* RTB-M functions are -provided in the ``spatialmath.base`` package as well as additional functions for -quaternions, vectors, twists and argument handling. There are also functions to -perform interpolation, plot and animate coordinate frames, and create movies, -using matplotlib. The underlying datatypes in all cases are 1D and 2D NumPy -arrays. - -.. warning:: For a user transitioning from MATLAB the most significant difference is - the use of 1D arrays -- all MATLAB arrays have two dimensions, even if one of - them is equal to one. - -However some challenges arise when using arrays, whether native MATLAB matrices -or NumPy arrays as in this case. Firstly, arrays are not typed and for example a -:math:`3 \times 3` array could be an element of :math:`\SE{2}` or -:math:`\SO{3}` or an arbitrary matrix. - -Secondly, the operators we need for poses are a subset of those available for -matrices, and some operators may need to be redefined in a specific way. For -example, :math:`\SE{3} * \SE{3} \rightarrow \SE{3}` but :math:`\SE{3} + \SE{3} \rightarrow \mathbb{R}^{4 \times 4}`, and equality testing for a -unit-quaternion has to respect the double mapping. - -Thirdly, in robotics we often need to represent time sequences of poses. We -could add an extra dimension to the matrices representing rigid-body -transformations or unit-quaternions, or place them in a list. The first -approach is cumbersome and reduces code clarity, while the second cannot ensure -that all elements of the list have the same type. - -We use classes and data encapsulation to address all these issues. SMTB-P -provides abstraction classes ``SE3``, ``Twist3``, ``SO3``, ``UnitQuaternion``, -``SE2``, ``Twist2`` and ``SO2``. For example, the previous example could be written -as: - -.. runblock:: pycon - :linenos: - - >>> from spatialmath import * - >>> T = SE3(0.5, 0.0, 0.0) * SE3.RPY([0.1, 0.2, 0.3], order='xyz') * SE3.Rx(-90, unit='deg') - >>> print(T) - >>> T.eul() - >>> T.R - >>> T.t - -where composition is denoted by the ``*`` operator and the matrix is printed more elegantly (and elements are color -coded at the console or in ipython). -``SE3.RPY()`` is a class method that acts like a constructor, creating an ``SE3`` instance from a set of roll-pitch-yaw angles, -and ``SE3.Rx()`` creates an ``SE3`` instance from a pure rotation about the x-axis. -Attempts to compose with a non ``SE3`` instance would result in a ``TypeError``. - -The orientation of the new coordinate frame may be expressed in terms of Euler angles (line 9) -and components can be extracted such as the rotation submatrix (line 11) and translation (line 15). - -The pose ``T`` can also be displayed as a 3D coordinate frame:: - - >>> T.plot(color='red', label='2') - - -Rotation can also be represented by a unit quaternion - -.. runblock:: pycon - - >>> from spatialmath import UnitQuaternion - >>> print(UnitQuaternion.Rx(0.3)) - >>> print(UnitQuaternion.AngVec(0.3, [1, 0, 0])) - -which again demonstrates several alternative constructors. - - - -Multiple values -^^^^^^^^^^^^^^^ - -To support sequences of values each of these types inherits list properties from ``collections.UserList`` +Inspired by the original Robotics Toolbox for MATLAB (RTB-M) :cite:`Corke96`, RTB-P :cite:`corke21a` +is a complete rewrite in Python, and is designed to be more modular and extensible. It +is intended to be used in conjunction with the Spatial Math Toolbox for Python (SMTB-P) +:cite:`SMTB-P` which provides the underlying representations of pose and orientation used +throughout. The project's history, and an introduction to the +spatial-math layer, are given at the end of this document. -.. figure:: ../figs/pose-values.png - :width: 600 - :alt: Any of the SMTB-P pose classes can contain a list of values - - Any of the SMTB-P pose classes can contain a list of values - -We can index the values, iterate over the values, assign to values. -Some constructors take an array-like argument allowing creation of multi-valued pose objects, -for example: - -.. runblock:: pycon - - >>> from spatialmath import SE3 - >>> import numpy as np - >>> R = SE3.Rx(np.linspace(0, np.pi/2, num=100)) - >>> len(R) - -where the instance ``R`` contains a sequence of 100 rotation matrices. -Composition with a single-valued (scalar) pose instance broadcasts the scalar -across the sequence - -.. figure:: ../figs/broadcasting.png - :alt: Overloaded operators support broadcasting - - Overloaded operators support broadcasting - -Common constructors -^^^^^^^^^^^^^^^^^^^ - -The Toolboxes classes are somewhat polymorphic and share many "variant constructors" that allow object construction: - -- with orientation expressed in terms of canonic axis rotations, Euler vectors, angle-vector pair, - Euler or roll-pitch-yaw angles or orientation- and approach-vectors. -- from random values ``.Rand()`` -- ``SE3``, ``SE2``, ``SO3`` and ``SO2`` also support a matrix exponential constructor where the argument is the - corresponding Lie algebra element. -- empty, i.e. having no values or a length of 0 ``.Empty()`` -- an array of ``N`` values initialized to the object's identity value ``.Alloc(N)`` - -Common methods and operators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The types all have an inverse method ``.inv()`` and support composition with the inverse using the ``/`` operator -and integer exponentiation (repeated composition) using the ``**`` operator. -Other overloaded operators include ``*``, ``*=``, ``**``, ``**=``, ``/``, ``/=``, ``==``, ``!=``, ``+``, ``-``. - -All of this allows for concise and readable code. -The use of classes ensures type safety and that the matrices abstracted by the class are always valid members of -the group. -Operations such as addition, which are not group operations, yield a NumPy array rather than a class instance. - -Performance -^^^^^^^^^^^ - -These benefits come at a price in terms of execution time due to the overhead of -constructors, methods which wrap base functions, and type checking. The -Toolbox supports SymPy which provides powerful symbolic support for Python and -it works well in conjunction with NumPy, ie. a NumPy array can contain symbolic -elements. Many the Toolbox methods and functions contain extra logic to ensure -that symbolic operations work as expected. While this adds to the overhead it -means that for the user, working with symbols is as easy as working with -numbers. - - -.. table:: Performance on a 3.6GHz Intel Core i9 - - =================== ============== - Function/method Execution time - =================== ============== - ``base.rotx()`` 4.07 μs - ``base.trotx()`` 5.79 μs - ``SE3.Rx()`` 12.3 μs - ``SE3 * SE3`` 4.69 μs - ``4x4 @`` 0.986 μs - ``SE3.inv()`` 7.62 μs - ``base.trinv()`` 4.19 μs - ``np.linalg.inv()`` 4.49 μs - =================== ============== - -Robotics Toolbox -================ +Arm robots +========== Robot models ^^^^^^^^^^^^ @@ -244,15 +23,15 @@ Robot models The Toolbox ships with over 50 robot models, most of which are purely kinematic but some have inertial and frictional parameters. Kinematic models can be specified in a variety of ways: standard or modified Denavit-Hartenberg (DH, -MDH) notation, as an ETS string [Corke07]_, as a rigid-body tree, or from a URDF +MDH) notation, as an ETS string :cite:`Corke07`, as a rigid-body tree, or from a URDF file. Denavit-Hartenberg parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To specify a kinematic model using DH notation, we create a new subclass of ``DHRobot`` and pass the superclass constructor -a list of link objects. For example, a Puma560 is simply:: +To specify a kinematic model using DH notation, we create a new Robot instance and pass +a list of DH link objects. For example, a Puma560 is simply:: >>> robot = DHRobot( [ @@ -263,16 +42,17 @@ a list of link objects. For example, a Puma560 is simply:: RevoluteDH(alpha=-pi/2), RevoluteDH() ], name="Puma560") - + where only the non-zero parameters need to be specified. In this case we used ``RevoluteDH`` objects for a revolute joint described using standard DH -conventions. Other classes available are ``PrismaticDH``, ``RevoluteMDH`` and +conventions. Other Link classes available are ``PrismaticDH``, ``RevoluteMDH`` and ``PrismaticMDH``. Other parameters such as mass, CoG, link inertia, motor inertia, viscous friction, Coulomb friction, and joint limits can also be specified using additional keyword arguments. -The toolbox provides such definitions wrapped as class definitions, for example:: +The toolbox provides some such robot models wrapped as class definitions, for example the +core logic of `models.DH.Puma560` is:: class Puma560(DHRobot): @@ -293,57 +73,66 @@ We can now easily perform standard kinematic operations .. runblock:: pycon :linenos: - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() # instantiate robot model + >>> from roboticstoolbox.models.DH import Puma560 + >>> puma = Puma560() # instantiate robot model >>> print(puma) >>> print(puma.qr) >>> T = puma.fkine([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) # forward kinematics >>> print(T) >>> sol = puma.ikine_LM(T) # inverse kinematics - >>> print(sol) + >>> print(sol.success) + >>> print(sol.q) + -The Toolbox supports named joint configurations and these are shown in the table -at lines 16-22. +The Toolbox supports named joint configurations and these are shown in the table at +lines 16-22. For example, ``puma.qr`` is the upright "ready" configuration, +``puma.qz`` is the zero angle configuration, and ``puma.qn`` is a nominal elbow up table-top working configuration. + +.. runblock:: pycon + + >>> from roboticstoolbox.models.DH import Puma560 + >>> puma = Puma560() # instantiate robot model + >>> puma.qn + +All robots can generate a random joint configuration informed by joint limits, if they exist + +.. runblock:: pycon + + >>> from roboticstoolbox.models.DH import Puma560 + >>> puma = Puma560() # instantiate robot model + >>> puma.random_q() ``ikine_LM`` is a generalised iterative numerical solution based on Levenberg-Marquadt minimization, and additional status results are also -returned as part of a named tuple. +returned as part of a named tuple. + +.. warning:: + - The solution is not unique and the algorithm may converge to different solutions depending on the initial joint configuration. If not specified, the initial joint configuration is random. + - The solution may also fail to converge, `sol.success` is `False`,if the specified transform is out of reach of the manipulator. The default plot method:: >>> puma.plot(q) -uses matplotlib to produce a "noodle robot" plot like +uses Matplotlib to produce a "noodle robot" plot like .. figure:: ../figs/noodle+ellipsoid.png :width: 600 - :alt: Puma560, with a velocity ellipsoid, rendered using the default matplotlib visualizer + :alt: Puma560, with a velocity ellipsoid, rendered using the default Matplotlib visualizer - Puma560, with a velocity ellipsoid, rendered using the default matplotlib visualizer. + Puma560, with a velocity ellipsoid, rendered using the default Matplotlib visualizer. and we can use the mouse to rotate and zoom the plot. -.. note:: The initial joint configuration for the inverse-kinematic solution may be specified, but - defaults to zero, and affects both the search time and the solution found, since in general - a manipulator may have several multiple joint configurations which result in the same end-effector pose. - For a redundant manipulator, a solution will be found but there is no - explicit control over the null-space. For a manipulator with :math:`n < 6` DOF - an additional argument is required to indicate which of the - :math:`6-n` Cartesian DOF are to be unconstrained in the solution. - -.. note:: A solution is not possible if the specified transform describes - a point out of reach of the manipulator -- in such a case the function will - return with an error. - -The inverse kinematic procedure for most robots can +The inverse kinematic procedure for most robots can be derived symbolically and an efficient closed-form solution obtained. Some provided robot models have an analytical solution coded, for example: .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() # instantiate robot model + >>> from roboticstoolbox.models.DH import Puma560 + >>> puma = Puma560() # instantiate robot model >>> T = puma.fkine([0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) >>> puma.ikine_a(T, config="lun") # analytic inverse kinematics @@ -353,71 +142,89 @@ where we have specified a left-handed, elbow up and wrist no-flip configuration. ETS notation ^^^^^^^^^^^^ -A Puma robot can also be specified in ETS format [Corke07]_ as a sequence of simple rigid-body transformations -- pure translation +A Puma robot can also be specified in ETS format :cite:`Corke07` as a sequence of simple rigid-body transformations -- pure translation or pure rotation -- each with either a constant parameter or a free parameter which is a joint variable. .. runblock:: pycon :linenos: - >>> from roboticstoolbox import ET - >>> import roboticstoolbox as rtb - >>> # Puma dimensions (m), see RVC2 Fig. 7.4 for details - >>> l1 = 0.672; l2 = -0.2337; l3 = 0.4318; l4 = 0.0203; l5 = 0.0837; l6 = 0.4318 + >>> from roboticstoolbox import ET, Robot + >>> l1, l2, l3, l4, l5, l6 = 0.672, -0.2337, 0.4318, 0.0203, 0.0837, 0.4318 # Puma dimensions (m), see RVC2 Fig. 7.4 for details >>> e = ET.tz(l1) * ET.Rz() * ET.ty(l2) * ET.Ry() * ET.tz(l3) * ET.tx(l4) * ET.ty(l5) * ET.Ry() * ET.tz(l6) * ET.Rz() * ET.Ry() * ET.Rz() >>> print(e) - >>> robot = rtb.Robot(e) + >>> robot = Robot(e) >>> print(robot) -Line 3 defines the unique lengths of the Puma robot, and line 4 defines the kinematic chain in -terms of elementary transforms. -In line 7 we pass this to the constructor for a ``Robot`` which partitions the +Line 2 defines the unique lengths of the Puma robot, and line 3 defines the kinematic chain in +terms of elementary transforms. In contrast to DH notation, this description allows joint rotations about arbitrary axes and translations along arbitrary axes, and the order of the transforms is explicit. +In line 6 we pass the ETS to the constructor for a ``Robot`` which partitions the elementary transform sequence into a series of links and joints -- link frames are declared after each joint variable as well as the start and end of the sequence. -The ``Robot`` can represent single-branched robots with any combination of revolute and prismatic joints, but -can also represent more general branched mechanisms. +By explicitly creating `ETSLink` objects we can +represent general branched robot structures and also specify inertial and frictional parameters for each link. + +Kinematic and plotting operations are performed using methods with the same names +as discussed above. -Robot: rigid-body tree and URDF import -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +URDF import +^^^^^^^^^^^ -The final approach to manipulator modeling is to an import a URDF file. The Toolbox includes a parser with built-in xacro processor +The final approach to manipulator modeling is to an import a URDF file. The Toolbox includes a parser and xacro preprocessor which makes many models from the ROS universe available. Provided models, such as for Panda or Puma, are again encapsulated as classes: .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> panda = rtb.models.DH.Panda() + >>> from roboticstoolbox.models.URDF import Panda + >>> panda = Panda() >>> print(panda) >>> T = panda.fkine(panda.qz) >>> print(T) -and kinematic operations are performed using methods with the same name +In the table above we see the end-effector indicated by @ (determined automatically +from the URDF file). +Kinematic operations and plotting operations are performed using methods with the same names as discussed above. -For branched robots, with multiple end-effectors, the name of the frame of interest must be provided. -Some URDF models have multiple end-effectors, in which case the particular -end-effector must be specified. +Some URDF models have multiple end-effectors, for example: .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> panda = rtb.models.URDF.Panda() - >>> print(panda) - >>> T = panda.fkine(panda.qz, end='panda_hand') + >>> from roboticstoolbox.models.URDF import YuMi + >>> yumi = YuMi() + >>> print(yumi) + + +and we see two end-effectors indicated by @. For kinematic operations we must specify one of these. + + >>> from roboticstoolbox.models.URDF import YuMi + >>> yumi = YuMi() + >>> T = yumi.fkine(yumi.qz, end='gripper_r_base') >>> print(T) -In the table above we see the end-effectors indicated by @ (determined automatically -from the URDF file), so we specify one of these. We can also specify any +We can also specify any other link in order to determine the pose of that link's coordinate frame. -This URDF model comes with meshes provided as Collada file which provide +.. runblock:: pycon + + >>> from roboticstoolbox.models.URDF import Panda + >>> panda = Panda() + >>> T = panda.fkine(panda.qz, end="panda_link3") + >>> print(T) + + +Most URDF models come with meshes provided as Collada file which provide detailed geometry and color. This can be visualized using the Swift simulator: - >>> panda.plot(qz, backend="swift") +.. runblock:: pycon + + >>> from roboticstoolbox.models.URDF import Panda + >>> panda = Panda() + >>> panda.plot(panda.qz, backend="swift") -which produces the 3-D plot +which produces the 3-D plot .. figure:: ../figs/swift.png :width: 600 @@ -429,17 +236,30 @@ Swift is a web-based visualizer using three.js to provide high-quality 3D animat It can produce vivid 3D effects using anaglyphs viewed with colored glasses. Animations can be recorded as MP4 files or animated GIF files which are useful for inclusion in GitHub markdown documents. +To load an arbitrary URDF or xacro file we can use:: + + >>> robot = URDFRobot(filename) + +which will preprocess and parse the file, and loads any associated mesh assets. + +If `filename` is a plain name with no suffix, like `"ur5"`, the Toolbox will attempt to dynamically load +the model from the `robot_descriptions `_ package, which is a collection of URDF and xacro files for many robots. +This package is installed automatically with RTB-P. + + Trajectories -============ +^^^^^^^^^^^^ -A joint-space trajectory for the Puma robot from its zero angle +A joint-space trajectory for the Puma robot from its zero angle pose to the upright (or READY) pose in 100 steps is .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> traj = rtb.jtraj(puma.qz, puma.qr, 100) + >>> from roboticstoolbox.models.DH import Puma560 + >>> from roboticstoolbox import jtraj + >>> puma = Puma560() + >>> traj = jtraj(puma.qz, puma.qr, 100) + >>> print(traj) >>> traj.q.shape where ``puma.qr`` is an example of a named joint configuration. @@ -449,11 +269,10 @@ The trajectory is a fifth order polynomial which has continuous jerk. By default, the initial and final velocities are zero, but these may be specified by additional arguments. -We could plot the joint coordinates as a function of time using the convenience +We could plot the joint coordinates and their velocities as a function of time using the convenience function:: - >>> rtb.qplot(traj.q) - + >>> traj.plot() Straight line (Cartesian) paths can be generated in a similar way between two points specified by a pair of poses in :math:`\SE{3}` @@ -463,12 +282,13 @@ two points specified by a pair of poses in :math:`\SE{3}` >>> import numpy as np >>> from spatialmath import SE3 - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() + >>> from roboticstoolbox.models.DH import Puma560 + >>> from roboticstoolbox import ctraj + >>> puma = Puma560() >>> t = np.arange(0, 2, 0.010) >>> T0 = SE3(0.6, -0.5, 0.3) >>> T1 = SE3(0.4, 0.5, 0.2) - >>> Ts = rtb.tools.trajectory.ctraj(T0, T1, len(t)) + >>> Ts = ctraj(T0, T1, len(t)) >>> len(Ts) >>> sol = puma.ikine_LM(Ts, q0=puma.qn) >>> sol.success @@ -485,7 +305,7 @@ one row per time step. Symbolic manipulation -===================== +^^^^^^^^^^^^^^^^^^^^^ As mentioned earlier, the Toolbox supports symbolic manipulation using SymPy. For example: @@ -500,27 +320,26 @@ The capability extends to forward kinematics .. runblock:: pycon :linenos: - >>> import roboticstoolbox as rtb - >>> import spatialmath.base as base - >>> puma = rtb.models.DH.Puma560(symbolic=True) + >>> from roboticstoolbox.models.DH import Puma560 + >>> from spatialmath import base + >>> puma = Puma560(symbolic=True) >>> q = base.sym.symbol("q_:6") # q = (q_1, q_2, ... q_5) >>> T = puma.fkine(q) >>> T.t[0] If we display the value of ``puma`` we see that the :math:`\alpha_j` values are now displayed in red to indicate that they are symbolic constants. The -x-coordinate of the end-effector is given by line 7. +x-coordinate of the end-effector is given by line 6. -SymPy allows any expression to be converted to LaTeX or a variety of languages -including C, Python and Octave/MATLAB. +SymPy allows any expression to be further manipulated and simplified, and to be converted to LaTeX or a variety of languages +including C, Rust, Python and Octave/MATLAB. Differential kinematics -======================= +^^^^^^^^^^^^^^^^^^^^^^^^ The Toolbox computes Jacobians:: - >>> J = puma.jacob0(q) >>> J = puma.jacobe(q) @@ -529,11 +348,12 @@ At a singular configuration .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() + >>> from roboticstoolbox.models.DH import Puma560 + >>> from roboticstoolbox import jsingu + >>> puma = Puma560() >>> J = puma.jacob0(puma.qr) >>> np.linalg.matrix_rank(J) - >>> rtb.jsingu(J) + >>> jsingu(J) Jacobians can also be computed for symbolic joint variables as for forward kinematics above. @@ -548,8 +368,8 @@ For all robot classes we can compute manipulability .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() + >>> from roboticstoolbox.models.DH import Puma560 + >>> puma = Puma560() >>> m = puma.manipulability(puma.qn) >>> print("Yoshikawa manipulability is", m) >>> m = puma.manipulability(puma.qn, method="asada") @@ -559,12 +379,12 @@ for the Yoshikawa and Asada measures respectively, and .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() + >>> from roboticstoolbox.models.DH import Puma560 + >>> puma = Puma560() >>> m = puma.manipulability(puma.qn, axes="trans") >>> print("Yoshikawa manipulability is", m) -is the Yoshikawa measure computed for just the task space translational degrees +is the Yoshikawa measure computed for just the task-space translational degrees of freedom. For ``Robot`` instances we can also compute the manipulability Jacobian:: @@ -576,8 +396,8 @@ such that :math:`\dot{m} = \mat{J}_m(\vec{q}) \dvec{q}`. Dynamics ^^^^^^^^ -The Python Toolbox supports several approaches to computing dynamics. -For models defined using standard or modified DH notation we use a classical version of the recursive Newton-Euler +The Python Toolbox supports several approaches to computing dynamics. +For models defined using standard- or modified-DH notation we use a classical version of the recursive Newton-Euler algorithm implemented in Python or C. .. note:: The same C code as used by RTB-M is called directly from Python, and does not use NumPy. @@ -586,8 +406,8 @@ For example, the inverse dynamics .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() + >>> from roboticstoolbox.models.DH import Puma560 + >>> puma = Puma560() >>> tau = puma.rne(puma.qn, np.zeros((6,)), np.zeros((6,))) >>> print(tau) @@ -613,9 +433,9 @@ which uses an RK45 numerical integration from the SciPy package to solve for the optional control function called as:: tau = mycontrol(robot, t, q, qd, **args) - + The fast C implementation is not capable of symbolic operation so a Python -version of RNE ``rne_python`` has been implemented as well. For a 6- or 7-DoF +version of RNE acts as a fallback. For a 6- or 7-DoF manipulator the torque expressions have thousands of terms yet are computed in less than a second. However, subsequent expression manipulation is slow. @@ -629,27 +449,10 @@ method, ``rne()``, and SMTB-P provides a set of classes for spatial velocity, acceleration, momentum, force and inertia. -New capability -============== - -There are several areas of innovation compared to the MATLAB version of the Toolbox. - -Branched mechanisms -^^^^^^^^^^^^^^^^^^^ - -The RTB-M ``SerialLink`` class had no option to express branching. In RTB-P the -equivalent class is ``DHRobot`` is similarly limited, but a new class ``ERobot`` -is more general and allows for branching (but not closed kinematic loops). The -robot is described by a set of ``ELink`` objects, each of which points to its -parent link. The ``ERobot`` has references to the root and leaf ``ELink`` objects. This -structure closely mirrors the URDF representation, allowing for easy import of -URDF models. - Collision checking ^^^^^^^^^^^^^^^^^^ -RTB-M had a simple, contributed but unsupported, collision checking capability. -This is dramatically improved in the Python version using [coal]_, the actively +The Toolbox supports collision checking using the Python version using :cite:`coal`, the actively maintained successor to FCL/hpp-fcl, which performs GJK/EPA-based distance and collision queries against primitive shapes such as Cylinders, Spheres and Boxes as well as mesh objects. Every robot link can have a collision shape in addition @@ -665,8 +468,8 @@ whole robots, discrete links, and objects in the world. For example a :math:`1 \times 1 \times 1` box centered at :math:`(1,0,0)` can be tested against all, or just one link, of the robot by:: - >>> panda = rtb.models.Panda() - >>> obstacle = Cuboid([1, 1, 1], pose = SE3(1, 0, 0)) + >>> panda = Panda() + >>> obstacle = Cuboid([1, 1, 1], pose = SE3(1, 0, 0)) >>> iscollision = panda.iscollided(panda.q, obstacle) # boolean >>> iscollision = panda.links[0].iscollided(obstacle) @@ -679,8 +482,103 @@ segment defined by two points in the world frame:: >>> d, p1, p2 = panda.links[0].closest_point(obstacle) -Interfaces -^^^^^^^^^^ +Mobile robots +============= + +The Toolbox also supports kinematic modeling, path planning and state +estimation for wheeled mobile robots, covering vehicle motion models, +waypoint/random-path driving, a variety of planners operating over different kinds of maps, and an Extended Kalman Filter (EKF) capable of dead-reckoning +localization, map-based localization, map making, or full Simultaneous +Localization and Mapping (SLAM). See the :doc:`mobile` reference pages for +the complete set of vehicle models, drivers, planners and estimators. + +Vehicle models +^^^^^^^^^^^^^^ + +Wheeled vehicles are modeled using kinematic motion models such as the +bicycle (car-like, Ackermann-steered) model used below, as well as unicycle +and differential-steer models. + +.. runblock:: pycon + + >>> from roboticstoolbox import Bicycle + >>> bike = Bicycle() + >>> print(bike) + +The vehicle can be driven by attaching a *driver* agent -- for example one +that steers toward a sequence of random waypoints within the workspace -- +and then simulated for a number of seconds + +.. runblock:: pycon + :linenos: + + >>> from roboticstoolbox import Bicycle, RandomPath + >>> bike = Bicycle(L=1) # wheelbase 1m + >>> bike.control = RandomPath(workspace=10, seed=0) + >>> _ = bike.run(T=5, animate=False) + >>> bike.x_hist.shape + +At line 4 the vehicle is driven for 5 seconds, and at line 5 we see that its +pose history, ``x_hist``, is an array with 50 rows (one per simulation time +step) and 3 columns (:math:`x`, :math:`y`, :math:`\theta`). + +Path planning +^^^^^^^^^^^^^ + +For navigation among obstacles, the Toolbox provides a variety of planners +which operate over an occupancy grid, for example a distance-transform +planner + +.. runblock:: pycon + :linenos: + + >>> import numpy as np + >>> from roboticstoolbox import DistanceTransformPlanner + >>> occgrid = np.zeros((10, 10)) + >>> occgrid[3:7, 5] = 1 # a wall-like obstacle + >>> dx = DistanceTransformPlanner(occgrid=occgrid, goal=(8, 8)) + >>> dx.plan() + >>> path = dx.query(start=(1, 1)) + >>> path.shape + +At line 7 a path is planned from the start to the goal cell, and its shape at +line 8 shows one row per waypoint. Other planners include ``Dstar``, ``PRM``, +``Lattice``, ``Dubins``, ``ReedsShepp``, ``CurvaturePoly`` and ``QuinticPoly``, +which trade off computation time, path optimality, and vehicle kinematic +constraints in different ways. + +Localization +^^^^^^^^^^^^ + +The Toolbox implements an Extended Kalman Filter (EKF) which, depending on +which combination of vehicle, sensor and landmark map is provided, solves +dead-reckoning localization, map-based localization, map making, or full +SLAM. + +For dead-reckoning localization, only a noisy vehicle motion model is needed + +.. runblock:: pycon + :linenos: + + >>> import numpy as np + >>> from roboticstoolbox import Bicycle, RandomPath, EKF + >>> V = np.diag([0.02, np.radians(0.5)]) ** 2 + >>> robot = Bicycle(covar=V, animation=None, workspace=10) + >>> robot.control = RandomPath(workspace=robot, seed=0) + >>> ekf = EKF(robot=(robot, V), P0=np.diag([0.05, 0.05, np.radians(0.5)]) ** 2) + >>> ekf.run(T=20) + >>> ekf.history[-1].xest + +Line 6 seeds the EKF with an estimate of the odometry noise covariance ``V`` +and an initial state covariance ``P0``, line 7 runs the filter for 20 seconds, +and line 8 shows the final estimated pose. Providing a range-bearing +``sensor`` and a ``map`` of known landmarks additionally enables map-based +localization; omitting the map instead performs SLAM, estimating both the +vehicle pose and the landmark positions concurrently. + + +Interfaces and software engineering +==================================== RTB-M could only animate a robot in a figure, and there was limited but not-well-supported ability to interface to V-REP and a physical robot. The @@ -709,21 +607,9 @@ The more general solution, and what is implemented inside ``plot`` in the exampl This makes it possible to animate multiple robots in the one graphical window, or the one robot in various environments either graphical or real. -.. figure:: ../figs/vp_ss_0000-2.png - :width: 600 - :alt: Puma560 rendered using the web-based VPython visualizer. - - Puma560 rendered using the web-based VPython visualizer. - -The VPython backend provides browser-based 3D graphics based on WebGL. This is advantageous for displaying on mobile -devices. Still frames and animations can be recorded. - - -Code engineering -^^^^^^^^^^^^^^^^ - -The code is implemented in Python :math:`\ge 3.10` and all code is hosted on GitHub and -unit-testing is performed using GitHub-actions. Test coverage is uploaded to +The code is implemented in Python, currently supporting versions 3.10 and higher. Type hinting is been added throughout the codebase +using modern (PEP604) Python type hints. Code coverage The code is hosted on GitHub and +unit-testing for Mac, Linux and Windows over all supported Python versions is performed using GitHub-actions. Test coverage, currently over 70%, is uploaded to ``codecov.io`` for visualization and trending. The code is documented with ReStructured Text format docstrings which provides powerful markup including cross-referencing, equations, class inheritance diagrams and figures -- all of which is converted @@ -731,54 +617,271 @@ to HTML documentation whenever a change is pushed, and this is accessible via GitHub pages. Issues can be reported via GitHub issues or patches submitted as pull requests. -RTB-P, and its dependencies, can be installed simply by either of:: +The Toolbox adopts a "when needed" approach to many dependencies and will only attempt +to import them if the user attempts to exploit a functionality that requires it. +If a dependency is not installed, a warning provides instructions on how to install it using ``pip``. - $ pip install roboticstoolbox-python +C/C++ extensions are provided for recursive Newton-Euler dynamics and optimized forward and inverse kinematics for ETS defined robots. These +wheels are built by the GitHub CI actions. A pyodide wheel is also built for use in the browser and is available as a GitHub release resource. - $ conda install -c conda-forge roboticstoolbox-python -which includes basic visualization using matplotlib. -Options such as ``vpython`` can be used to specify additional dependencies to be installed. -The Toolbox adopts a "when needed" approach to many dependencies and will only attempt -to import them if the user attempts to exploit a functionality that requires it. +Spatial math layer +=================== -If a dependency is not installed, a warning provides instructions on how to install it using ``pip``. -More details are given on the project home page. -This applies to the visualizers Vpython and Swift, as well as pybullet and ROS. -The Toolbox provides capability to import URDF-xacro files without ROS. -The backend architecture allows a user to connect to a ROS environment if required, and only then does ROS have to be -installed. +Robotics and computer vision require us to describe position, orientation and +pose in 3D space. Mobile robotics has the same requirement, but generally for 2D +space. We therefore need tools to represent quantities such as rigid-body +transformations (matrices :math:`\in \SE{n}` or twists :math:`\in \se{n}`), +rotations (matrices :math:`\in \SO{n}` or :math:`\so{n}`, Euler or roll-pitch-yaw +angles, or unit quaternions :math:`\in \mathrm{S}^3`). Such capability is amongst the oldest in +RTB-M and the equivalent functionality exists in RTB-P which makes use of the +Spatial Maths Toolbox for Python (SMTB-P) :cite:`SMTB-P`. For example: + +.. runblock:: pycon + >>> from spatialmath.base import * + >>> T = transl(0.5, 0.0, 0.0) @ rpy2tr(0.1, 0.2, 0.3, order='xyz') @ trotx(-90, 'deg') + >>> print(T) -Conclusion -========== +There is strong similarity to the equivalent MATLAB case apart from the use of +the ``@`` operator, the use of keyword arguments instead of keyword-value pairs, +and the format of the printed array. All the *classic* RTB-M functions are +provided in the ``spatialmath.base`` package as well as additional functions for +quaternions, vectors, twists and argument handling. There are also functions to +perform interpolation, plot and animate coordinate frames, and create movies, +using Matplotlib. The underlying datatypes in all cases are 1D and 2D NumPy +arrays. + +.. warning:: For a user transitioning from MATLAB the most significant difference is + the use of 1D arrays -- all MATLAB arrays have two dimensions, even if one of + them is equal to one. + +However some challenges arise when using arrays, whether native MATLAB matrices +or NumPy arrays as in this case. Firstly, arrays are not typed and for example a +:math:`3 \times 3` array could be an element of :math:`\SE{2}` or +:math:`\SO{3}` or an arbitrary matrix. + +Secondly, the operators we need for poses are a subset of those available for +matrices, and some operators may need to be redefined in a specific way. For +example, :math:`\SE{3} * \SE{3} \rightarrow \SE{3}` but :math:`\SE{3} + \SE{3} \rightarrow \mathbb{R}^{4 \times 4}`, and equality testing for a +unit-quaternion has to respect the double mapping. + +Thirdly, in robotics we often need to represent time sequences of poses. We +could add an extra dimension to the matrices representing rigid-body +transformations or unit-quaternions, or place them in a list. The first +approach is cumbersome and reduces code clarity, while the second cannot ensure +that all elements of the list have the same type. + +We use classes and data encapsulation to address all these issues. SMTB-P +provides abstraction classes ``SE3``, ``Twist3``, ``SO3``, ``UnitQuaternion``, +``SE2``, ``Twist2`` and ``SO2``. For example, the previous example could be written +as: + +.. runblock:: pycon + :linenos: + + >>> from spatialmath import * + >>> T = SE3(0.5, 0.0, 0.0) * SE3.RPY([0.1, 0.2, 0.3], order='xyz') * SE3.Rx(-90, unit='deg') + >>> print(T) + >>> T.eul() + >>> T.R + >>> T.t + +where composition is denoted by the ``*`` operator and the matrix is printed more elegantly (and elements are color +coded at the console or in ipython). +``SE3.RPY()`` is a class method that acts like a constructor, creating an ``SE3`` instance from a set of roll-pitch-yaw angles, +and ``SE3.Rx()`` creates an ``SE3`` instance from a pure rotation about the x-axis. +Attempts to compose with a non ``SE3`` instance would result in a ``TypeError``. + +The orientation of the new coordinate frame may be expressed in terms of Euler angles (line 9) +and components can be extracted such as the rotation submatrix (line 11) and translation (line 15). + +The pose ``T`` can also be displayed as a 3D coordinate frame:: + + >>> T.plot(color='red', label='2') + + +Rotation can also be represented by a unit quaternion + +.. runblock:: pycon + + >>> from spatialmath import UnitQuaternion + >>> print(UnitQuaternion.Rx(0.3)) + >>> print(UnitQuaternion.AngVec(0.3, [1, 0, 0])) + +which again demonstrates several alternative constructors. + + + +Multiple values +^^^^^^^^^^^^^^^ + +To support sequences of values each of these types inherits list properties from ``collections.UserList`` + +.. figure:: ../figs/pose-values.png + :width: 600 + :alt: Any of the SMTB-P pose classes can contain a list of values + + Any of the SMTB-P pose classes can contain a list of values + +We can index the values, iterate over the values, assign to values. +Some constructors take an array-like argument allowing creation of multi-valued pose objects, +for example: + +.. runblock:: pycon + + >>> from spatialmath import SE3 + >>> import numpy as np + >>> R = SE3.Rx(np.linspace(0, np.pi/2, num=100)) + >>> len(R) + +where the instance ``R`` contains a sequence of 100 rotation matrices. +Composition with a single-valued (scalar) pose instance broadcasts the scalar +across the sequence + +.. figure:: ../figs/broadcasting.png + :alt: Overloaded operators support broadcasting + + Overloaded operators support broadcasting + +Common constructors +^^^^^^^^^^^^^^^^^^^ + +The Toolboxes classes are somewhat polymorphic and share many "variant constructors" that allow object construction: + +- with orientation expressed in terms of canonic axis rotations, Euler vectors, angle-vector pair, + Euler or roll-pitch-yaw angles or orientation- and approach-vectors. +- from random values ``.Rand()`` +- ``SE3``, ``SE2``, ``SO3`` and ``SO2`` also support a matrix exponential constructor where the argument is the + corresponding Lie algebra element. +- empty, i.e. having no values or a length of 0 ``.Empty()`` +- an array of ``N`` values initialized to the object's identity value ``.Alloc(N)`` + +Common methods and operators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The types all have an inverse method ``.inv()`` and support composition with the inverse using the ``/`` operator +and integer exponentiation (repeated composition) using the ``**`` operator. +Other overloaded operators include ``*``, ``*=``, ``**``, ``**=``, ``/``, ``/=``, ``==``, ``!=``, ``+``, ``-``. + +All of this allows for concise and readable code. +The use of classes ensures type safety and that the matrices abstracted by the class are always valid members of +the group. +Operations such as addition, which are not group operations, yield a NumPy array rather than a class instance. + +Performance +^^^^^^^^^^^ + +These benefits come at a price in terms of execution time due to the overhead of +constructors, methods which wrap base functions, and type checking. The +Toolbox supports SymPy which provides powerful symbolic support for Python and +it works well in conjunction with NumPy, ie. a NumPy array can contain symbolic +elements. Many the Toolbox methods and functions contain extra logic to ensure +that symbolic operations work as expected. While this adds to the overhead it +means that for the user, working with symbols is as easy as working with +numbers. + + +.. table:: Performance on a 3.6GHz Intel Core i9 + + =================== ============== + Function/method Execution time + =================== ============== + ``base.rotx()`` 4.07 μs + ``base.trotx()`` 5.79 μs + ``SE3.Rx()`` 12.3 μs + ``SE3 * SE3`` 4.69 μs + ``4x4 @`` 0.986 μs + ``SE3.inv()`` 7.62 μs + ``base.trinv()`` 4.19 μs + ``np.linalg.inv()`` 4.49 μs + =================== ============== + + +History +======= + + + +Branched mechanisms +^^^^^^^^^^^^^^^^^^^ + +The RTB-M ``SerialLink`` class had no option to express branching. In RTB-P the +equivalent class is ``DHRobot`` is similarly limited, but a new class ``ERobot`` +is more general and allows for branching (but not closed kinematic loops). The +robot is described by a set of ``ELink`` objects, each of which points to its +parent link. The ``ERobot`` has references to the root and leaf ``ELink`` objects. This +structure closely mirrors the URDF representation, allowing for easy import of +URDF models. + +The Robotics Toolbox for MATLAB® (RTB-M) was created around 1991 to support +Peter Corke’s PhD research and was first published in 1995-6 :cite:`Corke95` +:cite:`Corke96`. It evolved over 30 years to track changes and improvements to +the MATLAB language and ecosystem, such as the addition of structures, objects, +lists (cell arrays) and strings, myriad of other improvements to the language, +new graphics and new tools such as IDE, debugger, notebooks (LiveScripts), apps +and continuous integration. An adverse consequence is that many poor (in +retrospect) early design decisions hinder development. Several notable +user contributions included collision detection, and symbolic analysis of kinematics +and dynamics leveraging the Symbolic Toolbox :cite:`Malzahn14`. + +Over time additional functionality was added, in particular for vision, and two +major refactorings led to the current state of three MATLAB toolboxes: Robotics Toolbox +for MATLAB, Machine Vision Toolbox for MATLAB (1999) both of which are now built +on the Spatial Math Toolbox for MATLAB (2019). + +The code was formally open sourced to support its use for the third edition of +John Craig’s book :cite:`Craig2005`. It was hosted on ftp sites, personal web +servers, Google code and currently GitHub and maintained under a succession of +version control tools including rcs, cvs, svn and git. + +The imperative for a Python version has long existed and the first port was +started in 2008 but ultimately failed for lack of ongoing resources to complete +a sufficient subset of functionality. Subsequent attempts have all met the same +fate. + +The design goals (as of 2021) can be summarised as new functionality: + +* A superset of the MATLAB Toolbox functionality +* Build on the Spatial Math Toolbox for Python :cite:`SMTB-P` which provides objects to + represent rotations as SO(2) and SE(3) matrices as well as unit-quaternions; + rigid-body motions as SE(2) and SE(3) matrices or twists in + se(2) and se(3); and Featherstone’s spatial vectors :cite:`Featherstone87`. +* Support models expressed using Denavit-Hartenberg notation (standard and + modified), elementary transform sequences :cite:`Corke07,Haviland20`, and URDF-style + rigid-body trees. Support branched, but not closed-loop or parallel, robots +* Collision checking + +and improved software engineering: + +* Use Python 3 (3.10 and greater) +* Utilize WebGL and Javascript graphics technologies +* Documentation in ReStructured Text using Sphinx and delivered via GitHub pages. +* Hosted on GitHub with continuous integration using GitHub actions +* High code-quality metrics for test coverage and automated code review and security analysis +* As few dependencies as possible, in particular being able to work with ROS but not be dependent on ROS. This sidesteps ROS constraints, at the time, on operating system and Python versions. +* Modular approach to interfacing to different graphics libraries, simulators and physical robots. +* Support Python notebooks which allows publication of static notebooks (for example via GitHub) and interactive online notebooks (JupyterLite, `MyBinder.org `_). +* Use of UniCode characters to make console output easier to read + +while being **familiar yet new**. It is hoped that it will serve the +community well for the next 30 years. + +The Toolbox: + +- has enabled the development of NEO, a high-performance reactive motion controller :cite:`neo` for robot arms and mobile manipulators; +- integrates with :cite:`bdsim`, a complementary minimalist block-diagram simulation tool. + +Summary +======= -This article has introduced and demonstrated in tutorial form the principle -features of the Robotics Toolbox for Python which runs on Mac, Windows and Linux +The Robotics Toolbox for Python runs on Mac, Windows and Linux using Python 3.10 or better. The code is free and open, and released under the -MIT licence. It provides many of the essential tools necessary for robotic -manipulator modelling, simulation and control which is essential for robotics -education and research. It is familiar yet new, and we hope it will serve the -community well for the next 25 years. - -A high-performance reactive motion controller, NEO, is based on this toolbox -[neo]_. The Toolbox has since grown to include mobile robotics motion models, -planners, EKF localization, map making and SLAM. Currently under development -are backend interfaces for CoppeliaSim, Dynamixel servo chains, and ROS, and -symbolic dynamics simplification/code generation; see also [bdsim]_, a -complementary minimalist block-diagram simulation tool. +MIT licence. It provides many of the essential tools necessary for modelling, simulation and control of arm and mobile robots, which is essential for robotics +education and research. References ========== -.. [Corke95] `P. Corke. "A computer tool for simulation and analysis: the Robotics Toolbox for MATLAB". In Proc. National Conf. Australian Robot Association, pages 319–330, Melbourne, July 1995. `_ -.. [Corke96] `P. Corke. "A robotics toolbox for MATLAB". IEEE Robotics and Automation Magazine, 3(1):24–32, Sept. 1996. `_ -.. [Craig2005] J. Craig, "Introduction to Robotics", Wiley, 2005. -.. [Featherstone87] R. Featherstone, Robot Dynamics Algorithms. Kluwer Academic, 1987. -.. [Corke07] P. Corke, `“A simple and systematic approach to assigning Denavit- Hartenberg parameters,” IEEE transactions on robotics, vol. 23, no. 3, pp. 590–594, 2007, DOI 10.1109/TRO.2007.896765. `_. -.. [Haviland20] `J. Haviland and P. Corke, “A systematic approach to computing the manipulator Jacobian and Hessian using the elementary transform sequence,” arXiv preprint, 2020. `_ -.. [coal] `coal: Collision detection And Lightweight `_ -.. [SMTB-P] `Spatial Math Toolbox for Python `_ -.. [bdsim] `Block diagram simulator for Python `_ -.. [neo] `NEO: A Novel Expeditious Optimisation Algorithm for Reactive Motion Control of Manipulators `_ -.. [corke21a] P. Corke and J. Haviland, "Not your grandmother’s toolbox – the Robotics Toolbox reinvented for Python", Proc. ICRA 2021. +.. bibliography:: + :style: unsrt diff --git a/docs/source/refs.bib b/docs/source/refs.bib new file mode 100644 index 000000000..62e245083 --- /dev/null +++ b/docs/source/refs.bib @@ -0,0 +1,93 @@ +@inproceedings{Corke95, + author = {Corke, Peter}, + title = {A computer tool for simulation and analysis: the {Robotics Toolbox} for {MATLAB}}, + booktitle = {Proc. National Conf. Australian Robot Association}, + pages = {319--330}, + address = {Melbourne}, + month = jul, + year = {1995}, + url = {http://www.petercorke.com/RTB/ARA95.pdf}, +} + +@article{Corke96, + author = {Corke, Peter}, + title = {A robotics toolbox for {MATLAB}}, + journal = {IEEE Robotics and Automation Magazine}, + volume = {3}, + number = {1}, + pages = {24--32}, + month = sep, + year = {1996}, + url = {https://ieeexplore.ieee.org/document/486658}, +} + +@book{Craig2005, + author = {Craig, John}, + title = {Introduction to Robotics}, + publisher = {Wiley}, + year = {2005}, +} + +@book{Featherstone87, + author = {Featherstone, Roy}, + title = {Robot Dynamics Algorithms}, + publisher = {Kluwer Academic}, + year = {1987}, +} + +@article{Corke07, + author = {Corke, Peter}, + title = {A simple and systematic approach to assigning {Denavit-Hartenberg} parameters}, + journal = {IEEE Transactions on Robotics}, + volume = {23}, + number = {3}, + pages = {590--594}, + year = {2007}, + doi = {10.1109/TRO.2007.896765}, + url = {https://ieeexplore.ieee.org/document/4252158}, +} + +@misc{Haviland20, + author = {Haviland, Jesse and Corke, Peter}, + title = {A systematic approach to computing the manipulator {Jacobian} and {Hessian} using the elementary transform sequence}, + howpublished = {arXiv preprint}, + year = {2020}, + url = {https://arxiv.org/abs/2010.08696}, +} + +@misc{coal, + title = {coal: Collision detection And Lightweight}, + url = {https://github.com/coal-library/coal}, +} + +@misc{SMTB-P, + title = {Spatial Math Toolbox for Python}, + url = {https://github.com/petercorke/spatialmath-python}, +} + +@misc{bdsim, + title = {Block diagram simulator for Python}, + url = {https://github.com/petercorke/bdsim}, +} + +@misc{neo, + author = {Haviland, Jesse}, + title = {{NEO}: A Novel Expeditious Optimisation Algorithm for Reactive Motion Control of Manipulators}, + url = {https://jhavl.github.io/neo}, +} + +@inproceedings{corke21a, + author = {Corke, Peter and Haviland, Jesse}, + title = {Not your grandmother's toolbox -- the {Robotics Toolbox} reinvented for {Python}}, + booktitle = {Proc. ICRA}, + year = {2021}, +} + +@phdthesis{Malzahn14, + author = {Malzahn, J{\"o}rn}, + title = {Modeling and Control of Multi-Elastic-Link Robots under Gravity}, + school = {Technical University of Dortmund (TU Dortmund)}, + address = {Germany}, + year = {2014}, + url = {https://eldorado.tu-dortmund.de/server/api/core/bitstreams/a3bccd85-3471-48ab-badb-21a49894e819/content}, +} diff --git a/pyproject.toml b/pyproject.toml index c831d4369..7bfde2a8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,10 +97,11 @@ dev = [ "ruff", "coal; sys_platform != 'win32'", "trimesh", + "nbmake", ] # Backward-compatible alias; notebook tooling is included in "dev". -nb = ["pytest-nbmake"] +nb = ["nbmake"] docs = [ "sphinx", @@ -108,6 +109,7 @@ docs = [ "sphinx-autorun", "sphinx_autodoc_typehints", "sphinx-favicon", + "sphinxcontrib-bibtex", ] diff --git a/src/roboticstoolbox/mobile/CurvaturePolyPlanner.py b/src/roboticstoolbox/mobile/CurvaturePolyPlanner.py index 53bb188f0..a01cffb8e 100644 --- a/src/roboticstoolbox/mobile/CurvaturePolyPlanner.py +++ b/src/roboticstoolbox/mobile/CurvaturePolyPlanner.py @@ -87,7 +87,7 @@ def __init__(self, curvature=None): def query(self, start, goal): r""" - Find a path betwee two configurations + Find a path between two configurations :param start: start configuration :math:`(x, y, \theta)` :type start: array_like(3), optional diff --git a/src/roboticstoolbox/mobile/PlannerBase.py b/src/roboticstoolbox/mobile/PlannerBase.py index e506fd970..c6d22307f 100644 --- a/src/roboticstoolbox/mobile/PlannerBase.py +++ b/src/roboticstoolbox/mobile/PlannerBase.py @@ -824,7 +824,7 @@ def plot_bg( - inflated occupied cells in pink If distance is provided, or the planner has a distancemap attribute - the the distance field will be used as the background and obstacle cells + the distance field will be used as the background and obstacle cells (actual or inflated) will be shown in red. A colorbar is added. """ if self._occgrid is None: diff --git a/src/roboticstoolbox/mobile/drivers.py b/src/roboticstoolbox/mobile/drivers.py index c4251d612..2f2e321fb 100644 --- a/src/roboticstoolbox/mobile/drivers.py +++ b/src/roboticstoolbox/mobile/drivers.py @@ -147,10 +147,11 @@ def driveto(self, goal): goal_heading = atan2(goal[1] - self._veh._x[1], goal[0] - self._veh._x[0]) delta_heading = base.angdiff(goal_heading, self._veh._x[2]) - print( - f"t={self._veh._t:.1f}, pos=({self._veh._x[0]:.1f}, {self._veh._x[1]:.1f}), ", - f"goal_heading={goal_heading * 180 / pi:.1f}, delta_heading={delta_heading * 180 / pi:.1f}", - ) + if self._veh.verbose or self._verbose: + print( + f"t={self._veh._t:.1f}, pos=({self._veh._x[0]:.1f}, {self._veh._x[1]:.1f}), ", + f"goal_heading={goal_heading * 180 / pi:.1f}, delta_heading={delta_heading * 180 / pi:.1f}", + ) return np.r_[self._speed, self._headinggain * delta_heading] diff --git a/src/roboticstoolbox/models/URDF/URDFRobot.py b/src/roboticstoolbox/models/URDF/URDFRobot.py index 50f7de280..3cc839ce6 100644 --- a/src/roboticstoolbox/models/URDF/URDFRobot.py +++ b/src/roboticstoolbox/models/URDF/URDFRobot.py @@ -11,6 +11,7 @@ from pathlib import Path import importlib +import sys import warnings from typing import Callable, TextIO @@ -102,6 +103,26 @@ def _load_rd_module(robot_name: str): except ImportError as e: last_error = e continue + except Exception as e: + # robot_descriptions clones a git repository (via GitPython, + # which shells out to a real git binary) the first time a given + # model is imported. Pyodide/JupyterLite has no subprocess + # execution and no git binary, so this always fails there -- + # not a bug, an environment limitation. The exact exception type + # depends on how GitPython fails in that sandbox, so this is + # deliberately broad, but only ever intercepts on Pyodide. + if sys.platform == "emscripten": + raise ValueError( + f"Toolbox uses {_rd_link()} to provide URDF robot models, " + "which clones a git repository on first use. That isn't " + "possible in this browser (Pyodide/JupyterLite) sandbox -- " + f'this is an expected limitation loading "{robot_name}" ' + "here, not a bug. Try a DH- or ETS-based model instead " + "(e.g. rtb.models.DH.Panda()), or run this notebook in a " + "regular Python environment to use robot_descriptions-" + "backed models." + ) from e + raise renamed_to = _find_rd_rename(robot_name, candidates) if renamed_to is not None: diff --git a/src/roboticstoolbox/models/URDF/px150.py b/src/roboticstoolbox/models/URDF/px150.py index 0d0a4fbf9..f1fef2e75 100644 --- a/src/roboticstoolbox/models/URDF/px150.py +++ b/src/roboticstoolbox/models/URDF/px150.py @@ -37,7 +37,7 @@ def __init__(self): ) self.qr = np.array([0, -0.3, 0, -2.2, 0, 2.0, np.pi / 4, 0]) - self.qz = np.zeros(7) + self.qz = np.zeros(8) self.addconfiguration("qr", self.qr) self.addconfiguration("qz", self.qz) diff --git a/src/roboticstoolbox/models/URDF/rx150.py b/src/roboticstoolbox/models/URDF/rx150.py index 3cb394ebb..a0ff57605 100644 --- a/src/roboticstoolbox/models/URDF/rx150.py +++ b/src/roboticstoolbox/models/URDF/rx150.py @@ -37,7 +37,7 @@ def __init__(self): ) self.qr = np.array([0, -0.3, 0, -2.2, 0, 2.0, np.pi / 4, 0]) - self.qz = np.zeros(7) + self.qz = np.zeros(8) self.addconfiguration("qr", self.qr) self.addconfiguration("qz", self.qz) diff --git a/src/roboticstoolbox/models/URDF/rx200.py b/src/roboticstoolbox/models/URDF/rx200.py index 8424f731b..6382837eb 100644 --- a/src/roboticstoolbox/models/URDF/rx200.py +++ b/src/roboticstoolbox/models/URDF/rx200.py @@ -37,7 +37,7 @@ def __init__(self): ) self.qr = np.array([0, -0.3, 0, -2.2, 0, 2.0, np.pi / 4, 0]) - self.qz = np.zeros(7) + self.qz = np.zeros(8) self.addconfiguration("qr", self.qr) self.addconfiguration("qz", self.qz) diff --git a/src/roboticstoolbox/robot/DHRobot.py b/src/roboticstoolbox/robot/DHRobot.py index 1ccacc679..bf2f8d8d8 100644 --- a/src/roboticstoolbox/robot/DHRobot.py +++ b/src/roboticstoolbox/robot/DHRobot.py @@ -1288,7 +1288,7 @@ def hessian0(self, q=None, J0=None, start=None, end=None): :return: Hessian matrix :rtype: ndarray(6,n,n) - This method calculcates the Hessisan in the base frame. One of ``J0`` or + This method calculates the Hessian in the base frame. One of ``J0`` or ``q`` is required. If ``J0`` is already calculated for the joint coordinates ``q`` it can be passed in to to save computation time. @@ -1950,12 +1950,12 @@ def ik_lm_chan( :param Tep: The desired end-effector pose or pose trajectory :param q0: initial joint configuration (default to random valid joint - configuration contrained by the joint limits of the robot) + configuration constrained by the joint limits of the robot) :param ilimit: maximum number of iterations per search :param slimit: maximum number of search attempts :param tol: final error tolerance :param reject_jl: constrain the solution to being within the joint limits of - the robot (reject solution with invalid joint configurations and perfrom + the robot (reject solution with invalid joint configurations and perform another search up to the slimit) :param we: a mask vector which weights the end-effector error priority. Corresponds to translation in X, Y and Z and rotation about X, Y and Z @@ -2054,12 +2054,12 @@ def ik_lm_wampler( :param Tep: The desired end-effector pose or pose trajectory :param q0: initial joint configuration (default to random valid joint - configuration contrained by the joint limits of the robot) + configuration constrained by the joint limits of the robot) :param ilimit: maximum number of iterations per search :param slimit: maximum number of search attempts :param tol: final error tolerance :param reject_jl: constrain the solution to being within the joint limits of - the robot (reject solution with invalid joint configurations and perfrom + the robot (reject solution with invalid joint configurations and perform another search up to the slimit) :param we: a mask vector which weights the end-effector error priority. Corresponds to translation in X, Y and Z and rotation about X, Y and Z @@ -2158,12 +2158,12 @@ def ik_lm_sugihara( :param Tep: The desired end-effector pose or pose trajectory :param q0: initial joint configuration (default to random valid joint - configuration contrained by the joint limits of the robot) + configuration constrained by the joint limits of the robot) :param ilimit: maximum number of iterations per search :param slimit: maximum number of search attempts :param tol: final error tolerance :param reject_jl: constrain the solution to being within the joint limits of - the robot (reject solution with invalid joint configurations and perfrom + the robot (reject solution with invalid joint configurations and perform another search up to the slimit) :param we: a mask vector which weights the end-effector error priority. Corresponds to translation in X, Y and Z and rotation about X, Y and Z @@ -2263,12 +2263,12 @@ def ik_nr( :param Tep: The desired end-effector pose or pose trajectory :param q0: initial joint configuration (default to random valid joint - configuration contrained by the joint limits of the robot) + configuration constrained by the joint limits of the robot) :param ilimit: maximum number of iterations per search :param slimit: maximum number of search attempts :param tol: final error tolerance :param reject_jl: constrain the solution to being within the joint limits of - the robot (reject solution with invalid joint configurations and perfrom + the robot (reject solution with invalid joint configurations and perform another search up to the slimit) :param we: a mask vector which weights the end-effector error priority. Corresponds to translation in X, Y and Z and rotation about X, Y and Z @@ -2370,12 +2370,12 @@ def ik_gn( :param Tep: The desired end-effector pose or pose trajectory :param q0: initial joint configuration (default to random valid joint - configuration contrained by the joint limits of the robot) + configuration constrained by the joint limits of the robot) :param ilimit: maximum number of iterations per search :param slimit: maximum number of search attempts :param tol: final error tolerance :param reject_jl: constrain the solution to being within the joint limits of - the robot (reject solution with invalid joint configurations and perfrom + the robot (reject solution with invalid joint configurations and perform another search up to the slimit) :param we: a mask vector which weights the end-effector error priority. Corresponds to translation in X, Y and Z and rotation about X, Y and Z diff --git a/src/roboticstoolbox/robot/IK.py b/src/roboticstoolbox/robot/IK.py index 4077dfc5d..5d9b22e1e 100644 --- a/src/roboticstoolbox/robot/IK.py +++ b/src/roboticstoolbox/robot/IK.py @@ -528,7 +528,7 @@ class IK_NR(IKSolver): :param joint_limits: Reject solutions with joint limit violations :param seed: A seed for the private RNG used to generate random joint coordinate vectors - :param pinv: If True, will use the psuedoinverse in the `step` method instead of + :param pinv: If True, will use the pseudoinverse in the `step` method instead of the normal inverse :param kq: The gain for joint limit avoidance. Setting to 0.0 will remove this completely from the solution @@ -909,7 +909,7 @@ class IK_GN(IKSolver): :param joint_limits: Reject solutions with joint limit violations :param seed: A seed for the private RNG used to generate random joint coordinate vectors - :param pinv: If True, will use the psuedoinverse in the `step` method instead of + :param pinv: If True, will use the pseudoinverse in the `step` method instead of the normal inverse :param kq: The gain for joint limit avoidance. Setting to 0.0 will remove this completely from the solution diff --git a/src/roboticstoolbox/robot/RobotKinematics.py b/src/roboticstoolbox/robot/RobotKinematics.py index 504603467..3fc793e9c 100644 --- a/src/roboticstoolbox/robot/RobotKinematics.py +++ b/src/roboticstoolbox/robot/RobotKinematics.py @@ -667,7 +667,7 @@ def ik_NR( :param end: the link considered as the end-effector :param start: the link considered as the base frame, defaults to the robots's base frame :param q0: initial joint configuration (default to random valid joint - configuration contrained by the joint limits of the robot) + configuration constrained by the joint limits of the robot) :param ilimit: maximum number of iterations per search :param slimit: maximum number of search attempts :param tol: final error tolerance @@ -675,10 +675,10 @@ def ik_NR( Corresponds to translation in X, Y and Z and rotation about X, Y and Z respectively :param joint_limits: constrain the solution to being within the joint limits of - the robot (reject solution with invalid joint configurations and perfrom + the robot (reject solution with invalid joint configurations and perform another search up to the slimit) - :param pinv: Use the psuedo-inverse instad of the normal matrix inverse - :param pinv_damping: Damping factor for the psuedo-inverse + :param pinv: Use the pseudo-inverse instead of the normal matrix inverse + :param pinv_damping: Damping factor for the pseudo-inverse :returns: tuple (q, success, iterations, searches, residual) ``sol = ets.ik_NR(Tep)`` are the joint coordinates (n) corresponding @@ -781,7 +781,7 @@ def ik_GN( :param end: the link considered as the end-effector :param start: the link considered as the base frame, defaults to the robots's base frame :param q0: initial joint configuration (default to random valid joint - configuration contrained by the joint limits of the robot) + configuration constrained by the joint limits of the robot) :param ilimit: maximum number of iterations per search :param slimit: maximum number of search attempts :param tol: final error tolerance @@ -789,10 +789,10 @@ def ik_GN( Corresponds to translation in X, Y and Z and rotation about X, Y and Z respectively :param joint_limits: constrain the solution to being within the joint limits of - the robot (reject solution with invalid joint configurations and perfrom + the robot (reject solution with invalid joint configurations and perform another search up to the slimit) - :param pinv: Use the psuedo-inverse instad of the normal matrix inverse - :param pinv_damping: Damping factor for the psuedo-inverse + :param pinv: Use the pseudo-inverse instead of the normal matrix inverse + :param pinv_damping: Damping factor for the pseudo-inverse :returns: tuple (q, success, iterations, searches, residual) ``sol = ets.ik_GN(Tep)`` are the joint coordinates (n) corresponding @@ -1102,7 +1102,7 @@ def ikine_NR( :param joint_limits: Reject solutions with joint limit violations :param seed: A seed for the private RNG used to generate random joint coordinate vectors - :param pinv: If True, will use the psuedoinverse in the `step` method instead of + :param pinv: If True, will use the pseudoinverse in the `step` method instead of the normal inverse :param kq: The gain for joint limit avoidance. Setting to 0.0 will remove this completely from the solution @@ -1225,7 +1225,7 @@ def ikine_GN( :param joint_limits: Reject solutions with joint limit violations :param seed: A seed for the private RNG used to generate random joint coordinate vectors - :param pinv: If True, will use the psuedoinverse in the `step` method instead of + :param pinv: If True, will use the pseudoinverse in the `step` method instead of the normal inverse :param kq: The gain for joint limit avoidance. Setting to 0.0 will remove this completely from the solution diff --git a/tech-debt.md b/tech-debt.md index 038c98011..2b8f1df00 100644 --- a/tech-debt.md +++ b/tech-debt.md @@ -929,3 +929,71 @@ rename is a breaking change for any external callers — deferred rather than done opportunistically. If/when a broader API-breaking pass happens on this module (or at the next major version bump), rename `list` to something that doesn't shadow a builtin. + +--- + +## `spatialgeometry` is vendored (pure-Python) inside RTB, but CI also installs the real external package from git + +### Background + +`src/spatialgeometry/` is a full copy of the `spatialgeometry` package +living inside this repo, bundled into RTB's own wheel +(`pyproject.toml`'s `[tool.scikit-build] wheel.packages` and +`sdist.include` both list `src/spatialgeometry` alongside +`src/roboticstoolbox`). Per git history this was deliberate: + +- `1b522e65` — "bundle spatialgeometry as pure-Python (removes external + dep)" +- `f99c154f` — "change collision backend from PyBullet to Coal" + +i.e. the external `spatialgeometry` PyPI package was compiled (native +extensions) and became a NumPy-1-vs-2 compatibility hazard, so RTB +vendored its own pure-Python copy to stop depending on it directly. + +However, `.github/workflows/ci.yml` still separately does, in every job +that needs `swift` (`test`, `coverage`, `docs-build`): + +``` +pip install git+https://github.com/petercorke/spatialgeometry.git@future +pip install git+https://github.com/petercorke/swift.git@future +``` + +This installs the *external* `spatialgeometry` under the same top-level +import name (`spatialgeometry`) that RTB's own wheel provides. Since +`pip install .[dev]` runs afterwards and rebuilds/installs RTB's own +wheel (which includes its bundled `src/spatialgeometry`), the files from +the external git install are almost certainly clobbered by RTB's vendored +copy — meaning the `@future`-branch install is likely a no-op in +practice, or at best relies on file-level overlap being total. This +hasn't been directly diagnosed/proven this session, just flagged as +suspicious: two packages exporting the same import name, installed in +sequence, is fragile regardless of which one currently "wins". + +### Proposed fix + +Decide on one ownership model and stop straddling both: + +- **Option A**: keep vendoring `spatialgeometry` inside RTB (current + state) and drop the redundant `pip install + git+.../spatialgeometry.git@future` step from `ci.yml` — only + `swift.git@future` is actually needed (assuming `swift` itself doesn't + require anything from the external `spatialgeometry` that the vendored + copy lacks; needs verifying). + `swift.git@future`'s own dependency on `spatialgeometry` also needs + checking — if `swift` unconditionally pulls in the real + `spatialgeometry` from PyPI, that's the actual source of the NumPy-2 + crash for `spatialgeometry` again, RTB's vendoring or not. +- **Option B** (what the user favours): un-vendor `src/spatialgeometry` + from RTB entirely, get `spatialgeometry` a proper NumPy-2-compatible + PyPI release (companion to whatever eventually resolves the + `swift`/`spatialgeometry` `@future`-branch hotfix noted elsewhere in + this file under CI), and go back to a normal external dependency in + `pyproject.toml`. Cleaner long-term (one source of truth for + `spatialgeometry`, matches how `spatialmath-python` is already handled + as a normal external dependency), but is real work: whoever maintains + `petercorke/spatialgeometry` needs to actually cut and publish a + release from its `future` branch first, or RTB is back to the same + NumPy-1 compiled-wheel crash it vendored its way out of. + +Either way, the current arrangement (vendored copy + redundant external +git install in CI) should not persist indefinitely without a decision.