Skip to content

Commit fbb88ba

Browse files
committed
Merge branch 'master' into simplify-ros-load
# Conflicts: # CHANGELOG.rst
2 parents 41648c6 + 272a062 commit fbb88ba

File tree

7 files changed

+607
-21
lines changed

7 files changed

+607
-21
lines changed

AUTHORS.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ If you use COMPAS FAB in a project, please use the following citation:
1717
Helmreich, M. and
1818
Gandia, A. and
1919
Ma, Z. and
20-
Ariza, I.
20+
Ariza, I. and
21+
Pacher, M.
2122
},
2223
howpublished={https://github.com/compas-dev/compas\_fab/},
2324
note={Gramazio Kohler Research, ETH Z\"{u}rich},
@@ -37,3 +38,4 @@ Authors
3738
* Augusto Gandia <[email protected]> `@augustogandia <https://github.com/augustogandia>`_
3839
* Zhao Ma <[email protected]> `@xarthurx <https://github.com/xarthurx>`_
3940
* Inés Ariza <[email protected]> `@inesariza <https://github.com/inesariza>`_
41+
* Matteo Pacher <[email protected]> `@matteo-pacher <https://github.com/matteo-pacher>`_

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Unreleased
1313
**Added**
1414

1515
* Added ``load_robot`` method to ROS client to simplify loading robots from running ROS setup.
16+
* Added ``compas_fab.robots.Wrench``: a Wrench class representing force in free space, separated into its linear (force) and angular (torque) parts.
17+
* Added ``compas_fab.robots.Inertia``: a Inertia class representing spatial distribution of mass in a rigid body
1618

1719
**Changed**
1820

Lines changed: 144 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
from __future__ import absolute_import
22

3-
from .std_msgs import ROSmsg
4-
from .std_msgs import Header
5-
from .std_msgs import Time
6-
73
from compas.geometry import Frame
84

5+
import compas_fab.robots
6+
7+
from .std_msgs import Header
8+
from .std_msgs import ROSmsg
9+
910

1011
class Point(ROSmsg):
11-
"""http://docs.ros.org/kinetic/api/geometry_msgs/html/msg/Point.html
12+
"""http://docs.ros.org/api/geometry_msgs/html/msg/Point.html
1213
"""
14+
1315
def __init__(self, x, y, z):
1416
self.x = x
1517
self.y = y
@@ -20,10 +22,12 @@ def from_msg(cls, msg):
2022
x, y, z = msg['x'], msg['y'], msg['z']
2123
return cls(x, y, z)
2224

25+
2326
class Quaternion(ROSmsg):
24-
"""http://docs.ros.org/kinetic/api/geometry_msgs/html/msg/Quaternion.html
27+
"""http://docs.ros.org/api/geometry_msgs/html/msg/Quaternion.html
2528
"""
26-
def __init__(self, x=0. ,y=0., z=0., w=1.):
29+
30+
def __init__(self, x=0., y=0., z=0., w=1.):
2731
self.x = x
2832
self.y = y
2933
self.z = z
@@ -34,12 +38,14 @@ def from_frame(cls, frame):
3438
qw, qx, qy, qz = frame.quaternion
3539
return cls(qx, qy, qz, qw)
3640

41+
3742
class Pose(ROSmsg):
38-
"""http://docs.ros.org/kinetic/api/geometry_msgs/html/msg/Pose.html
43+
"""http://docs.ros.org/api/geometry_msgs/html/msg/Pose.html
3944
"""
45+
4046
def __init__(self, position=None, orientation=None):
41-
self.position = position if position else Point(0,0,0)
42-
self.orientation = orientation if orientation else Quaternion(0,0,0,1)
47+
self.position = position if position else Point(0, 0, 0)
48+
self.orientation = orientation if orientation else Quaternion(0, 0, 0, 1)
4349

4450
@classmethod
4551
def from_frame(cls, frame):
@@ -59,37 +65,155 @@ def from_msg(cls, msg):
5965
orientation = Quaternion.from_msg(msg['orientation'])
6066
return cls(position, orientation)
6167

68+
6269
class PoseStamped(ROSmsg):
63-
"""http://docs.ros.org/melodic/api/geometry_msgs/html/msg/PoseStamped.html
70+
"""http://docs.ros.org/api/geometry_msgs/html/msg/PoseStamped.html
6471
"""
65-
def __init__(self, header=Header(), pose=Pose()):
66-
self.header = header
67-
self.pose = pose
72+
73+
def __init__(self, header=None, pose=None):
74+
self.header = header or Header()
75+
self.pose = pose or Pose()
6876

6977
@classmethod
7078
def from_msg(cls, msg):
7179
header = Header.from_msg(msg['header'])
7280
pose = Pose.from_msg(msg['pose'])
7381
return cls(header, pose)
7482

83+
7584
class Vector3(ROSmsg):
7685
"""http://docs.ros.org/api/geometry_msgs/html/msg/Vector3.html
7786
"""
87+
7888
def __init__(self, x=0., y=0., z=0.):
7989
self.x = x
8090
self.y = y
8191
self.z = z
8292

93+
@classmethod
94+
def from_msg(cls, msg):
95+
x, y, z = msg['x'], msg['y'], msg['z']
96+
return cls(x, y, z)
97+
98+
8399
class Transform(ROSmsg):
84100
"""http://docs.ros.org/api/geometry_msgs/html/msg/Transform.html
85101
"""
86-
def __init__(self, translation=Vector3(), rotation=Quaternion()):
87-
self.translation = translation
88-
self.rotation = rotation
102+
103+
def __init__(self, translation=None, rotation=None):
104+
self.translation = translation or Vector3()
105+
self.rotation = rotation or Quaternion()
106+
89107

90108
class Twist(ROSmsg):
91109
"""http://docs.ros.org/api/geometry_msgs/html/msg/Twist.html
92110
"""
93-
def __init__(self, linear=Vector3(), angular=Vector3()):
94-
self.linear = linear
95-
self.angular = angular
111+
112+
def __init__(self, linear=None, angular=None):
113+
self.linear = linear or Vector3()
114+
self.angular = angular or Vector3()
115+
116+
117+
class Wrench(ROSmsg):
118+
"""http://docs.ros.org/api/geometry_msgs/html/msg/Wrench.html
119+
120+
This represents force in free space, separated into its linear and angular parts.
121+
122+
Examples
123+
--------
124+
>>> wrench = compas_fab.robots.Wrench([0, 0, -98], [0, 0, 0])
125+
>>> ros_wrench = Wrench.from_wrench(wrench)
126+
>>> ros_wrench.msg
127+
{'force': {'x': 0.0, 'y': 0.0, 'z': -98.0}, 'torque': {'x': 0.0, 'y': 0.0, 'z': 0.0}}
128+
>>> ros_wrench.wrench
129+
Wrench(Vector(0.000, 0.000, -98.000), Vector(0.000, 0.000, 0.000))
130+
"""
131+
132+
def __init__(self, force=None, torque=None):
133+
self.force = force or Vector3()
134+
self.torque = torque or Vector3()
135+
136+
@classmethod
137+
def from_msg(cls, msg):
138+
force = Vector3.from_msg(msg['force'])
139+
torque = Vector3.from_msg(msg['torque'])
140+
return cls(force, torque)
141+
142+
@classmethod
143+
def from_wrench(cls, wrench):
144+
force = wrench.force
145+
torque = wrench.torque
146+
return cls(Vector3(*list(force)), Vector3(*list(torque)))
147+
148+
@property
149+
def wrench(self):
150+
force = [self.force.x, self.force.y, self.force.z]
151+
torque = [self.torque.x, self.torque.y, self.torque.z]
152+
return compas_fab.robots.Wrench(force, torque)
153+
154+
155+
class WrenchStamped(ROSmsg):
156+
"""http://docs.ros.org/api/geometry_msgs/html/msg/WrenchStamped.html
157+
158+
A wrench with reference coordinate frame and timestamp.
159+
"""
160+
161+
def __init__(self, header=None, wrench=None):
162+
self.header = header or Header()
163+
self.wrench = wrench or Wrench()
164+
165+
@classmethod
166+
def from_msg(cls, msg):
167+
header = Header.from_msg(msg['header'])
168+
wrench = Wrench.from_msg(msg['wrench'])
169+
return cls(header, wrench)
170+
171+
172+
class Inertia(ROSmsg):
173+
"""http://docs.ros.org/api/geometry_msgs/html/msg/Inertia.html
174+
175+
Examples
176+
--------
177+
>>> inertia = compas_fab.robots.Inertia([[0] * 3] * 3, 1., [0.1, 3.1, 4.4])
178+
>>> ros_inertia = Inertia.from_inertia(inertia)
179+
>>> ros_inertia.msg
180+
{'m': 1.0, 'com': {'x': 0.1, 'y': 3.1, 'z': 4.4}, 'ixx': 0.0, 'ixy': 0.0, 'ixz': 0.0, 'iyy': 0.0, 'iyz': 0.0, 'izz': 0.0}
181+
>>> ros_inertia.inertia
182+
Inertia([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], 1.0, Point(0.100, 3.100, 4.400))
183+
"""
184+
185+
def __init__(self, m=0.0, com=None, ixx=0., ixy=0., ixz=0., iyy=0., iyz=0., izz=0.):
186+
self.m = float(m) # Mass [kg]
187+
self.com = com or Vector3() # Center of mass [m]
188+
self.ixx = float(ixx)
189+
self.ixy = float(ixy)
190+
self.ixz = float(ixz)
191+
self.iyy = float(iyy)
192+
self.iyz = float(iyz)
193+
self.izz = float(izz)
194+
195+
@classmethod
196+
def from_msg(cls, msg):
197+
com = Vector3.from_msg(msg['com'])
198+
return cls(msg['m'], com, msg['ixx'], msg['ixy'], msg['ixz'], msg['iyy'], msg['iyz'], msg['izz'])
199+
200+
@classmethod
201+
def from_inertia(cls, inertia):
202+
m = inertia.mass
203+
com = Vector3(*list(inertia.center_of_mass))
204+
ixx, ixy, ixz = inertia.inertia_tensor[0]
205+
ixy, iyy, iyz = inertia.inertia_tensor[1]
206+
izz = inertia.inertia_tensor[2][2]
207+
return cls(m, com, ixx, ixy, ixz, iyy, iyz, izz)
208+
209+
@property
210+
def inertia(self):
211+
inertia_tensor = [[self.ixx, self.ixy, self.ixz],
212+
[self.ixy, self.iyy, self.iyz],
213+
[self.ixz, self.iyz, self.izz]]
214+
return compas_fab.robots.Inertia(inertia_tensor, self.m, [self.com.x, self.com.y, self.com.z])
215+
216+
217+
if __name__ == "__main__":
218+
import doctest
219+
doctest.testmod()

src/compas_fab/robots/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
RobotSemantics
2020
Configuration
2121
Duration
22+
Wrench
23+
Inertia
2224
2325
Path planning
2426
-------------
@@ -78,5 +80,7 @@
7880
from .semantics import *
7981
from .time_ import *
8082
from .trajectory import *
83+
from .wrench import *
84+
from .inertia import *
8185

8286
__all__ = [name for name in dir() if not name.startswith('_')]

src/compas_fab/robots/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from compas.datastructures import Mesh
66
from compas.geometry import Frame
77
from compas.geometry import Scale
8+
from compas.geometry import Rotation
89

910
import compas_fab
1011
from compas_fab.backends import RosClient
@@ -19,6 +20,7 @@ def add_imports(doctest_namespace):
1920
doctest_namespace["Frame"] = Frame
2021
doctest_namespace["Scale"] = Scale
2122
doctest_namespace["compas_fab"] = compas_fab
23+
doctest_namespace["Rotation"] = Rotation
2224

2325

2426
@pytest.fixture(scope="session", autouse=True)

src/compas_fab/robots/inertia.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from compas.geometry import Point
2+
3+
4+
class Inertia():
5+
"""The moments of inertia represent the spatial distribution of mass in a rigid body.
6+
7+
It depends on the mass, size, and shape of a rigid body with units of
8+
[mass * m**2]. The moments of inertia can be expressed as the components of
9+
a symmetric positive-definite 3x3 matrix, with 3 diagonal elements, and 3
10+
unique off-diagonal elements. Each inertia matrix is defined relative to a
11+
coordinate frame or set of axes.
12+
13+
Attributes
14+
----------
15+
inertia_tensor : list of float
16+
A symmetric positive-definite 3x3 matrix:
17+
| ixx ixy ixz |
18+
| ixy iyy iyz |
19+
| ixz iyz izz |
20+
with [ixx, iyy, izz] as the principal moments of inertia and
21+
[ixy, ixz, iyz] as the products of inertia.
22+
mass: float
23+
The mass of the object in kg.
24+
center_of_mass : :class:`Point`
25+
The center of mass of the object in meters.
26+
27+
Examples
28+
--------
29+
>>> inertia = Inertia([[0] * 3] * 3, 1., Point(0.1, 3.1, 4.4))
30+
>>> inertia
31+
Inertia([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 1.0, Point(0.100, 3.100, 4.400))
32+
>>> inertia.principal_moments
33+
[0, 0, 0]
34+
35+
Notes
36+
-----
37+
Assuming uniform mass density, inertial data can be obtained using the
38+
free software MeshLab, refering to this great tutorial:
39+
http://gazebosim.org/tutorials?tut=inertia
40+
"""
41+
42+
def __init__(self, inertia_tensor, mass, center_of_mass):
43+
self.inertia_tensor = inertia_tensor
44+
self.mass = mass
45+
self.center_of_mass = Point(*center_of_mass)
46+
47+
@property
48+
def principal_moments(self):
49+
"""Returns the diagonal elements of the inertia tensor [ixx, iyy, izz]
50+
"""
51+
I = self.inertia_tensor
52+
return [I[0][0], I[1][1], I[2][2]]
53+
54+
def __repr__(self):
55+
return "Inertia({0}, {1}, {2})".format(self.inertia_tensor, self.mass, self.center_of_mass)
56+
57+
@staticmethod
58+
def calculate_inertia_tensor(cls, mesh):
59+
"""Returns the inertia tensor.
60+
"""
61+
raise NotImplementedError
62+

0 commit comments

Comments
 (0)