|
4 | 4 | @author (Adapted by) Jesse Haviland
|
5 | 5 | """
|
6 | 6 |
|
| 7 | +from typing import Optional, Tuple |
7 | 8 | import numpy as np
|
8 | 9 | import roboticstoolbox as rtb
|
9 | 10 | import spatialgeometry as gm
|
@@ -121,6 +122,78 @@ def _find_standard_joint(joint: "Joint") -> "rtb.ET | None":
|
121 | 122 | std_joint = rtb.ET.SE3(SE3())
|
122 | 123 | return std_joint
|
123 | 124 |
|
| 125 | +def _find_joint_ets( |
| 126 | + joint: "Joint", |
| 127 | + parent_from_Rx_to_axis: Optional[SE3] = None, |
| 128 | +) -> Tuple["rtb.ETS", SE3]: |
| 129 | + """ |
| 130 | + Finds the ETS of a urdf joint object to be used by a Link. |
| 131 | +
|
| 132 | + This is based on the following fomula: |
| 133 | + ets(N) = axis(N-1).inv() * transl(N) * rot(N) * axis(N) * [Rx] |
| 134 | + where N is the current joint, and (N-1) the parent joint. |
| 135 | +
|
| 136 | + Attributes |
| 137 | + ---------- |
| 138 | + joint: Joint |
| 139 | + Joint from the urdf. |
| 140 | + Used to deduce: transl(N), rot(N), axis(N), [Rx] |
| 141 | + parent_from_Rx_to_axis: Optional[SE3] |
| 142 | + SE3 resulting from the axis orientation of the parent joint |
| 143 | + Used to deduce: axis(N-1) |
| 144 | +
|
| 145 | + Returns |
| 146 | + ------- |
| 147 | + ets: ETS |
| 148 | + ETS representing the joint. It ends with a joint. |
| 149 | + from_Rx_to_axis: SE3 |
| 150 | + SE3 representing the rotation of the axis attribute of the joint. |
| 151 | + """ |
| 152 | + joint_trans = SE3(joint.origin).t |
| 153 | + joint_rot = joint.rpy |
| 154 | + if parent_from_Rx_to_axis is None: |
| 155 | + parent_from_Rx_to_axis = SE3() |
| 156 | + |
| 157 | + joint_without_axis = SE3(joint_trans) * SE3.RPY(joint_rot) |
| 158 | + |
| 159 | + std_joint = _find_standard_joint(joint) |
| 160 | + is_simple_joint = std_joint is not None |
| 161 | + |
| 162 | + if is_simple_joint: |
| 163 | + from_Rx_to_axis = SE3() |
| 164 | + pure_joint = std_joint |
| 165 | + else: # rotates a Rx joint onto right axis |
| 166 | + joint_axis = joint.axis |
| 167 | + axis_of_Rx = np.array([1, 0, 0], dtype=float) |
| 168 | + |
| 169 | + rotation_from_Rx_to_axis = rotation_fromVec_toVec( |
| 170 | + from_this_vector=axis_of_Rx, to_this_vector=joint_axis |
| 171 | + ) |
| 172 | + from_Rx_to_axis = SE3(rotation_from_Rx_to_axis) |
| 173 | + |
| 174 | + if joint.joint_type in ("revolute", "continuous"): |
| 175 | + pure_joint = rtb.ET.Rx(flip=0) |
| 176 | + elif joint.joint_type == "prismatic": # I cannot test this case |
| 177 | + pure_joint = rtb.ET.tx(flip=0) |
| 178 | + else: |
| 179 | + pure_joint = rtb.ET.SE3(SE3()) |
| 180 | + |
| 181 | + listET = [] |
| 182 | + emptySE3 = SE3() |
| 183 | + |
| 184 | + # skips empty SE3 |
| 185 | + if not parent_from_Rx_to_axis == emptySE3: |
| 186 | + listET.append(rtb.ET.SE3(parent_from_Rx_to_axis.inv())) |
| 187 | + listET.append(rtb.ET.SE3(joint_without_axis)) |
| 188 | + if not from_Rx_to_axis == emptySE3: |
| 189 | + listET.append(rtb.ET.SE3(from_Rx_to_axis)) |
| 190 | + if not joint.joint_type == "fixed": |
| 191 | + listET.append(pure_joint) |
| 192 | + |
| 193 | + ets = rtb.ETS(listET) |
| 194 | + |
| 195 | + return ets, from_Rx_to_axis |
| 196 | + |
124 | 197 | class URDFType(object):
|
125 | 198 | """Abstract base class for all URDF types.
|
126 | 199 | This has useful class methods for automatic parsing/unparsing
|
@@ -1797,34 +1870,10 @@ def __init__(
|
1797 | 1870 | # constant part of link transform
|
1798 | 1871 | trans = SE3(joint.origin).t
|
1799 | 1872 | rot = joint.rpy
|
1800 |
| - |
1801 |
| - # Check if axis of rotation/tanslation is not 1 |
1802 |
| - if np.count_nonzero(joint.axis) < 2: |
1803 |
| - ets = rtb.ET.SE3(SE3(trans) * SE3.RPY(rot)) |
1804 |
| - else: |
1805 |
| - # Normalise the joint axis to be along or about z axis |
1806 |
| - # Convert rest to static ETS |
1807 |
| - v = joint.axis |
1808 |
| - u, n = unitvec_norm(v) |
1809 |
| - R = angvec2r(n, u) |
1810 |
| - |
1811 |
| - R_total = SE3.RPY(joint.rpy) * R |
1812 |
| - rpy = tr2rpy(R_total) |
1813 |
| - |
1814 |
| - ets = rtb.ET.SE3(SE3(trans) * SE3.RPY(rpy)) |
1815 |
| - |
1816 |
| - joint.axis = [0, 0, 1] |
1817 |
| - |
1818 |
| - var = _find_standard_joint(joint) |
1819 |
| - |
1820 |
| - is_not_fixed = joint.joint_type != "fixed" |
1821 |
| - if var is not None and is_not_fixed: |
1822 |
| - ets = ets * var |
1823 |
| - |
1824 |
| - if isinstance(ets, rtb.ET): |
1825 |
| - ets = rtb.ETS(ets) |
1826 |
| - |
1827 |
| - childlink.ets = ets |
| 1873 | + |
| 1874 | + # childlink.ets will be set later. |
| 1875 | + # This is because the fully defined parent link is required |
| 1876 | + # and this loop does not follow the parent-child order. |
1828 | 1877 |
|
1829 | 1878 | # joint limit
|
1830 | 1879 | try:
|
@@ -1855,6 +1904,64 @@ def __init__(
|
1855 | 1904 | # TODO, why did you put the base_link on the end?
|
1856 | 1905 | # easy to do it here
|
1857 | 1906 |
|
| 1907 | + # the childlink.ets will be set in there |
| 1908 | + self._recursive_axis_definition() |
| 1909 | + return |
| 1910 | + |
| 1911 | + def _recursive_axis_definition( |
| 1912 | + self, |
| 1913 | + parentname: Optional[str] = None, |
| 1914 | + parent_from_Rx_to_axis: Optional[SE3] = None, |
| 1915 | + ) -> None: |
| 1916 | + """ |
| 1917 | + Recursively sets the ets of all elinks (in place). |
| 1918 | +
|
| 1919 | + The ets of a link depends on the previous joint axis orientation. |
| 1920 | + In a URDF a joint is defined as the following ets: |
| 1921 | + ets = translation * rotation * axis * [Rx] * axis.inv() |
| 1922 | + where Rx is the variable joint ets, and "axis" rotates the variable joint |
| 1923 | + axis, BUT NOT the next link. Hence why Rx is rotated onto the axis, then |
| 1924 | + the rotation is canceled by axis.inv(). |
| 1925 | +
|
| 1926 | + A Link is requiered to end with a variable ets -- this is our [Rx]. |
| 1927 | + The previous formula must therefore be changed and requires recursion: |
| 1928 | + ets(N) = axis(N-1).inv() * transl(N) * rot(N) * axis(N) * [Rx] |
| 1929 | + where N is the current joint, and (N-1) the parent joint. |
| 1930 | + Chaining the ets of the second formula is equivalent to the first formula. |
| 1931 | +
|
| 1932 | + Attributes |
| 1933 | + ---------- |
| 1934 | + parentname: Optional[str] |
| 1935 | + Name of the parent link. |
| 1936 | + parent_from_Rx_to_axis: Optional[SE3] |
| 1937 | + SE3 resulting from the axis orientation of the parent joint |
| 1938 | + """ |
| 1939 | + if parentname is None: |
| 1940 | + base_link_exists = "base_link" in [j.name for j in self.elinks] |
| 1941 | + if base_link_exists: |
| 1942 | + parentname = "base_link" |
| 1943 | + else: |
| 1944 | + parentname = self.elinks[0].name |
| 1945 | + if parent_from_Rx_to_axis is None: |
| 1946 | + parent_from_Rx_to_axis = SE3() |
| 1947 | + |
| 1948 | + for joint in self._joints: # search all joint with identical parent |
| 1949 | + is_parent = joint.parent == parentname |
| 1950 | + if not is_parent: |
| 1951 | + continue # skips to next joint |
| 1952 | + |
| 1953 | + ets, from_Rx_to_axis = _find_joint_ets(joint, parent_from_Rx_to_axis) |
| 1954 | + |
| 1955 | + for child in self.elinks: # search all link with identical child |
| 1956 | + is_child = joint.child == child.name |
| 1957 | + if not is_child: |
| 1958 | + continue # skips to next link |
| 1959 | + |
| 1960 | + child.ets = ets # sets the ets of the joint |
| 1961 | + self._recursive_axis_definition( |
| 1962 | + parentname=child.name, parent_from_Rx_to_axis=from_Rx_to_axis |
| 1963 | + ) |
| 1964 | + |
1858 | 1965 | @property
|
1859 | 1966 | def name(self):
|
1860 | 1967 | """str : The name of the URDF."""
|
|
0 commit comments