Skip to content

Commit 650906b

Browse files
authored
Merge pull request #344 from compas-dev/reachability_map
Reachability Map
2 parents 9ff0013 + 248ea97 commit 650906b

39 files changed

+1168
-17
lines changed

.github/workflows/integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
curl --output $(python -c "import os;import compas_fab.backends.vrep.remote_api as v; print(os.path.dirname(v.__file__))")/remoteApi.so https://wolke.ethz.ch/s/idbWPRKzJ8pFkAn/download/remoteApi.so
3535
- name: Run integration tests
3636
run: |
37-
pytest --doctest-modules --ignore-glob="**ghpython/components/**.py"
37+
pytest --doctest-modules
3838
pytest docs
3939
- name: Tear down docker containers
4040
run: |

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Unreleased
1212

1313
**Added**
1414

15+
* Added ``compas_fab.robots.ReachabilityMap``
16+
* Added ``compas_fab.robots.DeviationVectorsGenerator``
17+
* Added ``compas_fab.robots.OrthonormalVectorsFromAxisGenerator``
18+
1519
**Changed**
1620

1721
**Fixed**

conftest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
def pytest_configure(config):
22
from twisted.internet import selectreactor
33
selectreactor.install()
4+
5+
6+
def pytest_ignore_collect(path):
7+
if "rhino" in str(path):
8+
return True
9+
10+
if "blender" in str(path):
11+
return True
12+
13+
if "ghpython" in str(path):
14+
return True
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. _examples_kinematics:
2+
3+
*******************************************************************************
4+
Planning: Reachability Map
5+
*******************************************************************************
6+
7+
.. toctree::
8+
:maxdepth: 2
9+
:glob:
10+
11+
07_reachability_map/*
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
*******************************************************************************
2+
Introduction to the Reachability Map
3+
*******************************************************************************
4+
5+
For workspace planning or robot path planning, it is useful to calculate and
6+
visualize the space of the robot's reachability, depending on its attached tool,
7+
obstacles in the environment and its own kinematic and geometric constraints.
8+
9+
The ``ReachabilityMap`` is a collection of all valid IK solutions, i.e. poses that the end-effector can reach, at specified locations. The map is built by discretizing the robot's environment, defining frames to be checked, and calculating the IK solutions for each frame.
10+
11+
The creation of this map depends on the availability of an analytical inverse
12+
kinematic solver for the used robot. Please checkout the kinematic backend for
13+
available robots.
14+
15+
Links
16+
=====
17+
* `Realuex (ROS's reachability map) <http://wiki.ros.org/reuleaux>`_
18+
19+
20+
Example 01: reachability map 1D
21+
===============================
22+
23+
Let's consider the following (abstract) example:
24+
25+
We have a UR5 and want the TCP of the robot to always orient itself towards a
26+
defined position in front of the robot. Therefore, we define a (half-)sphere
27+
with a certain radius and we evaluate points on this sphere. At each point, we then
28+
create a plane whose normal vector points towards the center of this sphere. From these planes
29+
we create frames for the robot's TCP. The function is written as a generator
30+
because the ``ReachabilityMap`` takes a ``Frame`` generator as input.
31+
32+
.. figure:: files/00_robot_halfsphere.jpg
33+
:figclass: figure
34+
:class: figure-img img-fluid
35+
36+
37+
.. code-block:: python
38+
39+
import math
40+
41+
from compas.geometry import Frame
42+
from compas.geometry import Plane
43+
from compas.geometry import Point
44+
from compas.geometry import Sphere
45+
46+
# 1. Define frames on a sphere
47+
sphere = Sphere((0.4, 0, 0), 0.15)
48+
49+
def points_on_sphere_generator(sphere):
50+
for theta_deg in range(0, 360, 20):
51+
for phi_deg in range(0, 90, 10): # only half-sphere
52+
theta = math.radians(theta_deg)
53+
phi = math.radians(phi_deg)
54+
x = sphere.point.x + sphere.radius * math.cos(theta) * math.sin(phi)
55+
y = sphere.point.y + sphere.radius * math.sin(theta) * math.sin(phi)
56+
z = sphere.point.z + sphere.radius * math.cos(phi)
57+
point = Point(x, y, z)
58+
axis = sphere.point - point
59+
plane = Plane((x, y, z), axis)
60+
f = Frame.from_plane(plane)
61+
# for the old UR5 model from ROS Kinetic is zaxis the xaxis
62+
yield [Frame(f.point, f.zaxis, f.yaxis)]
63+
64+
65+
Then we create a ``PyBulletClient`` (for collision checking), load the UR5 robot,
66+
set the analytical IK solver and define options for the IK solver.
67+
For simplicity, we do not add any tool or obstacles in the environment here, but in a
68+
real robot cell, this will usually be the case.
69+
70+
71+
.. code-block:: python
72+
73+
# 2. Set up robot cell
74+
from compas_fab.backends import AnalyticalInverseKinematics
75+
from compas_fab.backends import PyBulletClient
76+
77+
with PyBulletClient(connection_type='direct') as client:
78+
# load robot and define settings
79+
robot = client.load_ur5(load_geometry=True)
80+
ik = AnalyticalInverseKinematics(client)
81+
client.inverse_kinematics = ik.inverse_kinematics
82+
options = {"solver": "ur5", "check_collision": True, "keep_order": True}
83+
84+
Now we create a ``ReachabilityMap``. We calculate it passing the ``Frame``
85+
generator, the robot and the IK options. After calculation, we save the map as
86+
json for later visualization in Rhino/GH.
87+
88+
>>> # 3. Create reachability map 1D
89+
>>> map = ReachabilityMap() # doctest: +SKIP
90+
>>> map.calculate(points_on_sphere_generator(sphere), robot, options) # doctest: +SKIP
91+
>>> map.to_json(os.path.join(DATA, "reachability", "map1D.json")) # doctest: +SKIP
92+
93+
94+
`Link to full script <files/01_example_1D.py>`_
95+
96+
97+
Visualization
98+
=============
99+
100+
We can source the reachability map from the json file and use the ``Artist`` to
101+
visualize the saved frames by using the artist's function ``draw_frames``.
102+
103+
>>> map = ReachabilityMap.from_json(filepath) # doctest: +SKIP
104+
>>> artist = Artist(map) # doctest: +SKIP
105+
>>> frames = artist.draw_frames() # doctest: +SKIP
106+
107+
108+
.. figure:: files/00_robot_halfsphere.jpg
109+
:figclass: figure
110+
:class: figure-img img-fluid
111+
112+
113+
By default, the ``artist.draw()`` method returns points and colors for a point cloud,
114+
where the points are the positions of the frames and the colors are calculated
115+
from the score at the respective frame. The ``ReachabilityMap.score`` is
116+
the number of valid IK solutions for a frame. The default colormap is 'viridis'.
117+
118+
In the example below, the highest score is 4 (yellow) and the lowest score is 2 (violet).
119+
120+
.. figure:: files/01_robot_map1D.jpg
121+
:figclass: figure
122+
:class: figure-img img-fluid
123+
124+
125+
If you want to visualize the frames at a specific IK index (= number between 0-7), use the method
126+
``artist.draw_frames(ik_index=ik_index)``. If you compare the figure below
127+
with the figure of ``draw_frames``, you will see that a certain portion is not
128+
reachable at the selected IK index.
129+
130+
.. figure:: files/02_robot_frames_at_ik.jpg
131+
:figclass: figure
132+
:class: figure-img img-fluid
133+
134+
135+
Projects where the reachability map was applied
136+
===============================================
137+
138+
------------------------------------------------------------------------------------------
139+
`Adaptive Detailing <https://gramaziokohler.arch.ethz.ch/web/forschung/e/0/0/0/361.html>`_
140+
------------------------------------------------------------------------------------------
141+
142+
In this project, connections between structural elements are 3D printed in place,
143+
directly on top of parts, i.e. collision objects. A ``ReachabilityMap`` was created
144+
to capture the space where connections can be placed and ultimately find connecting
145+
geometries that the robot can print in between these objects. Printing process
146+
constraints can be included in the reachability map by choosing a meaningful
147+
``max_alpha`` in the ``DeviationVectorsGenerator``.
148+
149+
.. figure:: files/adaptive_detailing.png
150+
:figclass: figure
151+
:class: figure-img img-fluid
152+
153+
--------------------------------------------------------------------------------------------------------
154+
`Robotic 360° Light Painting Workshop <https://gramaziokohler.arch.ethz.ch/web/lehre/e/0/0/0/439.html>`_
155+
--------------------------------------------------------------------------------------------------------
156+
157+
This project served as inspiration for the presented examples 01-03. The robot TCP
158+
had to be oriented towards the 360° camera. The light paths were mapped on a hemisphere
159+
to maintain equal distance to the camera and little distortion of the designed paths.
160+
The reachability map was used to determine the best position and radius for the
161+
sphere with the UR5e robot model, the light tool, and the camera and tripods as
162+
collision objects.
163+
164+
.. |logo1| image:: files/workshop_sjsu_1.png
165+
:scale: 45%
166+
:align: middle
167+
.. |logo2| image:: files/workshop_sjsu_2.jpg
168+
:scale: 45%
169+
:align: middle
170+
171+
+---------+---------+
172+
| |logo1| | |logo2| |
173+
+---------+---------+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
*******************************************************************************
2+
Vector Generators
3+
*******************************************************************************
4+
5+
For using the ``ReachabilityMap`` we need a ``Frame`` generator function. In a
6+
simple case, this can be only a list of frames. However, sometimes it makes
7+
sense to generate multiple frames at a certain location that deviate a bit in
8+
orientation. We provide 2 helper vector generators with very verbose names:
9+
10+
* ``OrthonormalVectorsFromAxisGenerator``
11+
* ``DeviationVectorsGenerator``
12+
13+
Generate orthonormal vectors from an axis
14+
=========================================
15+
16+
The ``OrthonormalVectorsFromAxisGenerator`` generates vectors that are orthonormal a given axis.
17+
18+
In the example below, the given axis is visualized in red, and the generated vectors in black.
19+
20+
.. figure:: files/03_ortho_vectors.jpg
21+
:figclass: figure
22+
:class: figure-img img-fluid
23+
24+
>>> import math
25+
>>> from compas.geometry import Vector
26+
>>> from compas_fab.robots import OrthonormalVectorsFromAxisGenerator
27+
>>> zaxis = Vector(0, 0, 1)
28+
>>> max_alpha = 60
29+
>>> for xaxis in OrthonormalVectorsFromAxisGenerator(zaxis, math.radians(max_alpha)):
30+
... print(xaxis)
31+
Vector(0.000, -1.000, 0.000)
32+
Vector(0.866, -0.500, 0.000)
33+
Vector(0.866, 0.500, 0.000)
34+
Vector(0.000, 1.000, 0.000)
35+
Vector(-0.866, 0.500, 0.000)
36+
Vector(-0.866, -0.500, 0.000)
37+
38+
39+
Generate vectors that deviate
40+
=============================
41+
42+
The ``DeviationVectorsGenerator`` generates equally distributed vectors that deviate from the passed one by a maximal angle of max_alpha.
43+
44+
In the example below, the given axis is visualized in red, and the generated vectors in black.
45+
46+
.. figure:: files/04_devi_vectors.jpg
47+
:figclass: figure
48+
:class: figure-img img-fluid
49+
50+
>>> import math
51+
>>> from compas_fab.robots import DeviationVectorsGenerator
52+
>>> zaxis = Vector(0, 0, 1)
53+
>>> max_alpha = 40
54+
>>> step = 1
55+
>>> for axis in DeviationVectorsGenerator(zaxis, math.radians(max_alpha), step):
56+
... print(axis)
57+
Vector(0.000, 0.000, 1.000)
58+
Vector(-0.643, 0.000, 0.766)
59+
Vector(-0.321, -0.557, 0.766)
60+
Vector(0.321, -0.557, 0.766)
61+
Vector(0.643, -0.000, 0.766)
62+
Vector(0.321, 0.557, 0.766)
63+
Vector(-0.321, 0.557, 0.766)
64+
65+
66+
Or another example with using ``max_angle = 60`` and ``step = 2``.
67+
68+
.. figure:: files/05_devi_vectors_2.jpg
69+
:figclass: figure
70+
:class: figure-img img-fluid
71+
72+
73+
Generate frames
74+
===============
75+
76+
Now having these two vector generators, we can combine them for generating frames.
77+
78+
.. code-block:: python
79+
80+
from compas.geometry import Frame
81+
from compas.geometry import Point
82+
from compas.geometry import Vector
83+
from compas_fab.robots import DeviationVectorsGenerator
84+
from compas_fab.robots import OrthonormalVectorsFromAxisGenerator
85+
86+
def frame_generator():
87+
for i in range(6):
88+
pt = Point(0, 0, 0) + Vector(0, i, 0)
89+
zaxis = Vector(0, 0, 1)
90+
for axis in DeviationVectorsGenerator(zaxis, math.radians(40), 1):
91+
for xaxis in OrthonormalVectorsFromAxisGenerator(axis, math.radians(60)):
92+
yaxis = axis.cross(xaxis)
93+
yield Frame(pt, xaxis, yaxis)
94+
95+
.. figure:: files/06_devi_frames.jpg
96+
:figclass: figure
97+
:class: figure-img img-fluid

0 commit comments

Comments
 (0)