Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
874acce
fix: pre-commit is a development tool, not a runtime dependency
petercorke Oct 13, 2026
de57cf3
fix: make the OA and conjugation docstrings raw strings
petercorke Oct 13, 2026
972e0dd
Merge remote-tracking branch 'upstream/master' into fix/pre-commit-no…
petercorke Oct 13, 2026
d9eab2e
docs(base): fix the angle_std formula and the angle_mean and angle_wr…
petercorke Oct 14, 2026
6e02bb8
fix(base): angle_std returned nan for identical angles
petercorke Oct 14, 2026
2e48076
fix: Twist2.ad raised ValueError for every twist; add Twist2.Ad and S…
petercorke Oct 15, 2026
162b05d
docs: fix the math block in the tr2adjoint and tr2adjoint2 docstrings
petercorke Oct 15, 2026
102c91d
fix(graphics): ignore sign of scalar in expand_dims, document reverse…
petercorke Oct 15, 2026
37b5886
feat(base): set_window_title, set the title of a figure's window if i…
petercorke Oct 15, 2026
5fb7852
Defer optional SymPy loading until symbolic use
RoboX2020 Oct 15, 2026
2690a37
Check symbolic scalar types dynamically
RoboX2020 Oct 15, 2026
81c4ee4
Defer SymPy loading in transforms2d.py
RoboX2020 Oct 15, 2026
82b2dd6
Defer SymPy loading in transformsNd.py
RoboX2020 Oct 15, 2026
5df29aa
Defer SymPy loading in vectors.py
RoboX2020 Oct 15, 2026
514a615
Test optional SymPy loading in fresh interpreters
RoboX2020 Oct 15, 2026
69c8289
Merge branch 'rai-opensource:master' into perf/lazy-sympy
RoboX2020 Oct 15, 2026
fb39007
fix(graphics): axes_logic uses an empty current figure instead of cre…
petercorke Oct 17, 2026
54fb3c8
Normalize the axis in UnitQuaternion.AngVec
wpyaoxueros Oct 17, 2026
6940542
Use the scalar zero tolerance for angle-axis quaternions
wpyaoxueros Oct 19, 2026
67b2807
fix(base): angvec2r rejects a zero axis for a non-zero angle
petercorke Oct 19, 2026
d7e69da
Broadcast scalar integration steps over twist sequences
antonsoo Oct 12, 2026
1e23a46
Normalize twist poles by squared angular magnitude
antonsoo Oct 12, 2026
df14141
Merge PR #248: Broadcast scalar integration steps over twist sequences
taughz Oct 20, 2026
d2c9cfc
Merge PR #249: Keep twist poles invariant under angular speed scaling
taughz Oct 20, 2026
33262e8
Merge PR #250: fix: pre-commit is a development tool, not a runtime d…
taughz Oct 20, 2026
400f14b
Merge PR #254: fix(base): angle_std returned nan for identical angles
taughz Oct 20, 2026
914806d
Merge PR #266: Normalize the axis in UnitQuaternion.AngVec
taughz Oct 20, 2026
04f5b3e
Merge PR #251: fix: make the OA and conjugation docstrings raw strings
taughz Oct 20, 2026
53e0219
Merge PR #253: docs(base): fix the angle_std formula and the angle_me…
taughz Oct 20, 2026
c873b8b
Merge PR #259: fix: Twist2.ad raised ValueError for every twist; add …
taughz Oct 20, 2026
40a928a
Merge PR #260: docs: fix the math block in the tr2adjoint and tr2adjo…
taughz Oct 20, 2026
0d05c75
Merge PR #262: fix(graphics): ignore sign of scalar in expand_dims, d…
taughz Oct 20, 2026
bf8f675
Merge PR #263: feat(base): set_window_title, set the title of a figur…
taughz Oct 20, 2026
832acf9
Merge PR #264: Defer optional SymPy imports until symbolic operations…
taughz Oct 20, 2026
765fa44
Merge PR #265: fix(graphics): axes_logic uses an empty current figure…
taughz Oct 20, 2026
d35ee27
Merge PR #267: angvec2r: reject a zero axis for a non-zero angle
taughz Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ git clone https://github.com/rai-opensource/spatialmath-python.git
cd spatialmath-python
pip install -e .
# Optional: if you would like to contribute and commit code changes to the repository,
# install the development tools (including pre-commit) and set up the git hooks
# pip install -e .[dev]
# pre-commit install
```

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ dependencies = [
"matplotlib",
"ansitable",
"typing_extensions>=4.0.0",
"pre-commit",
]

[project.urls]
Expand All @@ -52,6 +51,7 @@ dependencies = [
[project.optional-dependencies]

dev = [
"pre-commit",
"sympy",
"pytest",
"pytest-timeout",
Expand Down
2 changes: 2 additions & 0 deletions spatialmath/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"plot_cone": "graphics",
"plot_cuboid": "graphics",
"axes_logic": "graphics",
"set_window_title": "graphics",
"plotvol2": "graphics",
"plotvol3": "graphics",
"expand_dims": "graphics",
Expand Down Expand Up @@ -391,6 +392,7 @@ def __getattr__(name):
"plot_cone",
"plot_cuboid",
"axes_logic",
"set_window_title",
"expand_dims",
"isnotebook",
# spatial.base.numeric
Expand Down
20 changes: 9 additions & 11 deletions spatialmath/base/argcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from collections.abc import Iterable

# from spatialmath.base import symbolic as sym # HACK
from spatialmath.base.symbolic import issymbol, symtype
from spatialmath.base.symbolic import issymbol

# valid scalar types
_scalartypes = (int, np.integer, float, np.floating) + symtype
_scalartypes = (int, np.integer, float, np.floating)

# from typing import Union, List, Tuple, Any, Optional, Type, Callable
# from numpy.typing import DTypeLike
Expand Down Expand Up @@ -47,7 +47,9 @@ def isscalar(x: Any) -> bool:
>>> isscalar([1])

"""
return isinstance(x, _scalartypes)
return isinstance(x, _scalartypes) or (
not isinstance(x, (list, tuple)) and issymbol(x)
)


def isinteger(x: Any) -> bool:
Expand Down Expand Up @@ -386,7 +388,7 @@ def getvector(
"""
dt = dtype

if isinstance(v, _scalartypes): # handle scalar case
if isscalar(v): # handle scalar case
v = [v] # type: ignore
if isinstance(v, (list, tuple)):
# list or tuple was passed in
Expand Down Expand Up @@ -501,7 +503,7 @@ def isvector(v: Any, dim: Optional[int] = None) -> bool:
if (
isinstance(v, (list, tuple))
and (dim is None or len(v) == dim)
and all(map(lambda x: isinstance(x, _scalartypes), v))
and all(map(isscalar, v))
):
return True # list or tuple

Expand All @@ -516,7 +518,7 @@ def isvector(v: Any, dim: Optional[int] = None) -> bool:
else:
return s == (dim,) or s == (1, dim) or s == (dim, 1)

if (dim is None or dim == 1) and isinstance(v, _scalartypes):
if (dim is None or dim == 1) and isscalar(v):
return True

return False
Expand Down Expand Up @@ -620,11 +622,7 @@ def isnumberlist(x: Any) -> bool:
>>> isnumberlist(np.r_[1,2])
"""

return (
isinstance(x, (list, tuple))
and len(x) > 0
and all(map(lambda x: isinstance(x, _scalartypes), x))
)
return isinstance(x, (list, tuple)) and len(x) > 0 and all(map(isscalar, x))


def isvectorlist(x: Any, n: int) -> bool:
Expand Down
50 changes: 49 additions & 1 deletion spatialmath/base/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,7 @@ def axes_logic(
"""

# print(f"new axis logic ({dimensions}D): ", end='')
use_current_figure = False
if ax is None:
# no axes passed in, find out what's happening
# need to be careful to not use gcf() or gca() since they
Expand All @@ -1597,6 +1598,10 @@ def axes_logic(
# print(f"ax has {_axes_dimensions(ax)} dimensions")
if _axes_dimensions(ax) == dimensions and not new:
return ax
elif not new:
# the current figure is empty, eg. from a bare plt.figure():
# put the axes in it rather than leave it empty and make another
use_current_figure = True
# otherwise it doesnt exist or dimension mismatch, create new axes
else:
# print("ax given", ax)
Expand All @@ -1607,7 +1612,8 @@ def axes_logic(
return ax
# print("mismatch in dimensions, create new axes")
# print("create new axes")
plt.figure()
if not use_current_figure:
plt.figure()
if dimensions == 2:
ax = plt.axes()
if autoscale:
Expand Down Expand Up @@ -1759,11 +1765,18 @@ def expand_dims(dim: ArrayLike = None, nd: int = 2) -> NDArray:
* A -> [-A, A, -A, A, -A, A]
* [A,B] -> [A, B, A, B, A, B]
* [A,B,C,D,E,F] -> [A, B, C, D, E, F]

A scalar is symmetric about zero, so its sign is ignored: ``-A`` is the
same as ``A``. For the other forms the values are returned as given and
no ordering check is made. If a minimum is greater than the
corresponding maximum, matplotlib will invert that axis when the limits
are applied, which is a convenient way to flip an axis direction.
"""
dim = smb.getvector(dim)

if nd == 2:
if len(dim) == 1:
dim = abs(dim)
return np.r_[-dim, dim, -dim, dim]
elif len(dim) == 2:
return np.r_[dim[0], dim[1], dim[0], dim[1]]
Expand All @@ -1773,6 +1786,7 @@ def expand_dims(dim: ArrayLike = None, nd: int = 2) -> NDArray:
raise ValueError("bad dimension specified")
elif nd == 3:
if len(dim) == 1:
dim = abs(dim)
return np.r_[-dim, dim, -dim, dim, -dim, dim]
elif len(dim) == 2:
return np.r_[dim[0], dim[1], dim[0], dim[1], dim[0], dim[1]]
Expand All @@ -1783,6 +1797,37 @@ def expand_dims(dim: ArrayLike = None, nd: int = 2) -> NDArray:
else:
raise ValueError("nd is 2 or 3")

def set_window_title(fig: plt.Figure, title: str) -> bool:
"""
Set the title of the window containing a figure, if it has one

:param fig: the figure
:type fig: Figure
:param title: the window title
:type title: str
:return: True if the title was set
:rtype: bool

A figure has a window only if it is managed by pyplot and the backend
provides one. A figure on a headless backend such as ``Agg``, or one
created directly with ``matplotlib.figure.Figure``, does not, and then
nothing is done and False is returned.

Example::

>>> from spatialmath.base import plotvol2, set_window_title
>>> ax = plotvol2(5)
>>> set_window_title(ax.figure, "my plot")

.. note:: The title of the window is not the title of the axes, see
:meth:`matplotlib.axes.Axes.set_title`.
"""
manager = getattr(fig.canvas, "manager", None)
if manager is not None and hasattr(manager, "set_window_title"):
manager.set_window_title(title)
return True
return False

def isnotebook() -> bool:
"""
Determine if code is being run from a Jupyter notebook
Expand Down Expand Up @@ -1849,3 +1894,6 @@ def plot_cone(*args, **kwargs) -> None:

def plot_cylinder(*args, **kwargs) -> None:
raise NotImplementedError("Matplotlib is not installed: pip install matplotlib")

def set_window_title(*args, **kwargs) -> None:
raise NotImplementedError("Matplotlib is not installed: pip install matplotlib")
Loading
Loading