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
7 changes: 6 additions & 1 deletion src/roboticstoolbox/ets/ETS.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,12 @@ def fixjoint(self, j: int, value: float, unit: str = "rad") -> ETS:
:returns: ETS with one fewer joint
:raises ValueError: for an invalid joint position or angular unit
"""
if not isinstance(j, (int, np.integer)) or not 0 <= j < self.n:
# bool subclasses int, but must not select joint 0 or 1.
if (
isinstance(j, bool)
or not isinstance(j, (int, np.integer))
or not 0 <= j < self.n
):
raise ValueError(f"joint index {j} out of range for {self.n} joints")
if unit not in ("rad", "deg"):
raise ValueError("unit must be 'rad' or 'deg'")
Expand Down
7 changes: 6 additions & 1 deletion src/roboticstoolbox/ets/ETS2.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,12 @@ def fixjoint(self, j: int, value: float, unit: str = "rad") -> ETS2:
:returns: ETS2 with one fewer joint
:raises ValueError: for an invalid joint position or angular unit
"""
if not isinstance(j, (int, np.integer)) or not 0 <= j < self.n:
# bool subclasses int, but must not select joint 0 or 1.
if (
isinstance(j, bool)
or not isinstance(j, (int, np.integer))
or not 0 <= j < self.n
):
raise ValueError(f"joint index {j} out of range for {self.n} joints")
if unit not in ("rad", "deg"):
raise ValueError("unit must be 'rad' or 'deg'")
Expand Down
17 changes: 17 additions & 0 deletions tests/test_ETS.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,23 @@ def test_fixjoint_rotation_in_degrees(self):
ets.fixjoint(0, 90, unit="grad")
nt.assert_allclose(ets.eval(q), expected)

def test_fixjoint_boolean_and_integer_indices(self):
ets = Rz() * tx()
q = np.array([0.2, 0.3])
expected = ets.eval(q)

# Both 0 and 1 are valid positions, so rejection must be based on type.
for index in (False, True, np.bool_(False), np.bool_(True)):
with self.subTest(index=index), self.assertRaises(ValueError):
ets.fixjoint(index, 0.2)

for index in (0, 1, np.int64(0), np.int64(1)):
with self.subTest(index=index):
fixed = ets.fixjoint(index, q[index])
nt.assert_allclose(fixed.eval(np.delete(q, index)), expected)
self.assertEqual(ets.n, 2)
nt.assert_allclose(ets.eval(q), expected)

def test_fixjoint_single_joint_and_bad_indices(self):
ets = rtb.ETS(Rz())
fixed = ets.fixjoint(0, 0.3)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_ETS2.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,23 @@ def test_fixjoint_rotation_in_degrees(self):
ets.fixjoint(0, 90, unit="grad")
nt.assert_allclose(ets.eval(q), expected)

def test_fixjoint_boolean_and_integer_indices(self):
ets = R() * tx()
q = np.array([0.2, 0.3])
expected = ets.eval(q)

# Both 0 and 1 are valid positions, so rejection must be based on type.
for index in (False, True, np.bool_(False), np.bool_(True)):
with self.subTest(index=index), self.assertRaises(ValueError):
ets.fixjoint(index, 0.2)

for index in (0, 1, np.int64(0), np.int64(1)):
with self.subTest(index=index):
fixed = ets.fixjoint(index, q[index])
nt.assert_allclose(fixed.eval(np.delete(q, index)), expected)
self.assertEqual(ets.n, 2)
nt.assert_allclose(ets.eval(q), expected)

def test_fixjoint_single_joint_and_bad_indices(self):
ets = rtb.ETS2(R())
fixed = ets.fixjoint(0, 0.3)
Expand Down
Loading