roboticstoolbox-python 1.4.3__py3-none-any.whl → 1.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -202,7 +202,7 @@ def make_banner():
202
202
  from spatialmath import *
203
203
  from spatialmath.base import *
204
204
  from spatialmath.base import sym
205
- from roboticstoolbox import *"
205
+ from roboticstoolbox import *
206
206
 
207
207
  # useful variables
208
208
  from math import pi
@@ -51,8 +51,52 @@ static void _IK_loop(
51
51
 
52
52
  if (*E < tol)
53
53
  {
54
- for (int i = 0; i < ets->n; i++)
55
- q(i) = std::fmod(q(i) + PI, PI_x2) - PI;
54
+ // Preserve translations and coordinates already within their limits.
55
+ // Other revolute coordinates may move only by whole turns, so the
56
+ // converged end-effector pose is unchanged (see IKSolver._normalise_q).
57
+ int j = 0;
58
+ for (int i = 0; i < ets->m; i++)
59
+ {
60
+ ET *et = ets->ets[i];
61
+ if (!et->isjoint)
62
+ continue;
63
+
64
+ double lower = ets->qlim_l[j];
65
+ double upper = ets->qlim_h[j];
66
+ if (et->axis < 3 && !(lower <= q(j) && q(j) <= upper))
67
+ {
68
+ // Avoid rounding a principal angle across a nearby limit.
69
+ double angle = q(j);
70
+ if (!(angle >= -PI && angle < PI))
71
+ {
72
+ // Unlike Python's %, fmod can return a negative remainder.
73
+ angle = std::fmod(angle + PI, PI_x2);
74
+ if (angle < 0)
75
+ angle += PI_x2;
76
+ angle -= PI;
77
+ }
78
+ if (angle < lower)
79
+ angle += PI_x2 * std::ceil((lower - angle) / PI_x2);
80
+ else if (angle > upper)
81
+ angle -= PI_x2 * std::ceil((angle - upper) / PI_x2);
82
+ if (!(lower <= angle && angle <= upper))
83
+ {
84
+ // Recover rounded endpoints only if whole turns
85
+ // reconstruct the original coordinate exactly.
86
+ for (double bound : {lower, upper})
87
+ {
88
+ double turns = std::round((q(j) - bound) / PI_x2);
89
+ if (turns != 0 && bound + turns * PI_x2 == q(j))
90
+ {
91
+ angle = bound;
92
+ break;
93
+ }
94
+ }
95
+ }
96
+ q(j) = angle;
97
+ }
98
+ j++;
99
+ }
56
100
  *solution = reject_jl ? _check_lim(ets, q) : 1;
57
101
  break;
58
102
  }
@@ -315,4 +359,4 @@ extern "C"
315
359
  // return q;
316
360
  }
317
361
 
318
- } /* extern "C" */
362
+ } /* extern "C" */
@@ -27,6 +27,11 @@ class UR10(DHRobot):
27
27
 
28
28
  .. note::
29
29
  - SI units are used.
30
+ - The last link's inertia tensor has :math:`I_{xx} = 0` -- that's not
31
+ a bug in this model, it's what the manufacturer's own reference
32
+ (below) publishes. It makes the robot's inertia matrix near-singular
33
+ (``det(robot.inertia(q)) ~ 2e-6``), so dynamics work that needs a
34
+ well-conditioned inertia matrix should account for this.
30
35
 
31
36
  :References:
32
37
 
@@ -47,6 +47,9 @@ class Fetch(URDFRobot):
47
47
  from a URDF file. The model describes its kinematic and graphical
48
48
  characteristics.
49
49
 
50
+ The model is loaded via the `robot_descriptions
51
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
52
+
50
53
  .. runblock:: pycon
51
54
 
52
55
  >>> import roboticstoolbox as rtb
@@ -13,6 +13,9 @@ class Frankie(URDFRobot):
13
13
  from a URDF file. The model describes its kinematic and graphical
14
14
  characteristics.
15
15
 
16
+ The model is loaded via the `robot_descriptions
17
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
18
+
16
19
  .. runblock:: pycon
17
20
 
18
21
  >>> import roboticstoolbox as rtb
@@ -23,8 +26,14 @@ class Frankie(URDFRobot):
23
26
 
24
27
  - qz, zero joint angle configuration, 'L' shaped configuration
25
28
  - qr, vertical 'READY' configuration
26
- - qs, arm is stretched out in the x-direction
27
- - qn, arm is at a nominal non-singular configuration
29
+
30
+ Loaded via the `robot_descriptions
31
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package
32
+ (real inertial data, plain-mesh collision geometry), unlike
33
+ :class:`~roboticstoolbox.models.URDF.Panda.Panda`'s bundled default. See
34
+ the wiki's `Panda models
35
+ <https://github.com/petercorke/robotics-toolbox-python/wiki/Panda-models>`_
36
+ page for why the two differ.
28
37
 
29
38
  .. codeauthor:: Jesse Haviland
30
39
  .. sectionauthor:: Peter Corke
@@ -27,8 +27,16 @@ class FrankieOmni(Robot):
27
27
 
28
28
  - qz, zero joint angle configuration, 'L' shaped configuration
29
29
  - qr, vertical 'READY' configuration
30
- - qs, arm is stretched out in the x-direction
31
- - qn, arm is at a nominal non-singular configuration
30
+
31
+ The arm is loaded from the same bundled ``qut_frankie_description`` xacro
32
+ as :class:`~roboticstoolbox.models.URDF.Panda.Panda`'s default (no
33
+ inertial data), spliced by hand onto a separately-loaded
34
+ ``clearpath_ridgeback_description`` mobile base -- it has no
35
+ ``use_robot_descriptions``-style option, since the base+arm splice is
36
+ done manually rather than through :class:`URDFRobot`'s own loader. See
37
+ the wiki's `Panda models
38
+ <https://github.com/petercorke/robotics-toolbox-python/wiki/Panda-models>`_
39
+ page for the full picture.
32
40
 
33
41
  .. codeauthor:: Jesse Haviland
34
42
  .. sectionauthor:: Peter Corke
@@ -12,6 +12,9 @@ class Jaco(URDFRobot):
12
12
  from a URDF file. The model describes its kinematic and graphical
13
13
  characteristics.
14
14
 
15
+ The model is loaded via the `robot_descriptions
16
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
17
+
15
18
  .. runblock:: pycon
16
19
 
17
20
  >>> import roboticstoolbox as rtb
@@ -12,6 +12,9 @@ class PR2(URDFRobot):
12
12
  from a URDF file. The model describes its kinematic and graphical
13
13
  characteristics.
14
14
 
15
+ The model is loaded via the `robot_descriptions
16
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
17
+
15
18
  .. runblock:: pycon
16
19
 
17
20
  >>> import roboticstoolbox as rtb
@@ -23,20 +23,40 @@ class Panda(URDFRobot):
23
23
 
24
24
  - qz, zero joint angle configuration, 'L' shaped configuration
25
25
  - qr, vertical 'READY' configuration
26
- - qs, arm is stretched out in the x-direction
27
- - qn, arm is at a nominal non-singular configuration
26
+
27
+ :param use_robot_descriptions: if ``True``, load the Panda URDF from the
28
+ `robot_descriptions <https://github.com/robot-descriptions/robot_descriptions.py>`_
29
+ package instead of the toolbox's own bundled ``qut_frankie_description``
30
+ xacro (the default, ``False``). The bundled model has real collision
31
+ geometry (a hand-built capsule approximation) but no inertial
32
+ (mass/CoM/inertia) data at all -- ``rne()``/``inertia()``/``coriolis()``/
33
+ ``gravload()`` are all silently zero. The ``robot_descriptions`` model
34
+ has real inertial data, but its collision geometry is plain meshes,
35
+ which are roughly an order of magnitude slower to collision-check
36
+ against than the bundled model's capsules -- noticeable in a
37
+ real-time reactive-avoidance loop (see ``examples/neo.py``). See the
38
+ wiki's `Panda models <https://github.com/petercorke/robotics-toolbox-python/wiki/Panda-models>`_
39
+ page for the full comparison and rationale.
40
+ :type use_robot_descriptions: bool
28
41
 
29
42
  .. codeauthor:: Jesse Haviland
30
43
  .. sectionauthor:: Peter Corke
31
44
  """
32
45
 
33
- def __init__(self):
34
-
35
- super().__init__(
36
- "qut_frankie_description/robots/panda_arm_hand.urdf.xacro",
37
- manufacturer="Franka Emika",
38
- gripper_link_index=9,
39
- )
46
+ def __init__(self, use_robot_descriptions: bool = False):
47
+
48
+ if use_robot_descriptions:
49
+ super().__init__(
50
+ "panda",
51
+ manufacturer="Franka Emika",
52
+ gripper_link_index=9,
53
+ )
54
+ else:
55
+ super().__init__(
56
+ "qut_frankie_description/robots/panda_arm_hand.urdf.xacro",
57
+ manufacturer="Franka Emika",
58
+ gripper_link_index=9,
59
+ )
40
60
 
41
61
  self.grippers[0].tool = SE3(0, 0, 0.1034)
42
62
 
@@ -12,6 +12,9 @@ class UR10(URDFRobot):
12
12
  definition from a URDF file. The model describes its kinematic and
13
13
  graphical characteristics.
14
14
 
15
+ The model is loaded via the `robot_descriptions
16
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
17
+
15
18
  .. runblock:: pycon
16
19
 
17
20
  >>> import roboticstoolbox as rtb
@@ -12,6 +12,9 @@ class UR3(URDFRobot):
12
12
  definition from a URDF file. The model describes its kinematic and
13
13
  graphical characteristics.
14
14
 
15
+ The model is loaded via the `robot_descriptions
16
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
17
+
15
18
  .. runblock:: pycon
16
19
 
17
20
  >>> import roboticstoolbox as rtb
@@ -13,6 +13,9 @@ class UR5(URDFRobot):
13
13
  definition from a URDF file. The model describes its kinematic and
14
14
  graphical characteristics.
15
15
 
16
+ The model is loaded via the `robot_descriptions
17
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
18
+
16
19
  .. runblock:: pycon
17
20
 
18
21
  >>> import roboticstoolbox as rtb
@@ -30,7 +33,16 @@ class UR5(URDFRobot):
30
33
 
31
34
  def __init__(self):
32
35
 
33
- super().__init__("ur5", manufacturer="Universal Robotics", gripper_link_index=7)
36
+ # Name-based lookup, not a raw positional index: robot_descriptions
37
+ # 3.0.0 changed which upstream repo "ur5" resolves to, reordering
38
+ # links so index 7 silently pointed at a real arm joint instead of
39
+ # the tool attachment link (#578). tool0/ee_link/flange are stable
40
+ # names across the versions checked.
41
+ super().__init__(
42
+ "ur5",
43
+ manufacturer="Universal Robotics",
44
+ gripper_link_name=["tool0", "ee_link", "flange"],
45
+ )
34
46
 
35
47
  # for link in links:
36
48
  # print(link)
@@ -383,10 +383,16 @@ class URDFRobot(Robot):
383
383
  super().__init__(
384
384
  "ur5",
385
385
  manufacturer="Universal Robotics",
386
- gripper_link_index=7,
386
+ gripper_link_name=["tool0", "ee_link"],
387
387
  )
388
388
  self.qz = np.zeros(6)
389
389
  ...
390
+
391
+ ``gripper_link_name`` (a name, or a list of candidate names tried in
392
+ order) survives an upstream URDF reordering that would silently break a
393
+ raw positional ``gripper_link_index`` -- see #578. Prefer it for new
394
+ models; ``gripper_link_index`` remains for models where a stable,
395
+ well-known link name isn't available.
390
396
  """
391
397
 
392
398
  def __init__(
@@ -394,6 +400,7 @@ class URDFRobot(Robot):
394
400
  urdf_path: "str | Path",
395
401
  manufacturer: str = "",
396
402
  gripper_link_index: "int | None" = None,
403
+ gripper_link_name: "str | list[str] | None" = None,
397
404
  patch: "Callable[[str], str] | None" = None,
398
405
  extra_packages: "dict[str, str] | None" = None,
399
406
  **kwargs,
@@ -401,7 +408,26 @@ class URDFRobot(Robot):
401
408
  elinks, name, filepath = URDF_file(
402
409
  urdf_path, patch=patch, extra_packages=extra_packages
403
410
  )
404
- if gripper_link_index is not None:
411
+ if gripper_link_name is not None:
412
+ candidates = (
413
+ [gripper_link_name]
414
+ if isinstance(gripper_link_name, str)
415
+ else gripper_link_name
416
+ )
417
+ for candidate in candidates:
418
+ match = next(
419
+ (link for link in elinks if link.name == candidate), None
420
+ )
421
+ if match is not None:
422
+ kwargs["gripper_links"] = match
423
+ break
424
+ else:
425
+ raise ValueError(
426
+ f"none of the candidate gripper link names {candidates!r} "
427
+ f"were found in the parsed URDF for {name!r} (have: "
428
+ f"{[link.name for link in elinks]})"
429
+ )
430
+ elif gripper_link_index is not None:
405
431
  kwargs["gripper_links"] = elinks[gripper_link_index]
406
432
  super().__init__(elinks, name=name, manufacturer=manufacturer, **kwargs)
407
433
  self._urdf_filepath = str(filepath) if filepath is not None else ""
@@ -41,6 +41,9 @@ class Valkyrie(Robot):
41
41
  from a URDF file. The model describes its kinematic and graphical
42
42
  characteristics.
43
43
 
44
+ The model is loaded via the `robot_descriptions
45
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
46
+
44
47
  .. runblock:: pycon
45
48
 
46
49
  >>> import roboticstoolbox as rtb
@@ -15,6 +15,9 @@ class YuMi(Robot):
15
15
  from a URDF file. The model describes its kinematic and graphical
16
16
  characteristics.
17
17
 
18
+ The model is loaded via the `robot_descriptions
19
+ <https://github.com/robot-descriptions/robot_descriptions.py>`_ package.
20
+
18
21
  .. runblock:: pycon
19
22
 
20
23
  >>> import roboticstoolbox as rtb
@@ -1889,7 +1889,7 @@ class DHRobot(Robot):
1889
1889
  if base is not None:
1890
1890
  T = base.inv() * T
1891
1891
  if tool is not None:
1892
- T = tool.inv() * T
1892
+ T = T * tool.inv()
1893
1893
 
1894
1894
  # q = np.zeros((6,))
1895
1895
  solutions = []
@@ -308,13 +308,10 @@ class IKSolver(ABC):
308
308
  while True:
309
309
  # Check convergence for the current q before another update.
310
310
  if E < self.tol:
311
- # Wrap q to be within +- 180 deg
312
- # If your robot has larger than 180 deg range on a joint
313
- # this line should be modified in incorporate the extra range
314
- q = (q + np.pi) % (2 * np.pi) - np.pi
311
+ self._normalise_q(ets, q)
315
312
 
316
313
  # Check if we have violated joint limits
317
- jl_valid = self._check_jl(ets, q)
314
+ jl_valid = self._check_jl(ets, q[ets.jindices])
318
315
 
319
316
  if not jl_valid and self.joint_limits:
320
317
  # Abandon search and try again
@@ -450,6 +447,46 @@ class IKSolver(ABC):
450
447
 
451
448
  return q
452
449
 
450
+ def _normalise_q(self, ets: "rtb.ETS", q: np.ndarray) -> None:
451
+ """
452
+ Choose equivalent revolute coordinates without changing prismatic joints.
453
+
454
+ :param ets: The ETS defining the joint types and limits
455
+ :param q: Joint coordinates, indexed by joint jindex, modified in place
456
+ :returns: None
457
+
458
+ Preserve coordinates already within their limits, including revolute joints
459
+ outside the principal interval. Otherwise prefer the principal angle, shifted
460
+ by whole turns towards the limits if necessary. If no equivalent angle lies
461
+ within the limits, the subsequent joint-limit check will reject the solution.
462
+ """
463
+ qlim = ets.qlim
464
+ for i, joint in enumerate(ets.joints()):
465
+ j = joint.jindex
466
+ lower, upper = qlim[:, i]
467
+ if not joint.isrotation or lower <= q[j] <= upper:
468
+ continue
469
+
470
+ # Avoid rounding a principal angle across a nearby joint limit.
471
+ angle = q[j]
472
+ if not -np.pi <= angle < np.pi:
473
+ angle = (angle + np.pi) % (2 * np.pi) - np.pi
474
+ if angle < lower:
475
+ angle += 2 * np.pi * np.ceil((lower - angle) / (2 * np.pi))
476
+ elif angle > upper:
477
+ angle -= 2 * np.pi * np.ceil((angle - upper) / (2 * np.pi))
478
+ if not lower <= angle <= upper:
479
+ # Argument reduction can round a limit plus whole turns just
480
+ # outside the closed interval. Accept that endpoint only when
481
+ # it reconstructs the original coordinate exactly, without an
482
+ # epsilon that could admit an unrelated out-of-limit angle.
483
+ for bound in (lower, upper):
484
+ turns = np.rint((q[j] - bound) / (2 * np.pi))
485
+ if turns != 0 and bound + turns * (2 * np.pi) == q[j]:
486
+ angle = bound
487
+ break
488
+ q[j] = angle
489
+
453
490
  def _check_jl(self, ets: "rtb.ETS", q: np.ndarray) -> bool:
454
491
  """
455
492
  Checks if the joints are within their respective limits
@@ -1385,7 +1385,7 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1385
1385
 
1386
1386
  def joint_velocity_damper(
1387
1387
  self,
1388
- q=None,
1388
+ q: NDArray | None = None,
1389
1389
  ps: float = 0.05,
1390
1390
  pi: float = 0.1,
1391
1391
  n: int | None = None,
@@ -1413,16 +1413,16 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1413
1413
  n = self.n
1414
1414
 
1415
1415
  if q is None:
1416
- q = np.copy(self.q)
1416
+ q = self.q
1417
1417
 
1418
1418
  Ain = np.zeros((n, n))
1419
1419
  Bin = np.zeros(n)
1420
1420
 
1421
1421
  for i in range(n):
1422
- if self.q[i] - self.qlim[0, i] <= pi:
1422
+ if q[i] - self.qlim[0, i] <= pi:
1423
1423
  Bin[i] = -gain * (((self.qlim[0, i] - q[i]) + ps) / (pi - ps))
1424
1424
  Ain[i, i] = -1
1425
- if self.qlim[1, i] - self.q[i] <= pi:
1425
+ if self.qlim[1, i] - q[i] <= pi:
1426
1426
  Bin[i] = gain * ((self.qlim[1, i] - q[i]) - ps) / (pi - ps)
1427
1427
  Ain[i, i] = 1
1428
1428
 
@@ -1460,13 +1460,21 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1460
1460
 
1461
1461
  end, start, _ = self._get_limit_links(start=start, end=end)
1462
1462
 
1463
- links, n, _ = self.get_path(start=start, end=end)
1463
+ links, _, _ = self.get_path(start=start, end=end)
1464
1464
 
1465
1465
  q = np.array(q)
1466
1466
  j = 0
1467
1467
  Ain = None
1468
1468
  bin = None
1469
1469
 
1470
+ def get_link_joint_index(link: Link | None) -> int:
1471
+ curr = link
1472
+ while curr is not None:
1473
+ if curr.isjoint and curr.jindex is not None:
1474
+ return curr.jindex
1475
+ curr = curr.parent
1476
+ return 0
1477
+
1470
1478
  def indiv_calculation(link: Link, link_col: CollisionShape, q: NDArray):
1471
1479
  d, wTlp, wTcp = link_col.closest_point(shape, di)
1472
1480
 
@@ -1491,9 +1499,10 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1491
1499
  Je = self.jacobe(q, start=start, end=link, tool=link_col.T)
1492
1500
  n_dim = Je.shape[1]
1493
1501
  dp = norm_h @ shape.v
1494
- l_Ain = np.zeros((1, n))
1502
+ l_Ain = np.zeros((1, self.n))
1495
1503
 
1496
- l_Ain[0, :n_dim] = 1 * norm_h @ Je
1504
+ start_jidx = get_link_joint_index(start)
1505
+ l_Ain[0, start_jidx : start_jidx + n_dim] = 1 * norm_h @ Je
1497
1506
  l_bin = (xi * (d - ds) / (di - ds)) + dp
1498
1507
  else: # pragma nocover
1499
1508
  l_Ain = None
@@ -1507,9 +1516,6 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1507
1516
 
1508
1517
  if collision_list is None:
1509
1518
  col_list = link.collision
1510
-
1511
- for c in col_list:
1512
- pass
1513
1519
  else:
1514
1520
  col_list = [collision_list[j - 1]] # pragma nocover
1515
1521
 
@@ -1786,28 +1792,71 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1786
1792
 
1787
1793
  link_groups: list[list[int]] = []
1788
1794
 
1789
- # Group links together based on whether they are joints or not
1790
- # Static links are grouped with the first joint encountered
1791
- current_group = []
1795
+ # Group each joint together with every static (fixed) link rigidly
1796
+ # attached to -- and moving with -- its own output frame: found by
1797
+ # walking each static link's ancestry (via .parent, not flat
1798
+ # self.links order, so this is correct under branching too) up to
1799
+ # its nearest joint. A static link ends up in that joint's group
1800
+ # regardless of where it sits relative to the *next* joint in the
1801
+ # chain -- immediately after this joint, sandwiched anywhere before
1802
+ # the next one, or trailing after the very last joint with nothing
1803
+ # further downstream (e.g. a tool flange/mount link, such as URDF
1804
+ # Panda's panda_link8). A static link with no joint ancestor at all
1805
+ # (rigidly mounted on the immovable base) contributes no joint
1806
+ # torque and is dropped.
1807
+ #
1808
+ # An earlier version grouped a static link with the first joint
1809
+ # encountered scanning *forward* -- correct for a trailing run
1810
+ # (#636), but wrong for a sandwiched static link (joint_A -> static
1811
+ # -> joint_B): that attaches the static link's mass to joint_B's
1812
+ # group, when it's actually rigidly welded to joint_A's output and
1813
+ # physically independent of joint_B's angle, silently misattributing
1814
+ # its torque contribution to the wrong joint (#483).
1815
+ group_of_link_idx: dict[int, int] = {}
1792
1816
  for i, link in enumerate(self.links):
1793
- current_group.append(i)
1794
-
1795
- # Break after adding the first link
1796
1817
  if link.isjoint:
1797
- link_groups.append(current_group)
1798
- current_group = []
1818
+ link_groups.append([i])
1819
+ group_of_link_idx[i] = len(link_groups) - 1
1820
+ elif link.parent is not None:
1821
+ group_idx = group_of_link_idx.get(self.links.index(link.parent))
1822
+ if group_idx is not None:
1823
+ link_groups[group_idx].append(i)
1824
+ group_of_link_idx[i] = group_idx
1799
1825
 
1800
1826
  # Make some intermediate variables
1801
1827
  for i, group in enumerate(link_groups):
1802
1828
  I_int = SpatialInertia()
1803
1829
 
1830
+ # group[0] is always the joint itself (see the grouping loop
1831
+ # above); everything after it in the group is a static link
1832
+ # rigidly carried by that joint's own output frame, but
1833
+ # link.r/link.I are each expressed in that *link's own* frame,
1834
+ # not the joint's. Compose their fixed transforms relative to
1835
+ # the joint and carry the CoM/inertia into the joint's frame
1836
+ # before summing -- otherwise a static link's mass lands in
1837
+ # I_int with the wrong moment arm relative to this joint
1838
+ # (effectively r=0), which for a non-negligible offset silently
1839
+ # drops its contribution to this joint's torque (#636, #483).
1840
+ past_joint = False
1841
+ T_from_joint = None
1804
1842
  for idx in group:
1805
1843
  link = self.links[idx]
1806
1844
 
1807
- I_int = I_int + SpatialInertia(m=link.m, r=link.r, I=link.I)
1845
+ if past_joint:
1846
+ T_from_joint = (
1847
+ SE3(link.A())
1848
+ if T_from_joint is None
1849
+ else T_from_joint * SE3(link.A())
1850
+ )
1851
+ r = T_from_joint.R @ np.asarray(link.r) + T_from_joint.t
1852
+ Ir = T_from_joint.R @ link.I @ T_from_joint.R.T
1853
+ I_int = I_int + SpatialInertia(m=link.m, r=r, I=Ir)
1854
+ else:
1855
+ I_int = I_int + SpatialInertia(m=link.m, r=link.r, I=link.I)
1808
1856
 
1809
1857
  if link.v is not None:
1810
1858
  s.append(link.v.s) # type: ignore[union-attr]
1859
+ past_joint = True
1811
1860
 
1812
1861
  I[i] = I_int
1813
1862
 
@@ -1832,8 +1881,8 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1832
1881
  # where the indices correspond to the index of the group within
1833
1882
  # link_groups
1834
1883
  # As always, q, qd, qdd are lists of length n, where indices correspond
1835
- # to the jindex of the joint, which will be the last link in the group
1836
- # within link_groups
1884
+ # to the jindex of the joint, which is always the first link in the
1885
+ # group within link_groups
1837
1886
 
1838
1887
  for k in range(l):
1839
1888
  qk = q[k, :]
@@ -1842,48 +1891,43 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1842
1891
 
1843
1892
  # forward recursion
1844
1893
  for j, group in enumerate(link_groups):
1845
- # The joint is the last link in the group
1846
- joint = self.links[group[-1]]
1894
+ # group[0] is always the joint (see the grouping loop
1895
+ # above); any static links after it in the group are
1896
+ # rigidly carried by this joint's own output frame and are
1897
+ # folded into I[j] directly instead, not into this
1898
+ # kinematic frame -- v[j]/a[j] stay defined at the joint's
1899
+ # own output, matching s[j]/vJ, which are themselves only
1900
+ # ever expressed there.
1901
+ joint = self.links[group[0]]
1847
1902
  jindex = joint.jindex
1848
1903
 
1849
1904
  vJ = SpatialVelocity(s[j] * qdk[jindex])
1850
1905
 
1851
- # transform from parent(j) to j
1852
- # Xup_int = SE3()
1853
- first_element = True
1854
- for idx in group:
1855
- link = self.links[idx]
1856
-
1857
- if link.isjoint and link.jindex is not None:
1858
- if first_element:
1859
- Xup_int = SE3(link.A(qk[link.jindex]))
1860
- first_element = False
1861
- else:
1862
- Xup_int = Xup_int * SE3(link.A(qk[link.jindex]))
1863
- else:
1864
- if first_element:
1865
- Xup_int = SE3(link.A())
1866
- first_element = False
1867
- else:
1868
- Xup_int = Xup_int * SE3(link.A())
1869
-
1906
+ # transform from parent(j) to j: joint.A() already
1907
+ # incorporates any fixed transform within the joint link's
1908
+ # own ETS (the joint variable is guaranteed to be the last
1909
+ # element of its segment -- see this method's docstring),
1910
+ # so no further composition is needed.
1911
+ Xup_int = SE3(joint.A(qk[jindex]))
1870
1912
  Xup[j] = Xup_int.inv() # type: ignore[union-attr]
1871
1913
 
1872
- # The first link in the group
1873
- first_link = self.links[group[0]]
1914
+ # group_of_link_idx also covers a joint whose immediate
1915
+ # .parent is a static link, or a run of them, rather than
1916
+ # another joint directly -- returning None (rather than
1917
+ # raising) when that walk reaches the root with no joint
1918
+ # ancestor at all (e.g. a static base link, such as URDF
1919
+ # Panda's panda_link0), which is kinematically equivalent
1920
+ # to .parent being None.
1921
+ group_idx = (
1922
+ group_of_link_idx.get(self.links.index(joint.parent))
1923
+ if joint.parent is not None
1924
+ else None
1925
+ )
1874
1926
 
1875
- if first_link.parent is None:
1927
+ if group_idx is None:
1876
1928
  v[j] = vJ
1877
1929
  a[j] = Xup[j] * a_grav + SpatialAcceleration(s[j] * qddk[jindex])
1878
1930
  else:
1879
- # The index of `link`s parent within self.links
1880
- parent_idx = self.links.index(first_link.parent)
1881
-
1882
- # The index of the group that the parent link is in
1883
- group_idx = [
1884
- i for i, group in enumerate(link_groups) if parent_idx in group
1885
- ][0]
1886
-
1887
1931
  v[j] = Xup[j] * v[group_idx] + vJ
1888
1932
  a[j] = (
1889
1933
  Xup[j] * a[group_idx]
@@ -1896,9 +1940,7 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1896
1940
  # Backward recursion
1897
1941
  for j in reversed(range(n)):
1898
1942
  group = link_groups[j]
1899
- joint = self.links[group[-1]]
1900
- first_link = self.links[group[0]]
1901
- # link = self.links[j]
1943
+ joint = self.links[group[0]] # always the joint -- see above
1902
1944
 
1903
1945
  # next line could be dot(), but fails for symbolic arguments
1904
1946
  Q[k, j] = sum(f[j].A * s[j])
@@ -1913,16 +1955,26 @@ class Robot(BaseRobot[Link], RobotKinematicsMixin):
1913
1955
  - joint.friction(qdk[jindex], coulomb=not symbolic)
1914
1956
  )
1915
1957
 
1916
- if first_link.parent is not None:
1917
- # The index of `link`s parent within self.links
1918
- parent_idx = self.links.index(first_link.parent)
1919
-
1920
- # The index of the group that the parent link is in
1921
- group_idx = [
1922
- i for i, group in enumerate(link_groups) if parent_idx in group
1923
- ][0]
1958
+ # See the forward recursion above: group_of_link_idx
1959
+ # resolves through any static link(s) between this joint
1960
+ # and its nearest joint ancestor, returning None if there
1961
+ # isn't one (root).
1962
+ group_idx = (
1963
+ group_of_link_idx.get(self.links.index(joint.parent))
1964
+ if joint.parent is not None
1965
+ else None
1966
+ )
1924
1967
 
1925
- f[group_idx] = f[group_idx] + Xup[j] * f[j]
1968
+ if group_idx is not None:
1969
+ # Xup[j] is the child<-parent motion transform (v_child =
1970
+ # Xup[j] * v_parent); propagating a force the other way,
1971
+ # child->parent, needs the transform in the other
1972
+ # direction too. spatialmath's SE3 * SpatialForce applies
1973
+ # the coadjoint of its left operand (since
1974
+ # spatialmath-python 1.1.18, rai-opensource/
1975
+ # spatialmath-python#207), so the un-inverted transform
1976
+ # (Xup[j].inv(), i.e. parent<-child) is what's needed here.
1977
+ f[group_idx] = f[group_idx] + Xup[j].inv() * f[j]
1926
1978
 
1927
1979
  # The current Q has the length equal to the number of links within the robot
1928
1980
  # rather than the number of joints. We need to remove the static links
@@ -21,6 +21,16 @@ class RobotKinematicsMixin:
21
21
 
22
22
  """
23
23
 
24
+ def _resolve_tool(self: KinematicsProtocol, tool: NDArray | SE3 | None) -> SE3:
25
+ """
26
+ Resolve an explicit ``tool`` argument, defaulting to the robot's
27
+ own :attr:`tool` (identity if never set) when not given explicitly
28
+ -- see :meth:`fkine`'s ``tool`` parameter.
29
+ """
30
+ if tool is None:
31
+ return self.tool
32
+ return tool if isinstance(tool, SE3) else SE3(tool, check=False)
33
+
24
34
  # --------------------------------------------------------------------- #
25
35
  # --------- Kinematic Methods ----------------------------------------- #
26
36
  # --------------------------------------------------------------------- #
@@ -85,9 +95,11 @@ class RobotKinematicsMixin:
85
95
  specify ``end``
86
96
  - For a robot with multiple end-effectors, the ``end`` must
87
97
  be specified.
88
- - The robot's base tool transform, if set, is incorporated
89
- into the result.
90
- - A tool transform, if provided, is incorporated into the result.
98
+ - The robot's own base transform (``self.base``), if set, is
99
+ always incorporated into the result.
100
+ - The robot's own tool transform (``self.tool``), if set, is
101
+ incorporated into the result unless the ``tool`` parameter is
102
+ given explicitly, which overrides it.
91
103
  - Works from the end-effector link to the base
92
104
 
93
105
  .. rubric:: References
@@ -101,7 +113,10 @@ class RobotKinematicsMixin:
101
113
 
102
114
  return SE3(
103
115
  self.ets(start, end).fkine(
104
- q, base=self._T, tool=tool, include_base=include_base
116
+ q,
117
+ base=self._T,
118
+ tool=self._resolve_tool(tool),
119
+ include_base=include_base,
105
120
  ),
106
121
  check=False,
107
122
  )
@@ -155,7 +170,7 @@ class RobotKinematicsMixin:
155
170
 
156
171
  """
157
172
 
158
- return self.ets(start, end).jacob0(q, tool=tool)
173
+ return self.ets(start, end).jacob0(q, tool=self._resolve_tool(tool))
159
174
 
160
175
  def jacobe(
161
176
  self: KinematicsProtocol,
@@ -206,7 +221,7 @@ class RobotKinematicsMixin:
206
221
 
207
222
  """
208
223
 
209
- return self.ets(start, end).jacobe(q, tool=tool)
224
+ return self.ets(start, end).jacobe(q, tool=self._resolve_tool(tool))
210
225
 
211
226
  @overload
212
227
  def hessian0(
@@ -306,7 +321,7 @@ class RobotKinematicsMixin:
306
321
 
307
322
  """
308
323
 
309
- return self.ets(start, end).hessian0(q, J0=J0, tool=tool)
324
+ return self.ets(start, end).hessian0(q, J0=J0, tool=self._resolve_tool(tool))
310
325
 
311
326
  @overload
312
327
  def hessiane(
@@ -406,7 +421,7 @@ class RobotKinematicsMixin:
406
421
 
407
422
  """
408
423
 
409
- return self.ets(start, end).hessiane(q, Je=Je, tool=tool)
424
+ return self.ets(start, end).hessiane(q, Je=Je, tool=self._resolve_tool(tool))
410
425
 
411
426
  def partial_fkine0(
412
427
  self: KinematicsProtocol,
@@ -506,7 +521,7 @@ class RobotKinematicsMixin:
506
521
  """
507
522
 
508
523
  return self.ets(start, end).jacob0_analytical(
509
- q, tool=tool, representation=representation
524
+ q, tool=self._resolve_tool(tool), representation=representation
510
525
  )
511
526
 
512
527
  # --------------------------------------------------------------------- #
@@ -526,6 +541,7 @@ class RobotKinematicsMixin:
526
541
  joint_limits: bool = True,
527
542
  k: float = 1.0,
528
543
  method: L["chan", "wampler", "sugihara"] = "chan",
544
+ tool: NDArray | SE3 | None = None,
529
545
  ) -> IKSolution:
530
546
  r"""
531
547
  Fast Levenberg-Marquardt Numerical Inverse Kinematics Solver
@@ -544,6 +560,9 @@ class RobotKinematicsMixin:
544
560
  :param k: Sets the gain value for the damping matrix Wn in the next iteration
545
561
  :param method: One of "chan", "sugihara" or "wampler". Defines which method is used
546
562
  to calculate the damping matrix Wn in the ``step`` method
563
+ :param tool: a static tool transformation matrix to apply to the
564
+ end of ``end``; defaults to the robot's own ``self.tool`` if
565
+ not given
547
566
  :returns: an IKSolution containing joint coordinates ``q``, ``success`` flag,
548
567
  ``iterations``, ``searches`` and ``residual`` error value (``reason`` is
549
568
  always empty -- this fast C++ solver doesn't produce a granular failure
@@ -661,7 +680,7 @@ class RobotKinematicsMixin:
661
680
  """
662
681
 
663
682
  return self.ets(start, end).ik_LM(
664
- Tep=Tep,
683
+ Tep=SE3(Tep, check=False) * self._resolve_tool(tool).inv(),
665
684
  q0=q0,
666
685
  ilimit=ilimit,
667
686
  slimit=slimit,
@@ -685,6 +704,7 @@ class RobotKinematicsMixin:
685
704
  joint_limits: bool = True,
686
705
  pinv: int = True,
687
706
  pinv_damping: float = 0.0,
707
+ tool: NDArray | SE3 | None = None,
688
708
  ) -> IKSolution:
689
709
  r"""
690
710
  Fast numerical inverse kinematics using Newton-Raphson optimization
@@ -705,6 +725,9 @@ class RobotKinematicsMixin:
705
725
  another search up to the slimit)
706
726
  :param pinv: Use the pseudo-inverse instead of the normal matrix inverse
707
727
  :param pinv_damping: Damping factor for the pseudo-inverse
728
+ :param tool: a static tool transformation matrix to apply to the
729
+ end of ``end``; defaults to the robot's own ``self.tool`` if
730
+ not given
708
731
  :returns: an IKSolution containing joint coordinates ``q``, ``success`` flag,
709
732
  ``iterations``, ``searches`` and ``residual`` error value (``reason`` is
710
733
  always empty -- this fast C++ solver doesn't produce a granular failure
@@ -782,7 +805,7 @@ class RobotKinematicsMixin:
782
805
  """
783
806
 
784
807
  return self.ets(start, end).ik_NR(
785
- Tep=Tep,
808
+ Tep=SE3(Tep, check=False) * self._resolve_tool(tool).inv(),
786
809
  q0=q0,
787
810
  ilimit=ilimit,
788
811
  slimit=slimit,
@@ -806,6 +829,7 @@ class RobotKinematicsMixin:
806
829
  joint_limits: bool = True,
807
830
  pinv: int = True,
808
831
  pinv_damping: float = 0.0,
832
+ tool: NDArray | SE3 | None = None,
809
833
  ) -> IKSolution:
810
834
  r"""
811
835
  Fast numerical inverse kinematics by Gauss-Newton optimization
@@ -826,6 +850,9 @@ class RobotKinematicsMixin:
826
850
  another search up to the slimit)
827
851
  :param pinv: Use the pseudo-inverse instead of the normal matrix inverse
828
852
  :param pinv_damping: Damping factor for the pseudo-inverse
853
+ :param tool: a static tool transformation matrix to apply to the
854
+ end of ``end``; defaults to the robot's own ``self.tool`` if
855
+ not given
829
856
  :returns: an IKSolution containing joint coordinates ``q``, ``success`` flag,
830
857
  ``iterations``, ``searches`` and ``residual`` error value (``reason`` is
831
858
  always empty -- this fast C++ solver doesn't produce a granular failure
@@ -918,7 +945,7 @@ class RobotKinematicsMixin:
918
945
  """
919
946
 
920
947
  return self.ets(start, end).ik_GN(
921
- Tep=Tep,
948
+ Tep=SE3(Tep, check=False) * self._resolve_tool(tool).inv(),
922
949
  q0=q0,
923
950
  ilimit=ilimit,
924
951
  slimit=slimit,
@@ -947,6 +974,7 @@ class RobotKinematicsMixin:
947
974
  km: float = 0.0,
948
975
  ps: float = 0.0,
949
976
  pi: NDArray | float = 0.3,
977
+ tool: NDArray | SE3 | None = None,
950
978
  **kwargs,
951
979
  ):
952
980
  r"""
@@ -976,6 +1004,9 @@ class RobotKinematicsMixin:
976
1004
  allowed to approach to its limit
977
1005
  :param pi: The influence angle/distance (in radians or metres) in null space motion
978
1006
  becomes active
1007
+ :param tool: a static tool transformation matrix to apply to the
1008
+ end of ``end``; defaults to the robot's own ``self.tool`` if
1009
+ not given
979
1010
  :returns: an IKSolution containing joint coordinates ``q``, ``success`` flag,
980
1011
  ``iterations``, ``searches``, ``residual`` error value, and ``reason``
981
1012
  string if applicable
@@ -1096,7 +1127,7 @@ class RobotKinematicsMixin:
1096
1127
  """
1097
1128
 
1098
1129
  return self.ets(start, end).ikine_LM(
1099
- Tep=Tep,
1130
+ Tep=SE3(Tep, check=False) * self._resolve_tool(tool).inv(),
1100
1131
  q0=q0,
1101
1132
  ilimit=ilimit,
1102
1133
  slimit=slimit,
@@ -1130,6 +1161,7 @@ class RobotKinematicsMixin:
1130
1161
  km: float = 0.0,
1131
1162
  ps: float = 0.0,
1132
1163
  pi: NDArray | float = 0.3,
1164
+ tool: NDArray | SE3 | None = None,
1133
1165
  **kwargs,
1134
1166
  ):
1135
1167
  r"""
@@ -1158,6 +1190,9 @@ class RobotKinematicsMixin:
1158
1190
  allowed to approach to its limit
1159
1191
  :param pi: The influence angle/distance (in radians or metres) in null space motion
1160
1192
  becomes active
1193
+ :param tool: a static tool transformation matrix to apply to the
1194
+ end of ``end``; defaults to the robot's own ``self.tool`` if
1195
+ not given
1161
1196
 
1162
1197
  A method which provides functionality to perform numerical inverse kinematics (IK)
1163
1198
  using the Newton-Raphson method.
@@ -1222,7 +1257,7 @@ class RobotKinematicsMixin:
1222
1257
  """
1223
1258
 
1224
1259
  return self.ets(start, end).ikine_NR(
1225
- Tep=Tep,
1260
+ Tep=SE3(Tep, check=False) * self._resolve_tool(tool).inv(),
1226
1261
  q0=q0,
1227
1262
  ilimit=ilimit,
1228
1263
  slimit=slimit,
@@ -1255,6 +1290,7 @@ class RobotKinematicsMixin:
1255
1290
  km: float = 0.0,
1256
1291
  ps: float = 0.0,
1257
1292
  pi: NDArray | float = 0.3,
1293
+ tool: NDArray | SE3 | None = None,
1258
1294
  **kwargs,
1259
1295
  ):
1260
1296
  r"""
@@ -1283,6 +1319,9 @@ class RobotKinematicsMixin:
1283
1319
  allowed to approach to its limit
1284
1320
  :param pi: The influence angle/distance (in radians or metres) in null space motion
1285
1321
  becomes active
1322
+ :param tool: a static tool transformation matrix to apply to the
1323
+ end of ``end``; defaults to the robot's own ``self.tool`` if
1324
+ not given
1286
1325
 
1287
1326
  A method which provides functionality to perform numerical inverse kinematics (IK)
1288
1327
  using the Gauss-Newton method.
@@ -1362,7 +1401,7 @@ class RobotKinematicsMixin:
1362
1401
  """
1363
1402
 
1364
1403
  return self.ets(start, end).ikine_GN(
1365
- Tep=Tep,
1404
+ Tep=SE3(Tep, check=False) * self._resolve_tool(tool).inv(),
1366
1405
  q0=q0,
1367
1406
  ilimit=ilimit,
1368
1407
  slimit=slimit,
@@ -1396,6 +1435,7 @@ class RobotKinematicsMixin:
1396
1435
  km: float = 0.0,
1397
1436
  ps: float = 0.0,
1398
1437
  pi: NDArray | float = 0.3,
1438
+ tool: NDArray | SE3 | None = None,
1399
1439
  **kwargs,
1400
1440
  ):
1401
1441
  r"""
@@ -1424,6 +1464,9 @@ class RobotKinematicsMixin:
1424
1464
  allowed to approach to its limit
1425
1465
  :param pi: The influence angle/distance (in radians or metres) in null space motion
1426
1466
  becomes active
1467
+ :param tool: a static tool transformation matrix to apply to the
1468
+ end of ``end``; defaults to the robot's own ``self.tool`` if
1469
+ not given
1427
1470
  :raises ImportError: If the package ``qpsolvers`` is not installed
1428
1471
  :returns: an IKSolution containing joint coordinates ``q``, ``success`` flag,
1429
1472
  ``iterations``, ``searches``, ``residual`` error value, and ``reason``
@@ -1541,7 +1584,7 @@ class RobotKinematicsMixin:
1541
1584
  """
1542
1585
 
1543
1586
  return self.ets(start, end).ikine_QP(
1544
- Tep=Tep,
1587
+ Tep=SE3(Tep, check=False) * self._resolve_tool(tool).inv(),
1545
1588
  q0=q0,
1546
1589
  ilimit=ilimit,
1547
1590
  slimit=slimit,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: roboticstoolbox-python
3
- Version: 1.4.3
3
+ Version: 1.4.4
4
4
  Summary: A Python library for robotics education and research
5
5
  Keywords: python,robotics,robotics-toolbox,kinematics,dynamics,motion-planning,trajectory-generation,jacobian,hessian,control,simulation,robot-manipulator,mobile-robot
6
6
  Author-Email: Jesse Haviland <j.haviland@qut.edu.au>, Peter Corke <rvc@petercorke.com>
@@ -39,7 +39,7 @@ Project-URL: documentation, https://petercorke.github.io/robotics-toolbox-python
39
39
  Project-URL: repository, https://github.com/petercorke/robotics-toolbox-python
40
40
  Requires-Python: >=3.10
41
41
  Requires-Dist: numpy
42
- Requires-Dist: spatialmath-python>=1.1.16
42
+ Requires-Dist: spatialmath-python>=1.1.18
43
43
  Requires-Dist: spatialgeometry>=1.4.0
44
44
  Requires-Dist: pgraph-python
45
45
  Requires-Dist: scipy
@@ -543,44 +543,4 @@ Graphical visualisation via Swift is currently not supported under Windows. Howe
543
543
 
544
544
  The toolbox is incredibly useful for developing and prototyping algorithms for research, thanks to the exhaustive set of well documented and mature robotic functions exposed through clean and painless APIs. Additionally, the ease at which a user can visualize their algorithm supports a rapid prototyping paradigm.
545
545
 
546
- ### Publication List
547
-
548
- J. Haviland, N. Sünderhauf and P. Corke, "**A Holistic Approach to Reactive Mobile Manipulation**," in _IEEE Robotics and Automation Letters_, doi: 10.1109/LRA.2022.3146554. In the video, the robot is controlled using the Robotics toolbox for Python and features a recording from the [Swift](https://github.com/jhavl/swift) Simulator.
549
-
550
- [[Arxiv Paper](https://arxiv.org/abs/2109.04749)] [[IEEE Xplore](https://ieeexplore.ieee.org/abstract/document/9695298)] [[Project Website](https://jhavl.github.io/holistic/)] [[Video](https://youtu.be/-DXBQPeLIV4)] [[Code Example](https://github.com/petercorke/robotics-toolbox-python/blob/main/roboticstoolbox/examples/holistic_mm_non_holonomic.py)]
551
-
552
- <p>
553
- <a href="https://youtu.be/-DXBQPeLIV4">
554
- <img src="https://raw.githubusercontent.com/petercorke/robotics-toolbox-python/main/docs/figs/holistic_youtube.png" width="560">
555
- </a>
556
- </p>
557
-
558
- J. Haviland and P. Corke, "**NEO: A Novel Expeditious Optimisation Algorithm for Reactive Motion Control of Manipulators**," in _IEEE Robotics and Automation Letters_, doi: 10.1109/LRA.2021.3056060. In the video, the robot is controlled using the Robotics toolbox for Python and features a recording from the [Swift](https://github.com/jhavl/swift) Simulator.
559
-
560
- [[Arxiv Paper](https://arxiv.org/abs/2010.08686)] [[IEEE Xplore](https://ieeexplore.ieee.org/document/9343718)] [[Project Website](https://jhavl.github.io/neo/)] [[Video](https://youtu.be/jSLPJBr8QTY)] [[Code Example](https://github.com/petercorke/robotics-toolbox-python/blob/main/roboticstoolbox/examples/neo.py)]
561
-
562
- <p>
563
- <a href="https://youtu.be/jSLPJBr8QTY">
564
- <img src="https://raw.githubusercontent.com/petercorke/robotics-toolbox-python/main/docs/figs/neo_youtube.png" width="560">
565
- </a>
566
- </p>
567
-
568
- K. He, R. Newbury, T. Tran, J. Haviland, B. Burgess-Limerick, D. Kulić, P. Corke, A. Cosgun, "**Visibility Maximization Controller for Robotic Manipulation**", in _IEEE Robotics and Automation Letters_, doi: 10.1109/LRA.2022.3188430. In the video, the robot is controlled using the Robotics toolbox for Python and features a recording from the [Swift](https://github.com/jhavl/swift) Simulator.
569
-
570
- [[Arxiv Paper](https://arxiv.org/abs/2202.12557)] [[IEEE Xplore](https://ieeexplore.ieee.org/abstract/document/9815144)] [[Project Website](https://rhys-newbury.github.io/projects/vmc/)] [[Video](https://youtu.be/vobLvg4E3kM)] [[Code Example](https://github.com/petercorke/robotics-toolbox-python/blob/main/roboticstoolbox/examples/fetch_vision.py)]
571
-
572
- <p>
573
- <a href="https://youtu.be/vobLvg4E3kM">
574
- <img src="https://raw.githubusercontent.com/petercorke/robotics-toolbox-python/main/docs/figs/vmc_youtube.png" width="560">
575
- </a>
576
- </p>
577
-
578
- **A Purely-Reactive Manipulability-Maximising Motion Controller**, J. Haviland and P. Corke. In the video, the robot is controlled using the Robotics toolbox for Python.
579
-
580
- [[Paper](https://arxiv.org/abs/2002.11901)] [[Project Website](https://jhavl.github.io/mmc/)] [[Video](https://youtu.be/Vu_rcPlaADI)] [[Code Example](https://github.com/petercorke/robotics-toolbox-python/blob/main/roboticstoolbox/examples/mmc.py)]
581
-
582
- <p>
583
- <a href="https://youtu.be/Vu_rcPlaADI">
584
- <img src="https://raw.githubusercontent.com/petercorke/robotics-toolbox-python/main/docs/figs/mmc_youtube.png" width="560">
585
- </a>
586
- </p>
546
+ It's been the implementation substrate for several published reactive-control papers, and its kinematics are a direct implementation of a two-part differential-kinematics tutorial — see the wiki's [Reactive control](https://github.com/petercorke/robotics-toolbox-python/wiki/Reactive-control) and [Inverse kinematics](https://github.com/petercorke/robotics-toolbox-python/wiki/Inverse-kinematics) pages.
@@ -30,7 +30,7 @@ roboticstoolbox/backends/__init__.py,sha256=WXkKfxqjgu_LdyyvZT0qmYT5bZ9XtRJQvEn_
30
30
  roboticstoolbox/backends/swift/__init__.py,sha256=uenayFsFBt1k3565I8rxjMHgyW3mrFhLXimRF5LYPn0,7077
31
31
  roboticstoolbox/bin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  roboticstoolbox/bin/_bintools.py,sha256=ofTQ47u8sixdJGIxMzcxHsBRtWyenSVGhCqUKV8rF5c,1903
33
- roboticstoolbox/bin/rtbtool.py,sha256=YIO-d9oycB2LRlHL4uaZM_swHSK9zUNkSKlCMdaceb0,12207
33
+ roboticstoolbox/bin/rtbtool.py,sha256=9OEfJ_dRCg0jMimP3O4TdIPsi5TjepB7Pr_9mYvgcdg,12206
34
34
  roboticstoolbox/blocks/Icons/250x250/armplot.png,sha256=3bZtXa_ZaLRtzEXZSooLEBp3EPFmEIaVIUQCT3bY8-8,5918
35
35
  roboticstoolbox/blocks/Icons/250x250/bicycle.png,sha256=BEQkGUY81Bms4gvdBechZzbgZClTSOq9YqX03-XuVZk,8995
36
36
  roboticstoolbox/blocks/Icons/250x250/camera.png,sha256=CGcTB_llMoucN-rfnP6ZP_7AfE_92z6JnCdam-hDLgs,1642
@@ -468,7 +468,7 @@ roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/MatrixCwiseUnaryOps.h,sha25
468
468
  roboticstoolbox/ets/cpp-extensions/Eigen/src/plugins/ReshapedMethods.h,sha256=OYyO_yoZ3TL7X8GFrIW-IKzqs_1LbM0M96HJyZAVOi8,6915
469
469
  roboticstoolbox/ets/cpp-extensions/README.md,sha256=xLvpxXx8xbe-wElyzpODFOxairojzFyGgeeCjXYQyMQ,3666
470
470
  roboticstoolbox/ets/cpp-extensions/fknm_nb.cpp,sha256=Zent7Jp9gn-Cg6Y2nZnrgaaMwi-WBrZ9tAuXE1ybz4A,22027
471
- roboticstoolbox/ets/cpp-extensions/ik.cpp,sha256=guMsgJ6xYhFP9oG_8FdP8Woxp4xjRPPuIE9T94VMad4,10974
471
+ roboticstoolbox/ets/cpp-extensions/ik.cpp,sha256=6B4cDKiVc_5WKGba-bTE_ngB7_jU9gyQXa3tGGZEku0,13132
472
472
  roboticstoolbox/ets/cpp-extensions/ik.h,sha256=OkgTgJ4tV8P47elnQCOnM76n8ru0PpkZPCQFmUn7fWY,1780
473
473
  roboticstoolbox/ets/cpp-extensions/linalg.cpp,sha256=D8c4GmITlK_2ardbeUys6SyuoTKqSYrSke0144caFpc,8905
474
474
  roboticstoolbox/ets/cpp-extensions/linalg.h,sha256=YeuiXgnFGHjmgv3GMN4q_veR3fLdnoEa00LLJk6Opos,1862
@@ -520,7 +520,7 @@ roboticstoolbox/models/DH/README.md,sha256=h8F7jSpP-6v0u4Ika5YEYluZg6qxTXQVsO6c8
520
520
  roboticstoolbox/models/DH/Sawyer.py,sha256=WUeAeQeJ7-Kw6rvJAcAIr0IwhNeFl_SPnpOdlB3xKVM,2084
521
521
  roboticstoolbox/models/DH/Stanford.py,sha256=fOLeK6oo6Zmr9-93Wd2yZJLIkDqMqm2WG6HNUHEGUtU,3908
522
522
  roboticstoolbox/models/DH/TwoLink.py,sha256=EavoSjY8waes_WehZ5TVs3EDfD2N1kMwgoJtuuPiHd4,6077
523
- roboticstoolbox/models/DH/UR10.py,sha256=-zzC5_LDSta9yJtX4Fc9vjEAIkFk5t8mt5JQpeaIrrs,3362
523
+ roboticstoolbox/models/DH/UR10.py,sha256=mj-smTlQox9nIqTOqXec0gXPFjvNlMn4dMbF8Ip_XPQ,3736
524
524
  roboticstoolbox/models/DH/UR3.py,sha256=ZHq5ayfCdU2HW2yV8Yist8BC8PKR1woKWRka3pO6e34,2483
525
525
  roboticstoolbox/models/DH/UR5.py,sha256=yYxp5JABLTbQjwiDQksBeyNmjzJ5uH8_IEOqwPawpls,2534
526
526
  roboticstoolbox/models/DH/Uprighttl.py,sha256=4bTrWW7uFTyoJCb4n9ivy1V8SCPCo0XLByo_Wni6-iw,571
@@ -536,21 +536,21 @@ roboticstoolbox/models/ETS/XYPanda.py,sha256=62_oLmwss-incP-NkaDdvKF7ee0gsbNsH7k
536
536
  roboticstoolbox/models/ETS/__init__.py,sha256=GYPivBP0GXaX8nODS-3lRoppTISH4PEcXnKUNasDXB8,583
537
537
  roboticstoolbox/models/README.md,sha256=2NHhybtNIE6rIViAw6ElKUd44zPM8umpM8pB4FpKUpU,1312
538
538
  roboticstoolbox/models/URDF/AL5D.py,sha256=He4bM47R-nyO15jv0fYOtvKQV0nPcsubH9UKamt2A3Y,1192
539
- roboticstoolbox/models/URDF/Fetch.py,sha256=ga6BVYVVLzwVxgo_2DspLXOvySDWxilcPGCJO978sFQ,3034
540
- roboticstoolbox/models/URDF/Frankie.py,sha256=ht5jKMH18I9u-uK2TKSHzgh9Ko6lb0MT_909VNLIVFw,1710
541
- roboticstoolbox/models/URDF/FrankieOmni.py,sha256=JN_eXZDlhxc7T3zWPiegB6kmaO68LTwn1Kj-51Yd0L4,2540
542
- roboticstoolbox/models/URDF/Jaco.py,sha256=LWqEV9lKzqxud5X2osJx1wOu7x_WTi1VKyOAKycZQ6E,1285
539
+ roboticstoolbox/models/URDF/Fetch.py,sha256=XZfV7wfIvy--3vTNqIq33TUdbLkFIznEjUzJJ0vaCsY,3164
540
+ roboticstoolbox/models/URDF/Frankie.py,sha256=FvZXXdT2-6VsnphkduiisDr4cmk_pZFq7w3s6Eqi-0s,2132
541
+ roboticstoolbox/models/URDF/FrankieOmni.py,sha256=D2TJSbxWD6LpW3_bh7uCtIFSBrE_kJqcHJdpC1NS2rc,2998
542
+ roboticstoolbox/models/URDF/Jaco.py,sha256=vwK88sHrMp6Q6gqHgDlbn4TiXfrwrD8jvEmgfVnb4V4,1415
543
543
  roboticstoolbox/models/URDF/KinovaGen3.py,sha256=fsmxZX7kooNMqp_tUGGXJgh86GOG2OT3_shOhRIdaSQ,1580
544
544
  roboticstoolbox/models/URDF/LBR.py,sha256=uzP-7FzCpF8mIhSiT8HoKuYeISOkYS-NkmTjmUJgQmA,1806
545
- roboticstoolbox/models/URDF/PR2.py,sha256=kTDa2IjkBobUsKw4dX7f8TmxInex8h5GqwAnaSVUnGY,1482
546
- roboticstoolbox/models/URDF/Panda.py,sha256=ZF5PPfPaAbZEVW3onR2ntaT6jgW1InAO8GdSaGmG9rM,1549
545
+ roboticstoolbox/models/URDF/PR2.py,sha256=ZEpFuOWXQW33l68EEP3QxnKs7l9lP0vHIvUJeVFDmc4,1612
546
+ roboticstoolbox/models/URDF/Panda.py,sha256=ws8YtoudZ88L9F6O2S7oioK3w_V4RzztMMLHqHAnJcU,2752
547
547
  roboticstoolbox/models/URDF/Puma560.py,sha256=1WHxYNXkYgJ6TJ9xJpJzfkZlfHZA2N9HFS6_p1b171w,2881
548
- roboticstoolbox/models/URDF/UR10.py,sha256=OIw8-f3diIytx9M4FKofNOlduEDRiYTVQZdvZjY5Zo4,1073
549
- roboticstoolbox/models/URDF/UR3.py,sha256=T0pd-KtNQYoqmONkfMaqWKZSA6WS3Wq0JzS3NIL1lvg,1066
550
- roboticstoolbox/models/URDF/UR5.py,sha256=DDLzthyJ0fyylxijTZ14X_KZIYsCgLO1S6Pk5vecQZ0,1671
551
- roboticstoolbox/models/URDF/URDFRobot.py,sha256=cEyhWSozFhvQwJsYZ8hhGjkByzChGjHWXahTjiK6a7k,15441
552
- roboticstoolbox/models/URDF/Valkyrie.py,sha256=7LLgVRXFZ_NM5lksNW7jx6jugSDEJ4uylqqqfwkbPP0,4133
553
- roboticstoolbox/models/URDF/YuMi.py,sha256=lg6d827b4cr0tazyFnds2i4LuPGI9bw2d4QWRntAJ7A,3245
548
+ roboticstoolbox/models/URDF/UR10.py,sha256=Excq3j1nuaqWGw67UPmAlWoXICUMsngbY1NU1Uc4MA8,1203
549
+ roboticstoolbox/models/URDF/UR3.py,sha256=FIQTrAlqUrY8qeCkbNc2hpQPnxnv19cz_nlC1EM0JGg,1196
550
+ roboticstoolbox/models/URDF/UR5.py,sha256=vCM373teYtNMcQXyWLuqtNqCNVoEJBgbnwa-BSeo-44,2221
551
+ roboticstoolbox/models/URDF/URDFRobot.py,sha256=x6ItlP5oZBLXo0IZAijDyrZwZ-LiFHfFnRs5S7TLNLY,16632
552
+ roboticstoolbox/models/URDF/Valkyrie.py,sha256=E75Sl1fG9lM-iCtxy6mo8OymW7M8qnXkc-uJSWMmvew,4263
553
+ roboticstoolbox/models/URDF/YuMi.py,sha256=BrkpWMsOB9jFVeNVaw8MX_vAVYEBIOUrNI7gKbBMGC4,3375
554
554
  roboticstoolbox/models/URDF/__init__.py,sha256=G4PNTfYRiYekdxssVHN8v3zPUWPhbcOrffJR9u6lWDo,1599
555
555
  roboticstoolbox/models/URDF/px100.py,sha256=sEU-keutJh_KP30zQfV-n51UZoaLCD1SQgw2vf31Ipk,1240
556
556
  roboticstoolbox/models/URDF/px150.py,sha256=NzGJ0pV9pLeS5mtaMvtLJpkcwvwYcSYpQcuqPTVxdqI,1243
@@ -566,16 +566,16 @@ roboticstoolbox/models/catalog.py,sha256=R-Lc_vHoOfae-afVS1olmoPu9cq7YTDfYqtCJeM
566
566
  roboticstoolbox/robot/BaseRobot.py,sha256=OcUR9yMLDvUUTrD34FnoufoZOdbU2Lc-pRxn08g3OCk,82370
567
567
  roboticstoolbox/robot/DHFactor.py,sha256=Bf-j7IvBKRA6_k3uev_ceQRMAC79D7W0Ir7kCJt4mec,15981
568
568
  roboticstoolbox/robot/DHLink.py,sha256=_7FtGDuS_dVxFGUQx9-Vqj7EoLsMIhV9SwVDFpWBPWs,27815
569
- roboticstoolbox/robot/DHRobot.py,sha256=aqi-EmoC9ixwjax8ORi1REKx54E_XLu_Hrb1QSq8wdM,67041
569
+ roboticstoolbox/robot/DHRobot.py,sha256=UTWuE7Fl2shooddEKQn2BQ6w5VHfNwClz2ljKYy093A,67041
570
570
  roboticstoolbox/robot/Dynamics.py,sha256=iCplz4L1wT2OeXuS0acO9x8h4uxsifT67PV4TViUwZw,52479
571
571
  roboticstoolbox/robot/ELink.py,sha256=sgA-1c-U0Gz6TxABSJvfauq2L0Wu_1_pGJVKfLgrGP8,525
572
572
  roboticstoolbox/robot/ERobot.py,sha256=aUnSg91PwHyIHO6ipWtnNBNLYCftKGIxgYz1qX8wghg,567
573
573
  roboticstoolbox/robot/Gripper.py,sha256=CYyXFBYbEp52-RX-b6xGWS3EH-Y2BsYdtaR8Q0yAm3s,6954
574
- roboticstoolbox/robot/IK.py,sha256=7CzondLtnZdETFJPC4MczdDz46Ar3n83OvJnMBauV6I,48515
574
+ roboticstoolbox/robot/IK.py,sha256=8s_YwcU22IxcMwke9yHX-ssv5HvdtHf_2vcuknhimBg,50270
575
575
  roboticstoolbox/robot/Link.py,sha256=m98nfpv61C5Z1GDrOV_iN6gxyh9qj8URwiju9ssKXxw,46283
576
576
  roboticstoolbox/robot/PoERobot.py,sha256=mByMjb3P5FrW3Z8zyLS5waYcKBy8lpHG44g2uBUyB70,11053
577
- roboticstoolbox/robot/Robot.py,sha256=-5SsVItMPKAd56lxPQrXlHHbdpMn-GMKGCUy73LeELw,78647
578
- roboticstoolbox/robot/RobotKinematics.py,sha256=Mq4MfE3hOuJaEdWD26s9xVlIOuXyiGACSd-iDuChLHg,61869
577
+ roboticstoolbox/robot/Robot.py,sha256=rkzRa9LTrTBIuSI0dbT1SjI44YdX5VS-1OuPm2W1Q6U,82555
578
+ roboticstoolbox/robot/RobotKinematics.py,sha256=0K-bdowzzzPUnImSVAxRylucSAyl-dyfhP7_4hpcvrs,64448
579
579
  roboticstoolbox/robot/RobotPlottingMPL.py,sha256=tmIGievDZwtLkd0To0Of2MeAB38yvbUd8T0bF0qwLd8,14207
580
580
  roboticstoolbox/robot/RobotProto.py,sha256=os5bJSXp4ZO-ANJjaSF8_Xb-TJxbCg1Ry_obS3A6LqM,4339
581
581
  roboticstoolbox/robot/__init__.py,sha256=RUeji1GTnAL3U3mn0ae6W1lnmvC-CFlUw5U7RTQnvO8,1237
@@ -604,8 +604,8 @@ roboticstoolbox/tools/urdf/tests/data/ur5.urdf,sha256=9g0uYkRHRsWHWl6vn5lIK_JMHq
604
604
  roboticstoolbox/tools/urdf/tests/test_urdf.py,sha256=MoRP8DolFu7RXxrKkwEWJOSfVygX7UJwEv-hLZkdlKw,3445
605
605
  roboticstoolbox/tools/urdf/urdf.py,sha256=IE5EUSNjkOAZZUOkuhXKDhYRq2AuJGulIvfbsRHy7pI,58743
606
606
  roboticstoolbox/tools/urdf/utils.py,sha256=6g1uuI2u7CUaWhKavI7wDHT3a2T3ijb92JByQ1lNq6w,1483
607
- roboticstoolbox_python-1.4.3.dist-info/METADATA,sha256=5GYwYgEhenEQBNgrQtYgHuCprlkow1AIfxbxq_xNczE,27162
608
- roboticstoolbox_python-1.4.3.dist-info/WHEEL,sha256=DJBbB-IMWy7eyPvSdm5L2t-BbrULtweJWf0YC-blkl0,95
609
- roboticstoolbox_python-1.4.3.dist-info/entry_points.txt,sha256=m9xEI6rJsYQL0HosyeS-4TUPzxOR3kqx4vbJVVxwo7k,214
610
- roboticstoolbox_python-1.4.3.dist-info/licenses/LICENSE,sha256=oXNGHTPcMKl8PPZWk_4NIWGAtiaQcTgVJ1hpnTcnKFA,1062
611
- roboticstoolbox_python-1.4.3.dist-info/RECORD,,
607
+ roboticstoolbox_python-1.4.4.dist-info/METADATA,sha256=VEV1p5AVn9XeJpI3VhDpvo9PYFA9mKufl8XwcQwYu1Y,24258
608
+ roboticstoolbox_python-1.4.4.dist-info/WHEEL,sha256=DJBbB-IMWy7eyPvSdm5L2t-BbrULtweJWf0YC-blkl0,95
609
+ roboticstoolbox_python-1.4.4.dist-info/entry_points.txt,sha256=m9xEI6rJsYQL0HosyeS-4TUPzxOR3kqx4vbJVVxwo7k,214
610
+ roboticstoolbox_python-1.4.4.dist-info/licenses/LICENSE,sha256=oXNGHTPcMKl8PPZWk_4NIWGAtiaQcTgVJ1hpnTcnKFA,1062
611
+ roboticstoolbox_python-1.4.4.dist-info/RECORD,,