From b98a83e4bbada60ffd7a59542597d6db3514f09f Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Sun, 5 Jul 2026 10:44:13 +1000 Subject: [PATCH 1/3] fix(tests): fix Python 3.10-only mock.patch AttributeError in fknm fallback tests roboticstoolbox/robot/ETS.py defines a class also called ETS, and robot/__init__.py's `from ...ETS import ETS` rebinds the "ETS" attribute on the roboticstoolbox.robot package to the class, shadowing the submodule of the same name. Python 3.10's unittest.mock resolves dotted-string patch targets via plain getattr (falling back to import only on AttributeError), so patch("...ETS.ETS_fkine", ...) resolved "ETS" to the shadowing class and failed with AttributeError; 3.11+ uses pkgutil.resolve_name and isn't fooled by the same shadowing. This is why it only ever showed up on Python 3.10 in CI. Not a real code bug - the actual fknm/facade fallback machinery was always fine, only the test's patch target resolution was broken on 3.10. Fixed by looking up the real module via sys.modules directly (the only lookup with no getattr involved) and patching against that with patch.object() instead of a dotted string. Rehearsed: all 41 tests in test_fknm_fallback.py pass under both Python 3.10 and 3.13. Co-Authored-By: Claude Sonnet 5 --- tests/test_fknm_fallback.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/tests/test_fknm_fallback.py b/tests/test_fknm_fallback.py index 2f945f906..8a07f5e52 100644 --- a/tests/test_fknm_fallback.py +++ b/tests/test_fknm_fallback.py @@ -13,6 +13,7 @@ """ import os +import sys import timeit import unittest from contextlib import contextmanager @@ -23,6 +24,24 @@ import sympy import roboticstoolbox as rtb +# roboticstoolbox/robot/ETS.py defines a class also called ETS, and +# roboticstoolbox/robot/__init__.py does `from ...ETS import ETS`, which +# rebinds the "ETS" attribute on the roboticstoolbox.robot package to the +# class, shadowing the submodule of the same name. `import a.b.c as x` is +# defined as `import a.b.c; x = a.b.c` — that second step is still +# attribute access, so it hits the same shadowing and also gives the +# class, not the module. sys.modules[...] is a plain dict keyed by the +# literal dotted string, with no getattr involved, so it's the only +# reliably-correct way to get the real module object here. +# +# This only matters for patch() at all because Python 3.10's +# unittest.mock resolves dotted string patch targets via plain getattr +# (falling back to import only on AttributeError), so +# patch("...ETS.ETS_fkine", ...) resolves "ETS" to the shadowing class +# and fails with AttributeError; 3.11+ uses pkgutil.resolve_name and +# isn't fooled. patch.object() against the real module (via sys.modules) +# works on every version. +_ETS_module = sys.modules["roboticstoolbox.robot.ETS"] from spatialmath import SE3 @@ -57,7 +76,7 @@ def _no_c_fkine(): def _py(fknm, q, base, tool, include_base, _data=None): return _python_fkine(_data, q, base, tool, include_base) - with patch("roboticstoolbox.robot.ETS.ETS_fkine", new=_py): + with patch.object(_ETS_module, "ETS_fkine", new=_py): yield @@ -69,7 +88,7 @@ def _no_c_jacob0(): def _py(fknm, q, tool, _data=None, _n=None): return _python_jacob0(_data, _n, q, tool) - with patch("roboticstoolbox.robot.ETS.ETS_jacob0", new=_py): + with patch.object(_ETS_module, "ETS_jacob0", new=_py): yield @@ -81,7 +100,7 @@ def _no_c_jacobe(): def _py(fknm, q, tool, _data=None, _n=None): return _python_jacobe(_data, _n, q, tool) - with patch("roboticstoolbox.robot.ETS.ETS_jacobe", new=_py): + with patch.object(_ETS_module, "ETS_jacobe", new=_py): yield @@ -101,7 +120,7 @@ def _py(fknm, q, J0, tool, _data=None, _n=None): verifymatrix(J0, (6, _n)) return _python_hessian(J0) - with patch("roboticstoolbox.robot.ETS.ETS_hessian0", new=_py): + with patch.object(_ETS_module, "ETS_hessian0", new=_py): yield @@ -121,7 +140,7 @@ def _py(fknm, q, Je, tool, _data=None, _n=None): verifymatrix(Je, (6, _n)) return _python_hessian(Je) - with patch("roboticstoolbox.robot.ETS.ETS_hessiane", new=_py): + with patch.object(_ETS_module, "ETS_hessiane", new=_py): yield From fc53e6dcad74a9613caa876b42ce75317b09b155 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Sun, 5 Jul 2026 10:45:34 +1000 Subject: [PATCH 2/3] docs(tech-debt): note the Python 3.10 mock workaround for future removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-references the fknm fallback test fix in this same branch. Python 3.10 reaches EOL October 2026 — flag this specific workaround for removal (and a general 3.10-specific-code sweep) at that point. Co-Authored-By: Claude Sonnet 5 --- tech-debt.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tech-debt.md b/tech-debt.md index de264de90..f6ad93c41 100644 --- a/tech-debt.md +++ b/tech-debt.md @@ -647,3 +647,39 @@ practice, re-add caching with a corrected key that includes `matrix.python-version` (or better, restructure so only one job per OS does the network fetch and others restore from it) — don't just restore this exact removed code. + +--- + +## Python-3.10-specific workaround: `sys.modules` lookup in `test_fknm_fallback.py` + +### Background + +`tests/test_fknm_fallback.py` (2026-07-05) has a `_ETS_module = +sys.modules["roboticstoolbox.robot.ETS"]` workaround, with a long comment +explaining why: `roboticstoolbox/robot/ETS.py` defines a class also called +`ETS`, `robot/__init__.py`'s `from ...ETS import ETS` shadows the +submodule of the same name on the `roboticstoolbox.robot` package, and +Python 3.10's `unittest.mock` resolves dotted-string `patch()` targets via +plain `getattr` (falling back to import only on `AttributeError`) — so it +gets fooled by the shadowing and raises `AttributeError`. Python 3.11+ +rewrote this resolution to use `pkgutil.resolve_name`, which isn't fooled. +Not a real code bug (the actual fknm/facade fallback machinery was always +fine) — purely a Python-3.10 `unittest.mock` limitation the test had to +work around. + +Python 3.10 reaches end-of-life in **October 2026** (per the official +CPython release schedule). `pyproject.toml`'s `requires-python = ">=3.10"` +and the module/class name collision in `ETS.py` aren't going anywhere on +their own, but this specific workaround exists *only* because of 3.10's +mock behavior. + +### Proposed fix + +When `requires-python` drops support for 3.10 (naturally, around/after its +EOL), search for this specific workaround and simplify +`test_fknm_fallback.py` back to plain `patch("roboticstoolbox.robot.ETS.ETS_fkine", ...)` +-style dotted strings, since 3.11+'s `pkgutil.resolve_name`-based resolution +handles the shadowing correctly on its own. Also worth a quick sweep for +any other `sys.version_info`/Python-3.10-specific conditionals elsewhere in +the codebase at that point, so 3.10 cleanup happens in one pass rather than +piecemeal. From 9922d07d5fdb9e12cf852f9b8c735d96d0114b2b Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Sun, 5 Jul 2026 12:53:07 +1000 Subject: [PATCH 3/3] fix(docs): resolve reST syntax and structural Sphinx build warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full docs build went from 216 warnings/errors down to 5 (the deferred KinematicCache/SensorBase/jointset items, left for the runblock-code pass). Fixes span: - Jinja2 filter-precedence bug in the autosummary method template ((class + '.' + name) | escape | underline) — the filter chain was only sizing the title underline to the method name, not the full "Class.method" title, breaking all 39 generated IK stub pages. - Duplicate object descriptions (ik_GN/ik_LM/ik_NR/ikine_GN/ikine_LM documented both inline and via IK/stubs/) via :no-index: in the stub template. - A broken Examples/.. runblock:: docstring template repeated across BaseRobot.py, ETS.py, Link.py, ET.py, Dynamics.py: missing blank lines before Examples/See Also, and un-indented runblock directive bodies (making those examples inert — never actually executed). - 7 malformed reST tables in DHRobot.py/RobotKinematics.py sharing one column-width overflow bug (``iterations`` wider than its border). - A genuine Napoleon + sphinx_autodoc_typehints conflict: bare NumPy-style "Note" headers mixed into otherwise-reST docstrings, root-caused via an isolated minimal Sphinx build bisecting the extension list. Confirmed Napoleon is still needed (tools/urdf/*.py has real vendored NumPy-style docstrings) — fixed by converting the two affected Note headers to .. rubric:: Notes instead. - Assorted one-offs: a Markdown-style pipe table misparsed as reST substitution syntax, unclosed inline literals/interpreted text, under/over-indented bullet continuations breaking list structure, a bare URL parsed as an implicit hyperlink target, an incomplete list-table row, `**kwargs` in a field name misparsed as bold markup, a nested bullet list missing its blank-line separator. - Removed two pieces of dead config: suppress_warnings' non-existent "duplicate" token (replaced with the real "ref.citation"), and napoleon_custom_sections listing an unused "Synopsis" section. Logged two tech-debt entries for what's deliberately left for later: repeated :returns:/:param: fields that don't trigger warnings but are still wrong, and the broader NumPy-to-reST docstring migration debt (48 vestigial "See Also" headers across 6 files sharing the same Napoleon-conflict risk class as the Note bug). Verified via a full local Sphinx build after each round of fixes, not just spot-checking individual warnings. Co-Authored-By: Claude Sonnet 5 --- docs/source/_templates/autosummary/method.rst | 3 +- docs/source/conf.py | 22 +- src/roboticstoolbox/blocks/arm.py | 1 - src/roboticstoolbox/models/DH/Hyper.py | 4 +- src/roboticstoolbox/models/DH/Hyper3d.py | 4 +- src/roboticstoolbox/models/DH/Orion5.py | 4 +- src/roboticstoolbox/models/ETS/Puma560.py | 9 +- src/roboticstoolbox/robot/BaseRobot.py | 250 +++++++++++------- src/roboticstoolbox/robot/DHLink.py | 3 + src/roboticstoolbox/robot/DHRobot.py | 49 ++-- src/roboticstoolbox/robot/Dynamics.py | 72 +++-- src/roboticstoolbox/robot/ET.py | 124 +++++---- src/roboticstoolbox/robot/ETS.py | 189 ++++++++----- src/roboticstoolbox/robot/Link.py | 58 ++-- src/roboticstoolbox/robot/Robot.py | 22 +- src/roboticstoolbox/robot/RobotKinematics.py | 12 +- src/roboticstoolbox/tools/trajectory.py | 6 +- tech-debt.md | 112 ++++++++ 18 files changed, 609 insertions(+), 335 deletions(-) diff --git a/docs/source/_templates/autosummary/method.rst b/docs/source/_templates/autosummary/method.rst index 819ffa497..9abaeec7f 100644 --- a/docs/source/_templates/autosummary/method.rst +++ b/docs/source/_templates/autosummary/method.rst @@ -1,7 +1,8 @@ :orphan: -{{ class + '.' + name | escape | underline}} +{{ (class + '.' + name) | escape | underline}} .. currentmodule:: {{ module }} .. auto{{ objtype }}:: {{ fullname }} + :no-index: diff --git a/docs/source/conf.py b/docs/source/conf.py index 64d12388b..c28456c07 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -170,7 +170,14 @@ # the documentation napoleon_include_special_with_doc = True -napoleon_custom_sections = ["Synopsis"] +# Napoleon is still needed: tools/urdf/utils.py and urdf.py have genuine +# NumPy-style docstrings (vendored/adapted from an external URDF library) +# that depend on it. Everywhere else uses explicit reST fields directly. +# Bare NumPy-style section headers (Note, Examples, See Also, ...) mixed +# into otherwise-reST docstrings should use explicit directives +# (.. rubric:: Notes, etc.) instead — Napoleon's heuristic recognition of +# those bare headers can conflict with sphinx_autodoc_typehints (see +# tech-debt.md, 2026-07-05). # -------- Options AutoSummary -------------------------------------------------------# @@ -187,11 +194,18 @@ # -------- Suppress common noisy warnings ----------------------------------------# # Suppress "more than one target found" for short/common attribute names like -# n, m, robot, symbolic that appear across many classes, and suppress duplicate -# object description warnings for classes documented both inline and in stubs. +# n, m, robot, symbolic that appear across many classes, and duplicate-citation +# warnings for references (e.g. Yoshikawa85) defined in both a narrative page +# and a docstring pulled in from two classes. +# +# Note: "duplicate object description" (autodoc members documented both +# inline and via a separate IK/stubs/ page) is a different warning class +# with no type/subtype tag at all, so it can't be suppressed here — it's +# fixed at the source instead, via :no-index: in +# _templates/autosummary/method.rst. suppress_warnings = [ "ref.python", # ambiguous cross-references (multiple targets for 'n', 'm', etc.) - "duplicate", # duplicate object descriptions (IKSolution, IKSolver defined twice) + "ref.citation", # duplicate citations (Yoshikawa85 etc in both arm_*.rst and docstrings) ] # -------- Options favicon -------------------------------------------------------# diff --git a/src/roboticstoolbox/blocks/arm.py b/src/roboticstoolbox/blocks/arm.py index 13a02d88c..9963b7d34 100644 --- a/src/roboticstoolbox/blocks/arm.py +++ b/src/roboticstoolbox/blocks/arm.py @@ -366,7 +366,6 @@ class ArmPlot(GraphicsBlock): - 0 - ndarray(N) - :math:`\mathit{q}`, joint configuration - * - Output Create a robot animation using the robot's ``plot`` method. diff --git a/src/roboticstoolbox/models/DH/Hyper.py b/src/roboticstoolbox/models/DH/Hyper.py index f96a3b8e5..1839b5f89 100644 --- a/src/roboticstoolbox/models/DH/Hyper.py +++ b/src/roboticstoolbox/models/DH/Hyper.py @@ -38,8 +38,8 @@ class Hyper(DHRobot): :References: - "A divide and conquer articulated-body algorithm for parallel O(log(n)) - calculation of rigid body dynamics, Part 2", - Int. J. Robotics Research, 18(9), pp 876-892. + calculation of rigid body dynamics, Part 2", + Int. J. Robotics Research, 18(9), pp 876-892. :seealso: :func:`Coil`, :func:`Ball` diff --git a/src/roboticstoolbox/models/DH/Hyper3d.py b/src/roboticstoolbox/models/DH/Hyper3d.py index 1c163cd7c..39605d433 100644 --- a/src/roboticstoolbox/models/DH/Hyper3d.py +++ b/src/roboticstoolbox/models/DH/Hyper3d.py @@ -38,8 +38,8 @@ class Hyper3d(DHRobot): :References: - "A divide and conquer articulated-body algorithm for parallel O(log(n)) - calculation of rigid body dynamics, Part 2", - Int. J. Robotics Research, 18(9), pp 876-892. + calculation of rigid body dynamics, Part 2", + Int. J. Robotics Research, 18(9), pp 876-892. :seealso: :func:`Coil`, :func:`Ball` diff --git a/src/roboticstoolbox/models/DH/Orion5.py b/src/roboticstoolbox/models/DH/Orion5.py index 37c09571c..114274d66 100755 --- a/src/roboticstoolbox/models/DH/Orion5.py +++ b/src/roboticstoolbox/models/DH/Orion5.py @@ -34,8 +34,8 @@ class Orion5(DHRobot): - Robot has only 4 DoF. :references: - - https://rawrobotics.com.au/orion5 - - https://drive.google.com/file/d/0B6_9_-ZgiRdTNkVqMEkxN2RSc2c/view + - ``https://rawrobotics.com.au/orion5`` + - ``https://drive.google.com/file/d/0B6_9_-ZgiRdTNkVqMEkxN2RSc2c/view`` .. codeauthor:: Aditya Dua .. codeauthor:: Peter Corke diff --git a/src/roboticstoolbox/models/ETS/Puma560.py b/src/roboticstoolbox/models/ETS/Puma560.py index ddb3ca462..5183b488b 100644 --- a/src/roboticstoolbox/models/ETS/Puma560.py +++ b/src/roboticstoolbox/models/ETS/Puma560.py @@ -15,10 +15,11 @@ class Puma560(Robot): - The model has different joint offset conventions compared to ``DH.Puma560()``. For this robot: - - Zero joint angles ``qz`` is the vertical configuration, - corresponding to ``qr`` with ``DH.Puma560()`` - - ``qbent`` is the bent configuration, corresponding to - ``qz`` with ``DH.Puma560()`` + + - Zero joint angles ``qz`` is the vertical configuration, + corresponding to ``qr`` with ``DH.Puma560()`` + - ``qbent`` is the bent configuration, corresponding to + ``qz`` with ``DH.Puma560()`` :references: - "A Simple and Systematic Approach to Assigning Denavit–Hartenberg diff --git a/src/roboticstoolbox/robot/BaseRobot.py b/src/roboticstoolbox/robot/BaseRobot.py index af76376c0..01ff118da 100644 --- a/src/roboticstoolbox/robot/BaseRobot.py +++ b/src/roboticstoolbox/robot/BaseRobot.py @@ -413,13 +413,16 @@ def __getitem__(self, i: int | str) -> LinkType: :param i: link number or name :returns: i'th link or named link + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> print(robot[1]) # print the 2nd link - >>> print([link.a for link in robot]) # print all the a_j values + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> print(robot[1]) # print the 2nd link + >>> print([link.a for link in robot]) # print all the a_j values .. rubric:: Notes @@ -573,12 +576,15 @@ def n(self) -> int: Number of joints :returns: Number of joints + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.n + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.n See Also -------- @@ -598,13 +604,15 @@ def nlinks(self): static links :returns: Number of links + Examples -------- .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.nlinks + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.nlinks See Also -------- @@ -624,13 +632,15 @@ def nbranches(self) -> int: zero children :returns: number of branches in the robot's kinematic tree + Examples -------- .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.ETS.Panda() - >>> robot.nbranches + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.ETS.Panda() + >>> robot.nbranches See Also -------- @@ -711,13 +721,15 @@ def hasdynamics(self): :returns: Robot has dynamic parameters :returns: At least one link has associated dynamic parameters. + Examples -------- .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.hasdynamics: + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.hasdynamics """ @@ -731,13 +743,15 @@ def hasgeometry(self): At least one link has associated mesh to describe its shape. :returns: Robot has geometry model + Examples -------- .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.hasgeometry + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.hasgeometry See Also -------- @@ -754,13 +768,15 @@ def hascollision(self): :returns: Robot has collision model :returns: At least one link has associated collision model. + Examples -------- .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.hascollision + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.hascollision See Also -------- @@ -883,12 +899,15 @@ def qlim(self) -> NDArray: :param qlim: An array of joints limits (2, n) :raises ValueError: unset limits for a prismatic joint :returns: Array of joint limit values + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.qlim + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.qlim """ @@ -941,14 +960,17 @@ def structure(self) -> str: joint, and ``P`` for a prismatic joint. :returns: joint configuration string + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.structure - >>> stanford = rtb.models.DH.Stanford() - >>> stanford.structure + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.structure + >>> stanford = rtb.models.DH.Stanford() + >>> stanford.structure .. rubric:: Notes @@ -974,14 +996,17 @@ def prismaticjoints(self) -> list[bool]: Revolute joints as bool array :returns: array of joint type, True if prismatic + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.prismaticjoints - >>> stanford = rtb.models.DH.Stanford() - >>> stanford.prismaticjoints + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.prismaticjoints + >>> stanford = rtb.models.DH.Stanford() + >>> stanford.prismaticjoints .. rubric:: Notes @@ -1003,14 +1028,17 @@ def revolutejoints(self) -> list[bool]: Revolute joints as bool array :returns: array of joint type, True if revolute + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.revolutejoints - >>> stanford = rtb.models.DH.Stanford() - >>> stanford.revolutejoints + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.revolutejoints + >>> stanford = rtb.models.DH.Stanford() + >>> stanford.revolutejoints .. rubric:: Notes @@ -1388,9 +1416,12 @@ def ets( :raises ValueError: a link does not belong to this ERobot :raises TypeError: a bad link argument :returns: elementary transform sequence + Examples -------- + .. runblock:: pycon + >>> import roboticstoolbox as rtb >>> panda = rtb.models.ETS.Panda() >>> panda.ets() @@ -1514,13 +1545,16 @@ def todegrees(self, q) -> NDArray: :returns: or prismatic joints, ie. prismatic joint values are not converted. :returns: If ``q`` is a matrix, with one column per joint, the conversion is :returns: performed columnwise. + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> from math import pi - >>> stanford = rtb.models.DH.Stanford() - >>> stanford.todegrees([pi/4, pi/8, 2, -pi/4, pi/6, pi/3]) + + >>> import roboticstoolbox as rtb + >>> from math import pi + >>> stanford = rtb.models.DH.Stanford() + >>> stanford.todegrees([pi/4, pi/8, 2, -pi/4, pi/6, pi/3]) """ @@ -1548,12 +1582,15 @@ def toradians(self, q) -> NDArray: :param q: The joint configuration of the robot :returns: a vector of joint coordinates in radians and metres + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> stanford = rtb.models.DH.Stanford() - >>> stanford.toradians([10, 20, 2, 30, 40, 50]) + + >>> import roboticstoolbox as rtb + >>> stanford = rtb.models.DH.Stanford() + >>> stanford.toradians([10, 20, 2, 30, 40, 50]) """ @@ -1573,14 +1610,17 @@ def isrevolute(self, j) -> bool: Check if joint is revolute :returns: True if revolute + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.revolutejoints - >>> stanford = rtb.models.DH.Stanford() - >>> stanford.isrevolute(1) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.revolutejoints + >>> stanford = rtb.models.DH.Stanford() + >>> stanford.isrevolute(1) See Also -------- @@ -1595,14 +1635,17 @@ def isprismatic(self, j) -> bool: Check if joint is prismatic :returns: True if prismatic + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.prismaticjoints - >>> stanford = rtb.models.DH.Stanford() - >>> stanford.isprismatic(1) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.prismaticjoints + >>> stanford = rtb.models.DH.Stanford() + >>> stanford.isprismatic(1) See Also -------- @@ -1652,14 +1695,17 @@ def addconfiguration_attr(self, name: str, q: ArrayLike, unit: str = "rad"): :param name: Name of the joint configuration :param q: Joint configuration + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.addconfiguration_attr("mypos", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) - >>> robot.mypos - >>> robot.configs["mypos"] + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.addconfiguration_attr("mypos", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) + >>> robot.mypos + >>> robot.configs["mypos"] .. rubric:: Notes @@ -1688,13 +1734,16 @@ def addconfiguration(self, name: str, q: NDArray): :param name: Name of the joint configuration :param q: Joint configuration + Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.addconfiguration_attr("mypos", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) - >>> robot.configs["mypos"] + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.addconfiguration_attr("mypos", [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]) + >>> robot.configs["mypos"] See Also -------- @@ -1748,11 +1797,12 @@ def random_q(self): The value for each joint is uniform randomly distributed between the limits set for the robot. - Note - ---- + .. rubric:: Notes + The joint limit for all joints must be set. :returns: Random joint configuration :rtype: ndarray(n) + See Also -------- :func:`Robot.qlim` @@ -1770,8 +1820,10 @@ def hierarchy(self) -> None: Pretty print the robot link hierachy :returns: Pretty print of the robot model + Examples -------- + Makes a robot and prints the heirachy .. runblock:: pycon @@ -2069,19 +2121,14 @@ def teach( ``robot.teach()`` as above except the robot's stored value of ``q`` is used. - q - The joint configuration of the robot (Optional, + :param q: The joint configuration of the robot (Optional, if not supplied will use the stored q values). - block - Block operation of the code and keep the figure open - limits - Custom view limits for the plot. If not supplied will + :param block: Block operation of the code and keep the figure open + :param limits: Custom view limits for the plot. If not supplied will autoscale, [x1, x2, y1, y2, z1, z2] - vellipse - (Plot Option) Plot the velocity ellipse at the + :param vellipse: (Plot Option) Plot the velocity ellipse at the end-effector (this option is for 'pyplot' only) - fellipse - (Plot Option) Plot the force ellipse at the + :param fellipse: (Plot Option) Plot the force ellipse at the end-effector (this option is for 'pyplot' only) :returns: A reference to the PyPlot object which controls the matplotlib figure @@ -2089,14 +2136,15 @@ def teach( .. rubric:: Notes - Program execution is blocked until the teach window is - dismissed. If ``block=False`` the method is non-blocking but - you need to poll the window manager to ensure that the window - remains responsive. + dismissed. If ``block=False`` the method is non-blocking but + you need to poll the window manager to ensure that the window + remains responsive. - The slider limits are derived from the joint limit properties. - If not set then: - - For revolute joints they are assumed to be [-pi, +pi] - - For prismatic joint they are assumed unknown and an error - occurs. + If not set then: + + - For revolute joints they are assumed to be [-pi, +pi] + - For prismatic joint they are assumed unknown and an error + occurs. """ @@ -2175,8 +2223,10 @@ def showgraph(self, display_graph: bool = True, **kwargs) -> str | None: :param etsbox: Put the link ETS in a box, otherwise an edge label :param jtype: Arrowhead to node indicates revolute or prismatic type :param static: Show static joints in blue and bold + Examples -------- + >>> import roboticstoolbox as rtb >>> panda = rtb.models.URDF.Panda() >>> panda.showgraph() @@ -2221,35 +2271,39 @@ def dotfile( """ Write a link transform graph as a GraphViz dot file - The file can be processed using dot: + The file can be processed using dot:: + % dot -Tpng -o out.png dotfile.dot The nodes are: - - Base is shown as a grey square. This is the world frame origin, - but can be changed using the ``base`` attribute of the robot. - - Link frames are indicated by circles - - ETS transforms are indicated by rounded boxes + + - Base is shown as a grey square. This is the world frame origin, + but can be changed using the ``base`` attribute of the robot. + - Link frames are indicated by circles + - ETS transforms are indicated by rounded boxes The edges are: - - an arrow if `jtype` is False or the joint is fixed - - an arrow with a round head if `jtype` is True and the joint is - revolute - - an arrow with a box head if `jtype` is True and the joint is - prismatic + + - an arrow if `jtype` is False or the joint is fixed + - an arrow with a round head if `jtype` is True and the joint is + revolute + - an arrow with a box head if `jtype` is True and the joint is + prismatic Edge labels or nodes in blue have a fixed transformation to the preceding link. - Note - ---- + .. rubric:: Notes + If ``filename`` is a file object then the file will *not* - be closed after the GraphViz model is written. + be closed after the GraphViz model is written. :param file: Name of file to write to :param etsbox: Put the link ETS in a box, otherwise an edge label :param ets: Display the full ets with "full" or a brief version with "brief" :param jtype: Arrowhead to node indicates revolute or prismatic type :param static: Show static joints in blue and bold + See Also -------- :func:`showgraph` diff --git a/src/roboticstoolbox/robot/DHLink.py b/src/roboticstoolbox/robot/DHLink.py index 74ed198ea..d847d485c 100644 --- a/src/roboticstoolbox/robot/DHLink.py +++ b/src/roboticstoolbox/robot/DHLink.py @@ -724,6 +724,7 @@ class RevoluteDH(DHLink): The link transform is :math:`\underbrace{\mathbf{T}_{rz}(q_i)}_{\mbox{variable}} \cdot \mathbf{T}_{tz}(d_i) \cdot \mathbf{T}_{tx}(a_i) \cdot \mathbf{T}_{rx}(\alpha_i)` where :math:`q_i` is the joint variable. + :references: - Robotics, Vision & Control in Python, 3e, P. Corke, Springer 2023, Chap 7. @@ -779,12 +780,14 @@ class PrismaticDH(DHLink): :type Tc: ndarray(2,) :param G: dynamic - gear ratio :type G: float + A subclass of the DHLink class for a prismatic joint that holds all information related to a robot link such as kinematics parameters, rigid-body inertial parameters, motor and transmission parameters. The link transform is :math:`\mathbf{T}_{rz}(\theta_i) \cdot \underbrace{\mathbf{T}_{tz}(q_i)}_{\mbox{variable}} \cdot \mathbf{T}_{tx}(a_i) \cdot \mathbf{T}_{rx}(\alpha_i)` where :math:`q_i` is the joint variable. + :references: - Robotics, Vision & Control in Python, 3e, P. Corke, Springer 2023, Chap 7. diff --git a/src/roboticstoolbox/robot/DHRobot.py b/src/roboticstoolbox/robot/DHRobot.py index 43cc17d33..1ccacc679 100644 --- a/src/roboticstoolbox/robot/DHRobot.py +++ b/src/roboticstoolbox/robot/DHRobot.py @@ -1047,7 +1047,7 @@ def fkine_all(self, q=None, old=True): .. note:: - Old behaviour is to return a list of ``n`` frames {1} to {n}, but - if ``old=False`` it returns ``n``+1 frames {0} to {n}, ie. it + if ``old=False`` it returns ``n`` + 1 frames {0} to {n}, ie. it includes the base frame. - The robot's base or tool transform, if present, are incorporated into the result. @@ -1512,7 +1512,7 @@ def rne_python( .. note:: - This is a pure Python implementation and slower than the .rne() - which is written in C. + which is written in C. - This version supports symbolic model parameters - Verified against MATLAB code @@ -1970,15 +1970,15 @@ def ik_lm_chan( This method can be used for robots with any number of degrees of freedom. The return value ``sol`` is a tuple with elements: - ============ ========== =============================================== + ============== ========== =============================================== Element Type Description - ============ ========== =============================================== + ============== ========== =============================================== ``q`` ndarray(n) joint coordinates in units of radians or metres ``success`` int whether a solution was found ``iterations`` int total number of iterations ``searches`` int total number of searches ``residual`` float final value of cost function - ============ ========== =============================================== + ============== ========== =============================================== If ``success == 0`` the ``q`` values will be valid numbers, but the solution will be in error. The amount of error is indicated by @@ -2021,8 +2021,7 @@ def ik_lm_chan( .. note:: - - See `Toolbox kinematics wiki page - `_ + - See `Toolbox kinematics wiki page `_ - Implements a Levenberg-Marquadt variable-damping solver. - The tolerance is computed on the norm of the error between current and desired tool pose. This norm is computed from @@ -2075,15 +2074,15 @@ def ik_lm_wampler( This method can be used for robots with any number of degrees of freedom. The return value ``sol`` is a tuple with elements: - ============ ========== =============================================== + ============== ========== =============================================== Element Type Description - ============ ========== =============================================== + ============== ========== =============================================== ``q`` ndarray(n) joint coordinates in units of radians or metres ``success`` int whether a solution was found ``iterations`` int total number of iterations ``searches`` int total number of searches ``residual`` float final value of cost function - ============ ========== =============================================== + ============== ========== =============================================== If ``success == 0`` the ``q`` values will be valid numbers, but the solution will be in error. The amount of error is indicated by @@ -2126,8 +2125,7 @@ def ik_lm_wampler( .. note:: - - See `Toolbox kinematics wiki page - `_ + - See `Toolbox kinematics wiki page `_ - Implements a Levenberg-Marquadt variable-damping solver. - The tolerance is computed on the norm of the error between current and desired tool pose. This norm is computed from @@ -2180,15 +2178,15 @@ def ik_lm_sugihara( This method can be used for robots with any number of degrees of freedom. The return value ``sol`` is a tuple with elements: - ============ ========== =============================================== + ============== ========== =============================================== Element Type Description - ============ ========== =============================================== + ============== ========== =============================================== ``q`` ndarray(n) joint coordinates in units of radians or metres ``success`` int whether a solution was found ``iterations`` int total number of iterations ``searches`` int total number of searches ``residual`` float final value of cost function - ============ ========== =============================================== + ============== ========== =============================================== If ``success == 0`` the ``q`` values will be valid numbers, but the solution will be in error. The amount of error is indicated by @@ -2231,8 +2229,7 @@ def ik_lm_sugihara( .. note:: - - See `Toolbox kinematics wiki page - `_ + - See `Toolbox kinematics wiki page `_ - Implements a Levenberg-Marquadt variable-damping solver. - The tolerance is computed on the norm of the error between current and desired tool pose. This norm is computed from @@ -2286,15 +2283,15 @@ def ik_nr( This method can be used for robots with any number of degrees of freedom. The return value ``sol`` is a tuple with elements: - ============ ========== =============================================== + ============== ========== =============================================== Element Type Description - ============ ========== =============================================== + ============== ========== =============================================== ``q`` ndarray(n) joint coordinates in units of radians or metres ``success`` int whether a solution was found ``iterations`` int total number of iterations ``searches`` int total number of searches ``residual`` float final value of cost function - ============ ========== =============================================== + ============== ========== =============================================== If ``success == 0`` the ``q`` values will be valid numbers, but the solution will be in error. The amount of error is indicated by @@ -2337,8 +2334,7 @@ def ik_nr( .. note:: - - See `Toolbox kinematics wiki page - `_ + - See `Toolbox kinematics wiki page `_ - Implements a Levenberg-Marquadt variable-damping solver. - The tolerance is computed on the norm of the error between current and desired tool pose. This norm is computed from @@ -2394,15 +2390,15 @@ def ik_gn( This method can be used for robots with any number of degrees of freedom. The return value ``sol`` is a tuple with elements: - ============ ========== =============================================== + ============== ========== =============================================== Element Type Description - ============ ========== =============================================== + ============== ========== =============================================== ``q`` ndarray(n) joint coordinates in units of radians or metres ``success`` int whether a solution was found ``iterations`` int total number of iterations ``searches`` int total number of searches ``residual`` float final value of cost function - ============ ========== =============================================== + ============== ========== =============================================== If ``success == 0`` the ``q`` values will be valid numbers, but the solution will be in error. The amount of error is indicated by @@ -2445,8 +2441,7 @@ def ik_gn( .. note:: - - See `Toolbox kinematics wiki page - `_ + - See `Toolbox kinematics wiki page `_ - Implements a Levenberg-Marquadt variable-damping solver. - The tolerance is computed on the norm of the error between current and desired tool pose. This norm is computed from diff --git a/src/roboticstoolbox/robot/Dynamics.py b/src/roboticstoolbox/robot/Dynamics.py index f3b7bbba9..5d77422ab 100644 --- a/src/roboticstoolbox/robot/Dynamics.py +++ b/src/roboticstoolbox/robot/Dynamics.py @@ -39,10 +39,12 @@ def dynamics(self: RobotProto): Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.dynamics() + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.dynamics() """ unicode = rtb_get_param("unicode") @@ -417,10 +419,12 @@ def accel(self: RobotProto, q, qd, torque, gravity=None): Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.accel(puma.qz, 0.5 * np.ones(6), np.zeros(6)) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.accel(puma.qz, 0.5 * np.ones(6), np.zeros(6)) .. rubric:: Notes @@ -667,10 +671,12 @@ def inertia(self: RobotProto, q: NDArray) -> NDArray: Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.inertia(puma.qz) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.inertia(puma.qz) .. rubric:: Notes @@ -734,10 +740,12 @@ def coriolis(self: RobotProto, q, qd): Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.coriolis(puma.qz, 0.5 * np.ones((6,))) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.coriolis(puma.qz, 0.5 * np.ones((6,))) .. rubric:: Notes @@ -828,10 +836,12 @@ def gravload( Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.gravload(puma.qz) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.gravload(puma.qz) """ @@ -898,10 +908,12 @@ def inertia_x( Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.inertia_x(puma.qn) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.inertia_x(puma.qn) .. rubric:: Notes @@ -1016,10 +1028,12 @@ def coriolis_x( Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.coriolis_x(puma.qn, 0.5 * np.ones((6,))) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.coriolis_x(puma.qn, 0.5 * np.ones((6,))) .. rubric:: Notes @@ -1135,10 +1149,12 @@ def gravload_x( Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.gravload_x(puma.qn) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.gravload_x(puma.qn) .. rubric:: Notes @@ -1321,10 +1337,12 @@ def itorque(self: RobotProto, q, qdd): Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> puma = rtb.models.DH.Puma560() - >>> puma.itorque(puma.qz, 0.5 * np.ones((6,))) + + >>> import roboticstoolbox as rtb + >>> puma = rtb.models.DH.Puma560() + >>> puma.itorque(puma.qz, 0.5 * np.ones((6,))) .. rubric:: Notes diff --git a/src/roboticstoolbox/robot/ET.py b/src/roboticstoolbox/robot/ET.py index d191b40ec..363c7442e 100644 --- a/src/roboticstoolbox/robot/ET.py +++ b/src/roboticstoolbox/robot/ET.py @@ -266,14 +266,16 @@ def eta(self) -> float | Sym | None: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx(1) - >>> e.eta - >>> e = ET.Rx(90, 'deg') - >>> e.eta - >>> e = ET.ty() - >>> e.eta + + >>> from roboticstoolbox import ET + >>> e = ET.tx(1) + >>> e.eta + >>> e = ET.Rx(90, 'deg') + >>> e.eta + >>> e = ET.ty() + >>> e.eta .. rubric:: Notes @@ -312,12 +314,14 @@ def axis(self) -> str: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx(1) - >>> e.axis - >>> e = ET.Rx(90, 'deg') - >>> e.axis + + >>> from roboticstoolbox import ET + >>> e = ET.tx(1) + >>> e.axis + >>> e = ET.Rx(90, 'deg') + >>> e.axis """ return self._axis @@ -332,12 +336,14 @@ def isjoint(self) -> bool: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx(1) - >>> e.isjoint - >>> e = ET.tx() - >>> e.isjoint + + >>> from roboticstoolbox import ET + >>> e = ET.tx(1) + >>> e.isjoint + >>> e = ET.tx() + >>> e.isjoint """ return self._joint @@ -355,12 +361,14 @@ def isflip(self) -> bool: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx() - >>> e.T(1) - >>> eflip = ET.tx(flip=True) - >>> eflip.T(1) + + >>> from roboticstoolbox import ET + >>> e = ET.tx() + >>> e.T(1) + >>> eflip = ET.tx(flip=True) + >>> eflip.T(1) """ @@ -376,12 +384,14 @@ def isrotation(self) -> bool: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx(1) - >>> e.isrotation - >>> e = ET.rx() - >>> e.isrotation + + >>> from roboticstoolbox import ET + >>> e = ET.tx(1) + >>> e.isrotation + >>> e = ET.rx() + >>> e.isrotation """ @@ -397,12 +407,14 @@ def istranslation(self) -> bool: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx(1) - >>> e.istranslation - >>> e = ET.rx() - >>> e.istranslation + + >>> from roboticstoolbox import ET + >>> e = ET.tx(1) + >>> e.istranslation + >>> e = ET.rx() + >>> e.istranslation """ @@ -431,13 +443,15 @@ def jindex(self) -> int | None: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx() - >>> print(e) - >>> e = ET.tx(j=3) - >>> print(e) - >>> print(e.jindex) + + >>> from roboticstoolbox import ET + >>> e = ET.tx() + >>> print(e) + >>> e = ET.tx(j=3) + >>> print(e) + >>> print(e.jindex) """ @@ -482,11 +496,13 @@ def inv(self): Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz(2.5) - >>> print(e) - >>> print(e.inv()) + + >>> from roboticstoolbox import ET + >>> e = ET.Rz(2.5) + >>> print(e) + >>> print(e.inv()) """ # noqa @@ -514,12 +530,14 @@ def A(self, q: float | Sym = 0.0) -> ndarray: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tx(1) - >>> e.A() - >>> e = ET.tx() - >>> e.A(0.7) + + >>> from roboticstoolbox import ET + >>> e = ET.tx(1) + >>> e.A() + >>> e = ET.tx() + >>> e.A(0.7) """ try: @@ -930,12 +948,14 @@ def A(self, q: float | Sym = 0.0) -> ndarray: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET2 - >>> e = ET2.tx(1) - >>> e.A() - >>> e = ET2.tx() - >>> e.A(0.7) + + >>> from roboticstoolbox import ET2 + >>> e = ET2.tx(1) + >>> e.A() + >>> e = ET2.tx() + >>> e.A(0.7) """ diff --git a/src/roboticstoolbox/robot/ETS.py b/src/roboticstoolbox/robot/ETS.py index 548029894..8dd4b0a25 100644 --- a/src/roboticstoolbox/robot/ETS.py +++ b/src/roboticstoolbox/robot/ETS.py @@ -134,18 +134,20 @@ def __str__(self, q: str | None = None): Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz() * ET.tx(1) * ET.Rz() - >>> print(e[:2]) - >>> print(e) - >>> print(e.__str__("")) - >>> print(e.__str__("θ{0}")) # numbering from 0 - >>> print(e.__str__("θ{1}")) # numbering from 1 - >>> # explicit joint indices - >>> e = ET.Rz(jindex=3) * ET.tx(1) * ET.Rz(jindex=4) - >>> print(e) - >>> print(e.__str__("θ{0}")) + + >>> from roboticstoolbox import ET + >>> e = ET.Rz() * ET.tx(1) * ET.Rz() + >>> print(e[:2]) + >>> print(e) + >>> print(e.__str__("")) + >>> print(e.__str__("θ{0}")) # numbering from 0 + >>> print(e.__str__("θ{1}")) # numbering from 1 + >>> # explicit joint indices + >>> e = ET.Rz(jindex=3) * ET.tx(1) * ET.Rz(jindex=4) + >>> print(e) + >>> print(e.__str__("θ{0}")) Angular parameters are converted to degrees, except if they are symbolic. @@ -231,6 +233,7 @@ def _repr_pretty_(self, p, cycle): Examples -------- + In [1]: e Out [1]: R(q0) ⊕ tx(1) ⊕ R(q1) ⊕ tx(1) @@ -247,10 +250,12 @@ def joint_idx(self) -> list[int]: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) - >>> e.joint_idx() + + >>> from roboticstoolbox import ET + >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) + >>> e.joint_idx() """ @@ -265,10 +270,12 @@ def joints(self) -> list[ET]: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) - >>> e.joints() + + >>> from roboticstoolbox import ET + >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) + >>> e.joints() """ @@ -283,10 +290,12 @@ def jindex_set(self) -> set[int]: # Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz(jindex=1) * ET.tx(jindex=2) * ET.Rz(jindex=1) * ET.tx(1) - >>> e.jointset() + + >>> from roboticstoolbox import ET + >>> e = ET.Rz(jindex=1) * ET.tx(jindex=2) * ET.Rz(jindex=1) * ET.tx(1) + >>> e.jointset() """ @@ -302,10 +311,12 @@ def jindices(self) -> NDArray: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz(jindex=1) * ET.tx(jindex=2) * ET.Rz(jindex=1) * ET.tx(1) - >>> e.jointset() + + >>> from roboticstoolbox import ET + >>> e = ET.Rz(jindex=1) * ET.tx(jindex=2) * ET.Rz(jindex=1) * ET.tx(1) + >>> e.jointset() """ @@ -330,10 +341,12 @@ def qlim(self): Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot.qlim + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot.qlim """ @@ -384,10 +397,12 @@ def structure(self) -> str: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.tz() * ET.tx(1) * ET.Rz() * ET.tx(1) - >>> e.structure + + >>> from roboticstoolbox import ET + >>> e = ET.tz() * ET.tx(1) * ET.Rz() * ET.tx(1) + >>> e.structure """ @@ -407,10 +422,12 @@ def n(self) -> int: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rx() * ET.tx(1) * ET.tz() - >>> e.n + + >>> from roboticstoolbox import ET + >>> e = ET.Rx() * ET.tx(1) * ET.tz() + >>> e.n See Also -------- @@ -432,10 +449,12 @@ def m(self) -> int: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rx() * ET.tx(1) * ET.tz() - >>> e.m + + >>> from roboticstoolbox import ET + >>> e = ET.Rx() * ET.tx(1) * ET.tz() + >>> e.m """ @@ -508,11 +527,13 @@ def inv(self: T) -> T: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz(jindex=2) * ET.tx(1) * ET.Rx(jindex=3,flip=True) * ET.tx(1) - >>> print(e) - >>> print(e.inv()) + + >>> from roboticstoolbox import ET + >>> e = ET.Rz(jindex=2) * ET.tx(1) * ET.Rx(jindex=3,flip=True) * ET.tx(1) + >>> print(e) + >>> print(e.inv()) .. rubric:: Notes @@ -547,12 +568,14 @@ def __getitem__(self, i): Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) - >>> e[0] - >>> e[1] - >>> e[1:3] + + >>> from roboticstoolbox import ET + >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) + >>> e[0] + >>> e[1] + >>> e[1:3] """ return self._data[i] # can be [2] or slice, eg. [3:5] @@ -601,12 +624,14 @@ def random_q(self, i: int = 1) -> NDArray: Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.Panda() - >>> ets = robot.ets() - >>> q = ets.random_q() - >>> q + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.Panda() + >>> ets = robot.ets() + >>> q = ets.random_q() + >>> q """ @@ -643,21 +668,23 @@ class ETS(BaseETS): Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ETS, ET - >>> e = ET.Rz(0.3) # a single ET, rotation about z - >>> ets1 = ETS(e) - >>> len(ets1) - >>> ets2 = ET.Rz(0.3) * ET.tx(2) # an ETS - >>> len(ets2) # of length 2 - >>> ets2[1] # an ET sliced from the ETS + + >>> from roboticstoolbox import ETS, ET + >>> e = ET.Rz(0.3) # a single ET, rotation about z + >>> ets1 = ETS(e) + >>> len(ets1) + >>> ets2 = ET.Rz(0.3) * ET.tx(2) # an ETS + >>> len(ets2) # of length 2 + >>> ets2[1] # an ET sliced from the ETS .. rubric:: References - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part I: - Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022). + Kinematics, Velocity, and Applications." arXiv preprint arXiv:2207.01796 (2022). - J. Haviland, and P. Corke. "Manipulator Differential Kinematics Part II: - Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022). + Acceleration and Advanced Applications." arXiv preprint arXiv:2207.01794 (2022). See Also @@ -760,12 +787,14 @@ def compile(self) -> "ETS": Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.ETS.Panda() - >>> ets = robot.ets() - >>> ets - >>> ets.compile() + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.ETS.Panda() + >>> ets = robot.ets() + >>> ets + >>> ets.compile() See Also -------- @@ -812,12 +841,14 @@ def insert( # type: ignore[override] Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import ET - >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) - >>> f = ET.Ry() - >>> e.insert(2, f) - >>> e + + >>> from roboticstoolbox import ET + >>> e = ET.Rz() * ET.tx(1) * ET.Rz() * ET.tx(1) + >>> f = ET.Ry() + >>> e.insert(2, f) + >>> e """ @@ -854,6 +885,7 @@ def fkine( Examples -------- + The following example makes a ``panda`` robot object, gets the ets, and solves for the forward kinematics at the listed configuration. @@ -915,10 +947,12 @@ def eval( Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> panda = rtb.models.Panda().ets() - >>> panda.eval([0, -0.3, 0, -2.2, 0, 2, 0.7854]) + + >>> import roboticstoolbox as rtb + >>> panda = rtb.models.Panda().ets() + >>> panda.eval([0, -0.3, 0, -2.2, 0, 2, 0.7854]) .. rubric:: Notes @@ -956,6 +990,7 @@ def jacob0( Examples -------- + The following example makes a ``Puma560`` robot object, and solves for the base-frame Jacobian at the zero joint angle configuration @@ -1002,6 +1037,7 @@ def jacobe( Examples -------- + The following example makes a ``Puma560`` robot object, and solves for the end-effector frame Jacobian at the zero joint angle configuration @@ -1075,6 +1111,7 @@ def hessian0( Examples -------- + The following example makes a ``Panda`` robot object, and solves for the base frame Hessian at the given joint angle configuration @@ -1143,6 +1180,7 @@ def hessiane( Examples -------- + The following example makes a ``Panda`` robot object, and solves for the end-effector frame Hessian at the given joint angle configuration @@ -1191,6 +1229,7 @@ def jacob0_analytical( Examples -------- + Makes a robot object and computes the analytic Jacobian for the given joint configuration @@ -1391,6 +1430,7 @@ def partial_fkine0(self, q: ArrayLike, n: int) -> NDArray: Examples -------- + The following example makes a ``Panda`` robot object, and solves for the base-effector frame 4th derivative of the forward kinematics at the given joint angle configuration @@ -1648,6 +1688,7 @@ def ik_LM( Examples -------- + The following example gets the ``ets`` of a ``panda`` robot object, makes a goal pose ``Tep``, and then solves for the joint coordinates which result in the pose ``Tep`` using the `ikine_LM` method. @@ -1737,6 +1778,7 @@ def ik_NR( Examples -------- + The following example gets the ``ets`` of a ``panda`` robot object, makes a goal pose ``Tep``, and then solves for the joint coordinates which result in the pose ``Tep`` using the `ik_NR` method. @@ -1840,6 +1882,7 @@ def ik_GN( Examples -------- + The following example gets the ``ets`` of a ``panda`` robot object, makes a goal pose ``Tep``, and then solves for the joint coordinates which result in the pose ``Tep`` using the `ikine_GN` method. @@ -1982,6 +2025,7 @@ def ikine_LM( Examples -------- + The following example gets the ``ets`` of a ``panda`` robot object, makes a goal pose ``Tep``, and then solves for the joint coordinates which result in the pose ``Tep`` using the `ikine_LM` method. @@ -2090,6 +2134,7 @@ def ikine_NR( Examples -------- + The following example gets the ``ets`` of a ``panda`` robot object, makes a goal pose ``Tep``, and then solves for the joint coordinates which result in the pose ``Tep`` using the `ikine_NR` method. @@ -2208,6 +2253,7 @@ def ikine_GN( Examples -------- + The following example gets the ``ets`` of a ``panda`` robot object, makes a goal pose ``Tep``, and then solves for the joint coordinates which result in the pose ``Tep`` using the `ikine_GN` method. @@ -2366,6 +2412,7 @@ def ikine_QP( Examples -------- + The following example gets the ``ets`` of a ``panda`` robot object, makes a goal pose ``Tep``, and then solves for the joint coordinates which result in the pose ``Tep`` using the `ikine_QP` method. diff --git a/src/roboticstoolbox/robot/Link.py b/src/roboticstoolbox/robot/Link.py index 83625e0f6..2736fb586 100644 --- a/src/roboticstoolbox/robot/Link.py +++ b/src/roboticstoolbox/robot/Link.py @@ -273,12 +273,14 @@ def Ts(self) -> NDArray | None: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import Link, ET - >>> link = Link( ET.tz(0.333) * ET.Rx(90, 'deg') * ET.Rz() ) - >>> link.Ts - >>> link = Link( ET.Rz() ) - >>> link.Ts + + >>> from roboticstoolbox import Link, ET + >>> link = Link( ET.tz(0.333) * ET.Rx(90, 'deg') * ET.Rz() ) + >>> link.Ts + >>> link = Link( ET.Rz() ) + >>> link.Ts """ return self._Ts @@ -449,10 +451,12 @@ def v(self) -> BaseET | None: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import Link, ET, ETS - >>> link = Link( ET.tz(0.333) * ET.Rx(90, 'deg') * ET.Rz() ) - >>> print(link.v) + + >>> from roboticstoolbox import Link, ET, ETS + >>> link = Link( ET.tz(0.333) * ET.Rx(90, 'deg') * ET.Rz() ) + >>> print(link.v) """ return self._v @@ -594,10 +598,12 @@ def hasdynamics(self) -> bool: Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> robot[1].hasdynamics + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> robot[1].hasdynamics """ return self._hasdynamics @@ -943,11 +949,13 @@ def isjoint(self) -> bool: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import models - >>> robot = models.URDF.Panda() - >>> robot[1].isjoint # link with joint - >>> robot[8].isjoint # static link + + >>> from roboticstoolbox import models + >>> robot = models.URDF.Panda() + >>> robot[1].isjoint # link with joint + >>> robot[8].isjoint # static link """ @@ -1015,11 +1023,13 @@ def parent(self) -> Self | None: Examples -------- + .. runblock:: pycon - >>> from roboticstoolbox import models - >>> robot = models.URDF.Panda() - >>> robot[0].parent # base link has no parent - >>> robot[1].parent # second link's parent + + >>> from roboticstoolbox import models + >>> robot = models.URDF.Panda() + >>> robot[0].parent # base link has no parent + >>> robot[1].parent # second link's parent """ @@ -1156,11 +1166,13 @@ def dyn(self, indent=0): Examples -------- + .. runblock:: pycon - >>> import roboticstoolbox as rtb - >>> robot = rtb.models.DH.Puma560() - >>> print(robot.links[2]) # kinematic parameters - >>> print(robot.links[2].dyn()) # dynamic parameters + + >>> import roboticstoolbox as rtb + >>> robot = rtb.models.DH.Puma560() + >>> print(robot.links[2]) # kinematic parameters + >>> print(robot.links[2].dyn()) # dynamic parameters :seealso: :func:`~dyntable` """ diff --git a/src/roboticstoolbox/robot/Robot.py b/src/roboticstoolbox/robot/Robot.py index 05dc0ac8c..507c0f3f8 100644 --- a/src/roboticstoolbox/robot/Robot.py +++ b/src/roboticstoolbox/robot/Robot.py @@ -761,17 +761,15 @@ def manipulability( Various measures are supported: - | Measure | Description | - | ``"yoshikawa"`` | Volume of the velocity ellipsoid, *distance* | - | | from singularity [Yoshikawa85]_ | - | ``"invcondition"``| Inverse condition number of Jacobian, isotropy | - | | of the velocity ellipsoid [Klein87]_ | - | ``"minsingular"`` | Minimum singular value of the Jacobian, | - | | *distance* from singularity [Klein87]_ | - | ``"asada"`` | Isotropy of the task-space acceleration | - | | ellipsoid which is a function of the Cartesian | - | | inertia matrix which depends on the inertial | - | | parameters [Asada83]_ | + - ``"yoshikawa"`` -- volume of the velocity ellipsoid, *distance* + from singularity [Yoshikawa85]_ + - ``"invcondition"`` -- inverse condition number of Jacobian, + isotropy of the velocity ellipsoid [Klein87]_ + - ``"minsingular"`` -- minimum singular value of the Jacobian, + *distance* from singularity [Klein87]_ + - ``"asada"`` -- isotropy of the task-space acceleration ellipsoid + which is a function of the Cartesian inertia matrix which depends + on the inertial parameters [Asada83]_ **Trajectory operation**: @@ -804,8 +802,8 @@ def manipulability( Kinematically Redundant Manipulators. Klein CA, Blaho BE. The International Journal of Robotics Research. 1987;6(2):72-83. doi:10.1177/027836498700600206 - - Robotics, Vision & Control, Chap 8, P. Corke, Springer 2011. + - Robotics, Vision & Control, Chap 8, P. Corke, Springer 2011. .. versionchanged:: 1.0.3 Removed 'both' option for axes, added a custom list option. diff --git a/src/roboticstoolbox/robot/RobotKinematics.py b/src/roboticstoolbox/robot/RobotKinematics.py index 760f14a80..504603467 100644 --- a/src/roboticstoolbox/robot/RobotKinematics.py +++ b/src/roboticstoolbox/robot/RobotKinematics.py @@ -695,15 +695,15 @@ def ik_NR( The return value ``sol`` is a tuple with elements: - ============ ========== =============================================== + ============== ========== =============================================== Element Type Description - ============ ========== =============================================== + ============== ========== =============================================== ``q`` ndarray(n) joint coordinates in units of radians or metres ``success`` int whether a solution was found ``iterations`` int total number of iterations ``searches`` int total number of searches ``residual`` float final value of cost function - ============ ========== =============================================== + ============== ========== =============================================== If ``success == 0`` the ``q`` values will be valid numbers, but the solution will be in error. The amount of error is indicated by @@ -809,15 +809,15 @@ def ik_GN( The return value ``sol`` is a tuple with elements: - ============ ========== =============================================== + ============== ========== =============================================== Element Type Description - ============ ========== =============================================== + ============== ========== =============================================== ``q`` ndarray(n) joint coordinates in units of radians or metres ``success`` int whether a solution was found ``iterations`` int total number of iterations ``searches`` int total number of searches ``residual`` float final value of cost function - ============ ========== =============================================== + ============== ========== =============================================== If ``success == 0`` the ``q`` values will be valid numbers, but the solution will be in error. The amount of error is indicated by diff --git a/src/roboticstoolbox/tools/trajectory.py b/src/roboticstoolbox/tools/trajectory.py index cc068b00b..c2161c9d9 100644 --- a/src/roboticstoolbox/tools/trajectory.py +++ b/src/roboticstoolbox/tools/trajectory.py @@ -256,7 +256,7 @@ def qplot(self, **kwargs): """ Plot multi-axis trajectory - :param **kwargs: optional arguments passed to ``qplot`` + :param \*\*kwargs: optional arguments passed to ``qplot`` Plots a multi-axis trajectory, held within the object, as position against time. @@ -816,9 +816,9 @@ def ctraj(T0, T1, t=None, s=None): .. note:: - In the second case ``s`` could be generated by a scalar trajectory - generator such as ``quintic`` or ``trapezoidal`` (default). + generator such as ``quintic`` or ``trapezoidal`` (default). - Orientation interpolation is performed using unit-quaternion - interpolation. + interpolation. :References: - Robotics, Vision & Control in Python, 3e, P. Corke, Springer 2023, Chap 3. diff --git a/tech-debt.md b/tech-debt.md index f6ad93c41..26b551181 100644 --- a/tech-debt.md +++ b/tech-debt.md @@ -683,3 +683,115 @@ handles the shadowing correctly on its own. Also worth a quick sweep for any other `sys.version_info`/Python-3.10-specific conditionals elsewhere in the codebase at that point, so 3.10 cleanup happens in one pass rather than piecemeal. + +--- + +## Repeated `:returns:`/`:param:` field markers in `robot/*.py` docstrings + +### Background + +While fixing the "Field list ends without a blank line" Sphinx build warnings +(2026-07-05 — a broken `Examples`/`.. runblock::` template pattern across +`BaseRobot.py`, `ETS.py`, `Link.py`, `ET.py`, `Dynamics.py`, likely from the +same refactor work already covered by the "ETS / fknm / frne refactor" entry +above), several docstrings turned up with **repeated field markers** that +don't actually trigger a Sphinx/docutils warning (repeated field names are +silently accepted, not flagged) but are still wrong: + +- `BaseRobot.hasdynamics`/`hascollision`: two consecutive `:returns:` lines + (only one is really shown depending on renderer; the two sentences should + be merged into one field). +- `BaseRobot.ets`: `:param :param start: ...` / `:param :param end: ...` — + a duplicated `:param` marker, plus a stray trailing colon after the + description. +- `BaseRobot.todegrees`: **six** consecutive `:returns:` lines that are + clearly meant to be one multi-line description. +- A `path`-returning method and a gripper/end-effector method with three + consecutive `:returns:` lines each. +- `BaseRobot` (one of the `q`-random methods, ~line 1804): + `:returns: Random joint configuration :rtype: ndarray(n)` — `:rtype:` + crammed onto the same line as `:returns:` instead of being its own field. + +Left alone deliberately (2026-07-05) since fixing the actual build warnings +was the scoped task — this is docstring *content* quality, not a build +break, and merging multi-sentence explanations correctly needs a human +read of intent rather than a mechanical script. + +### Proposed fix + +Sweep `robot/*.py` for `^\s*:returns:.*\n\s*:returns:` (and `:param:`) +regex hits and manually merge each into a single well-formed field. + +--- + +## `napoleon` + `sphinx_autodoc_typehints`: bare NumPy-style section headers conflict + +### Background + +While fixing Sphinx build warnings (2026-07-05), `BaseRobot.dotfile`'s +docstring kept reporting "Explicit markup ends without a blank line" at a +line that had nothing wrong with it by inspection. Root-caused via an +isolated minimal Sphinx build (bisecting the extension list): the +combination of `sphinx.ext.napoleon` + `sphinx_autodoc_typehints` conflicts +when a docstring mixes bare NumPy-style section headers (`Note` underlined +with `----`, recognized and reformatted by Napoleon's heuristics) with +explicit reST fields (`:param:`, `:returns:`) and typehint injection from +`sphinx_autodoc_typehints`. Neither extension alone reproduces it. + +Confirmed napoleon is still genuinely needed, not just historical baggage: +`tools/urdf/utils.py` and `tools/urdf/urdf.py` have real NumPy-style +`Parameters`/`Returns` docstrings (looks vendored/adapted from an external +URDF library) that Napoleon actually converts. Everywhere else in the +codebase (confirmed via grep) writes explicit reST fields directly and +doesn't need Napoleon's conversion — but two `robot/BaseRobot.py` docstrings +had a stray bare `Note` header mixed into otherwise-reST content, which is +what triggered the conflict. Also found `napoleon_custom_sections = +["Synopsis"]` in `conf.py` was dead config — nothing in the codebase uses a +"Synopsis" section — removed. + +### Proposed fix + +Fixed the two known `Note` occurrences (now `.. rubric:: Notes`, matching +the pattern already used elsewhere in the same file). If this warning +resurfaces elsewhere, the fix is the same: replace the bare NumPy-style +header with the equivalent explicit directive (`.. rubric:: Notes`, +`.. warning::`, etc.) rather than relying on Napoleon to recognize it — +don't touch `tools/urdf/*.py`, which needs Napoleon's real NumPy-style +parsing left alone. + +--- + +## Codebase is mid-migration from NumPy-style to reST-style docstrings + +### Background + +The `Note` header bug above is one instance of a broader pattern: this +codebase has clearly moved from NumPy-style docstrings (bare underlined +section headers — `Parameters`/`Returns`/`Notes`/`See Also`, each followed +by a `----` underline) to explicit reST fields (`:param:`, `:returns:`, +`:seealso:`), but the migration is incomplete. Checked 2026-07-05: the +`:seealso:` field is used **202** times across the codebase — clearly the +intended, current convention — but the vestigial bare `See Also` +NumPy-style heading still appears **48** times across 6 files. Each one is +a latent instance of the same Napoleon-heuristic-recognition risk that hit +`BaseRobot.dotfile`/`random_q` (see above) — most haven't triggered a +visible warning yet only because they haven't hit the right combination of +circumstances, not because they're actually safe. + +The same is likely true for other NumPy-style section names (`Notes`, +`Warning`, `Raises`, `Attributes`, `Examples`) wherever they appear as a +bare underlined heading instead of the reST equivalent — this file only +audited `Note`/`See Also` specifically because those are what broke the +build this session. `tools/urdf/utils.py` and `tools/urdf/urdf.py` are the +one confirmed exception: genuine vendored/adapted NumPy-style docstrings +that Napoleon is correctly converting — don't touch those. + +### Proposed fix + +A future pass should grep for each NumPy-recognized bare section header +(`^\s*(Notes?|Warnings?|Parameters|Returns|Raises|Yields|Attributes|Methods|References|See Also|Examples)\s*$` +followed by a matching-length `-+` underline) outside `tools/urdf/`, and +convert each to its reST equivalent (`:seealso:`, `.. rubric:: Notes`, +`.. warning::`, etc.) — same treatment as this session's `Note` fixes, +just systematically rather than one-off as each breaks the build. +parsing left alone.