From 4841a580a0714b69bfbf6d3e675e712dfa7be081 Mon Sep 17 00:00:00 2001 From: daoshi1593 <2023557967@qq.com> Date: Sun, 18 Oct 2026 23:48:40 +0800 Subject: [PATCH] fix(ets): reject boolean joint indices in fixjoint --- src/roboticstoolbox/ets/ETS.py | 7 ++++++- src/roboticstoolbox/ets/ETS2.py | 7 ++++++- tests/test_ETS.py | 17 +++++++++++++++++ tests/test_ETS2.py | 17 +++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/roboticstoolbox/ets/ETS.py b/src/roboticstoolbox/ets/ETS.py index f4daeb32c..b13d080fe 100644 --- a/src/roboticstoolbox/ets/ETS.py +++ b/src/roboticstoolbox/ets/ETS.py @@ -174,7 +174,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'") diff --git a/src/roboticstoolbox/ets/ETS2.py b/src/roboticstoolbox/ets/ETS2.py index 781fda0d1..6d73c41f4 100644 --- a/src/roboticstoolbox/ets/ETS2.py +++ b/src/roboticstoolbox/ets/ETS2.py @@ -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'") diff --git a/tests/test_ETS.py b/tests/test_ETS.py index baf4acc60..a91328ff8 100644 --- a/tests/test_ETS.py +++ b/tests/test_ETS.py @@ -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) diff --git a/tests/test_ETS2.py b/tests/test_ETS2.py index 8747fa139..63be6f47f 100644 --- a/tests/test_ETS2.py +++ b/tests/test_ETS2.py @@ -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)