Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions src/roboticstoolbox/bin/rtbtool.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,7 @@

# set default backend for Robot.plot
if args.swift:
# an attribute that shadows the class method, see findings
Robot.default_backend = "swift" # type: ignore[method-assign,assignment]
Robot.set_default_backend("swift")

Check warning on line 324 in src/roboticstoolbox/bin/rtbtool.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/roboticstoolbox/bin/rtbtool.py#L324

'Robot' may be undefined, or defined from star imports: roboticstoolbox, spatialgeometry, spatialmath, spatialmath.base (F405)

# set matrix printing mode for spatialmath
SE3._ansimatrix = args.ansi # type: ignore[name-defined] # from the star import
Expand Down
8 changes: 3 additions & 5 deletions src/roboticstoolbox/models/URDF/URDFRobot.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,11 +363,9 @@ def _links_from_urdf(urdf: URDF) -> Any:
elinks.append(elink)
elinkdict[link.name] = elink

try:
# geometry has no setter, so this raises AttributeError and is ignored
elink.geometry = [v.geometry.ob for v in link.visuals]
except AttributeError:
pass
# a visual without a shape, or with a mesh that cannot be found, is
# rejected when the URDF is parsed, so every visual here has an `ob`
elink.geometry = [v.geometry.ob for v in link.visuals]

if link.collisions:
shapes = []
Expand Down
28 changes: 26 additions & 2 deletions src/roboticstoolbox/robot/BaseRobot.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ class BaseRobot(SceneNode, DynamicsMixin, RobotPlottingMPLMixin, ABC, Generic[Li
# set when the links are processed in the constructor
_base_link: LinkType

#: default graphical backend for every robot of this class, used when the
#: robot has not been given its own, see :meth:`set_default_backend`
_class_default_backend: Any = None

def __init__(
self,
links: list[LinkType],
Expand Down Expand Up @@ -823,19 +827,39 @@ def default_backend(self) -> Any:
Get default graphical backend

- ``robot.default_backend`` Get the default graphical backend, used when
no explicit backend is passed to ``Robot.plot``.
no explicit backend is passed to ``Robot.plot``. This is the value set
for the robot, otherwise the value set for all robots by
:meth:`set_default_backend`, otherwise ``None``.
- ``robot.default_backend = ...`` Set the default graphical backend, used when
no explicit backend is passed to ``Robot.plot``. The default set here will
be overridden if the particular ``Robot`` subclass cannot support it.

:returns: backend name
"""
return self._default_backend
if self._default_backend is not None:
return self._default_backend
return self._class_default_backend

@default_backend.setter
def default_backend(self, be: Any) -> None:
self._default_backend = be

@classmethod
def set_default_backend(cls, be: Any) -> None:
"""
Set the default graphical backend for all robots

:param be: backend name, for example ``"swift"``, or ``None`` to clear

``Robot.set_default_backend("swift")`` sets the backend used by
:meth:`plot` for every robot, existing or created later, that has not
been given one of its own by ``robot.default_backend = ...``. The robot's
own value takes precedence.

:seealso: :attr:`default_backend`
"""
cls._class_default_backend = be

@property
def gravity(self) -> NDArray:
"""
Expand Down
45 changes: 45 additions & 0 deletions tests/test_default_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Robot.default_backend, per robot, and the class-level default used by rtbtool"""

import inspect

import pytest

import roboticstoolbox as rtb
from roboticstoolbox import Robot


@pytest.fixture
def restore_class_default():
yield
Robot.set_default_backend(None)


def test_class_default_reaches_every_robot(restore_class_default) -> None:
before = rtb.models.DH.Puma560()
assert before.default_backend is None

Check warning on line 19 in tests/test_default_backend.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_default_backend.py#L19

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Robot.set_default_backend("swift")

after = rtb.models.DH.Panda()
assert before.default_backend == "swift"

Check warning on line 24 in tests/test_default_backend.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_default_backend.py#L24

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert after.default_backend == "swift"

Check warning on line 25 in tests/test_default_backend.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_default_backend.py#L25

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_instance_value_wins_over_the_class_default(restore_class_default) -> None:
Robot.set_default_backend("swift")
robot = rtb.models.DH.Puma560()
robot.default_backend = "pyplot"
assert robot.default_backend == "pyplot"

Check warning on line 32 in tests/test_default_backend.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_default_backend.py#L32

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
assert rtb.models.DH.Panda().default_backend == "swift"

Check warning on line 33 in tests/test_default_backend.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_default_backend.py#L33

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_default_backend_remains_a_property(restore_class_default) -> None:
# rtbtool used to replace it with a string on the class
Robot.set_default_backend("swift")
assert isinstance(inspect.getattr_static(Robot, "default_backend"), property)

Check warning on line 39 in tests/test_default_backend.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_default_backend.py#L39

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


def test_class_default_can_be_cleared(restore_class_default) -> None:
Robot.set_default_backend("swift")
Robot.set_default_backend(None)
assert rtb.models.DH.Puma560().default_backend is None

Check warning on line 45 in tests/test_default_backend.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_default_backend.py#L45

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
43 changes: 43 additions & 0 deletions tests/test_urdf_visual_geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Visual geometry of a URDF robot reaches the links, or fails at parse time"""

import pytest

from roboticstoolbox.models.URDF.URDFRobot import _links_from_urdf
from roboticstoolbox.tools.urdf import URDF, URDFError

_LINK = """<robot name="r"><link name="base">
<inertial><mass value="1"/>
<inertia ixx="1" ixy="0" ixz="0" iyy="1" iyz="0" izz="1"/></inertial>
{visuals}
<collision><geometry><box size="1 1 1"/></geometry></collision>
</link></robot>"""


def test_visual_shapes_are_set_on_the_link() -> None:
urdf = URDF.loadstr(
_LINK.format(
visuals=(
'<visual><geometry><box size="1 1 1"/></geometry></visual>'
'<visual><geometry><sphere radius="0.1"/></geometry></visual>'
)
),
None,
)
links, _ = _links_from_urdf(urdf)
assert len(links[0].geometry.scene_children) == 2

Check warning on line 27 in tests/test_urdf_visual_geometry.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_urdf_visual_geometry.py#L27

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.


@pytest.mark.parametrize(
"visual",
[
"<visual><geometry/></visual>",
"<visual/>",
"<visual><geometry><pyramid/></geometry></visual>",
'<visual><geometry><mesh filename="package://nope/missing.stl"/>'
"</geometry></visual>",
],
)
def test_a_visual_without_a_shape_is_rejected_when_parsed(visual: str) -> None:
# so that _links_from_urdf never meets a visual without an `ob`
with pytest.raises(URDFError):
URDF.loadstr(_LINK.format(visuals=visual), None)
Loading