Pictures: screws.viz¶
Pictures and animations of screw axes. Everything here needs matplotlib
(screws[plot]), imported only when a function is called.
Static: draw_robot, draw_screw, explore (static.py). Moving: animate_twist, animate_screw, compare_frames (animate.py), with the scenes door, drawer and screwdriver (scenes.py) and the Mesh, box and prism to build your own body. Jacobians and ellipsoids: draw_jacobian, draw_ellipsoid, animate_robot, compare_paths (jacobian.py).
Animation
dataclass
¶
A finished animation: the figure, the FuncAnimation, and the numbers drawn in each frame (theta, T_sb, and per entry point V_s, V_b, Ad, ...) for checking by hand.
Source code in screws/viz/animate.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | |
save ¶
save(path, fps=None, dpi=None)
Write a .gif (Pillow, no extra needed) or .mp4 (ffmpeg from screws[video]).
Source code in screws/viz/animate.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
ColumnDrawing
dataclass
¶
One column of the Jacobian as drawn, in the drawing's frame: the column S (of J_s, or J_b in the body frame), its axis line (q a point on it, the joint's own origin; s_hat; pitch h, inf for a prismatic joint), and the velocity it gives the tool point at unit rate, R J_bv[:, i] (the column itself, in 2D, of the notes' J(theta)).
Source code in screws/viz/jacobian.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
EllipsoidDrawing
dataclass
¶
What draw_ellipsoid drew: the manipulability ellipsoid, the force ellipsoid if asked for, and the scales they were drawn at.
Source code in screws/viz/jacobian.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
JacobianDrawing
dataclass
¶
What draw_jacobian drew: the figure, the matrix printed with its rank, and one ColumnDrawing per joint (all in the drawing's frame).
Source code in screws/viz/jacobian.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
Mesh
dataclass
¶
Polygons sharing a vertex array: vertices (V, 3), faces as lists of vertex indices, one colour per face (or one for all).
Source code in screws/viz/scenes.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | |
face_colours ¶
face_colours()
One colour per face.
Source code in screws/viz/scenes.py
37 38 39 40 41 | |
transformed ¶
transformed(T)
The same mesh moved by the transform T.
Source code in screws/viz/scenes.py
43 44 45 46 | |
Scene
dataclass
¶
One screw motion, ready to animate. S is written in {s}; body meshes and points are in {b} coordinates; theta is (start, end) in radians (metres when S is a translation).
Source code in screws/viz/scenes.py
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
JointDrawing
dataclass
¶
What was drawn for one joint, and the numbers behind it (all in the drawing's frame).
Source code in screws/viz/static.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
RobotDrawing
dataclass
¶
What draw_robot drew: the skeleton, and one JointDrawing per joint drawn (all in the drawing's frame).
Source code in screws/viz/static.py
70 71 72 73 74 75 76 77 78 79 80 | |
ScrewDrawing
dataclass
¶
What draw_screw drew: the axis (omega, v), a point q on it, the pitch, and the two terms of v = -omega x q + h omega.
Source code in screws/viz/static.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | |
animate_screw ¶
animate_screw(
S,
T0=None,
*,
body=None,
points=None,
theta=None,
frames=80,
fps=20,
show_matrix=True,
arrow_scale=None,
figsize=None,
)
The screw axis S = (omega, v), written in {s}, swept over theta: the body at T(theta) = e^{[S] theta} T(0). Beside it the line read off S (q = omega x v, s-hat and h = omega . v for a unit omega) and, with show_matrix, T(theta) itself as theta grows. S may be a Scene. MR 3.3.3.2, eq. 3.85; notes 3.10.
Source code in screws/viz/animate.py
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | |
animate_twist ¶
animate_twist(
V,
T0=None,
*,
body=None,
points=None,
duration=4.0,
fps=20,
arrow_scale=None,
figsize=(6.4, 5.6),
)
A body moving under the constant twist V = (omega, v), written in {s}, for duration seconds: e^{[V] t} T0. Pass a Scene (door(), drawer(), screwdriver()) to play its motion in duration seconds, V = S thetadot. The screw axis is dashed with s-hat and the pitch, one point's path is traced in grey (a helix; a circle at zero pitch), and the purple arrows are the velocities omega x p + v of the marked points, drawn arrow_scale seconds long. Colours as everywhere in screws.viz: purple a velocity, red, green, blue a frame. MR 3.3.2; notes 3.10.
Source code in screws/viz/animate.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | |
compare_frames ¶
compare_frames(
scene, *, frames=80, fps=20, view=None, figsize=(12, 8)
)
One motion drawn twice, per unit rate (thetadot = 1). Left, the space twist V_s: its v_s is the velocity of the body point at the {s} origin, the body imagined to fill all of space; the grey marker is the point that was there at theta = 0, carried off by the motion while the point now at the origin keeps the same velocity. Right, the body twist V_b: v_b is the velocity of the point at {b}'s origin, in {b}'s axes, the same at every theta. Below: V_s = [Ad_{T_sb(theta)}] V_b, Ad changing, V_s not. view is (elev, azim); the default looks down more steeply than the scene's own view, so the {s} origin and the axis separate. MR 3.3.2, eq. 3.83; notes 3.10.
Source code in screws/viz/animate.py
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 | |
animate_robot ¶
animate_robot(
robot,
theta_start,
theta_end=None,
*,
path="joint",
frames=60,
fps=20,
show=SHOW_ANIMATION,
kind="linear",
force=False,
chart=True,
matrix=False,
planar=False,
scale=None,
view=None,
figsize=None,
)
The robot moving from theta_start to theta_end at a constant rate, its Jacobian and ellipsoid following, the ellipsoid's semi-axes charted below.
path="joint" (default) moves every joint at a constant rate, a straight line in joint space; path="screw" moves the tool at a constant twist, the screw motion from T_sb(theta_start) to T_sb(theta_end), the joints following by inverse kinematics seeded from the frame before. Or pass one (N, n) array of configurations as theta_start, such as a joint_trajectory or an IKResult.history, and it is played as given. show takes the items of draw_jacobian and draw_ellipsoid plus "path" (the tool's path) and "velocity" (the tool point's velocity, d/ds of its position). The chart plots the semi-axes against s from 0 to 1, so a singularity shows as one going to zero. matrix prints J_s(theta) beside the robot (J with planar=True, which draws a planar arm flat). .data holds s, theta, dtheta (per unit s), T_sb, J_s, J_b, V_s, V_b, tool, radii and measures for every frame. MR 5.4, 9.2; notes 5.5.
Source code in screws/viz/jacobian.py
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 | |
compare_paths ¶
compare_paths(
robot,
theta_start,
theta_end,
*,
frames=60,
fps=20,
show=("skeleton", "frames", "path", "velocity"),
planar=False,
view=None,
figsize=(12.5, 7.6),
)
The same change of configuration made two ways, side by side. Left, constant joint rates: theta-dot is fixed and the tool's twist V_b = J_b(theta) theta-dot changes, so the tool does not follow a screw. Right, a constant twist: the tool follows the one screw motion from start to end (a helix about its axis) and the joint rates change, found by inverse kinematics. Each panel charts the quantity that changes; the caption gives the one held fixed. .data["joint"] and .data["screw"] hold each side's numbers, as animate_robot's. MR 9.2, eqs. 9.7 and 9.14; notes 3.10, 9.2.
Source code in screws/viz/jacobian.py
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 | |
draw_ellipsoid ¶
draw_ellipsoid(
robot,
theta,
*,
kind="linear",
force=False,
show=None,
scale=None,
force_scale=None,
planar=False,
ax=None,
figsize=None,
labels=True,
view=None,
)
The robot at theta with its manipulability ellipsoid at the tool (in VELOCITY, its principal semi-axes drawn) and, with force=True, the force ellipsoid (grey, dashed).
kind="linear" is the velocity of the tool point, from J_b's linear rows as MR advises, turned into {s} so it sits where the tool is; kind="angular" is the angular velocity, from J_s's angular rows. scale is drawing metres per unit of semi-axis (m/s, or rad/s); the default makes the longest semi-axis 0.3 of the robot's reach. force_scale has its own default, the same rule for the force ellipsoid; pass force_scale=scale to draw both at one scale, as the notes do. Ellipsoids are drawn at their true size, and the frame does not grow to hold a long one: it runs out of the frame. A force ellipsoid at a singularity is unbounded along the lost direction, so it runs out on both sides, two lines in the plane or a cylinder in space; that axis is dotted and marked ∞. The title gives the semi-axes and mu1, mu2, mu3. For a chain mixing revolute and prismatic joints the semi-axes mix units and no unit is printed (notes 5.5). Every robot is drawn in 3D, where a planar arm's ellipsoid is flat and its mu are infinite; planar=True draws it flat, the ellipse of the notes' 2 x n J(theta). MR 5.4; notes 5.1, 5.5.
Source code in screws/viz/jacobian.py
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 | |
draw_jacobian ¶
draw_jacobian(
robot,
theta,
*,
frame="space",
dtheta=None,
show=SHOW_JACOBIAN,
matrix=True,
planar=False,
ax=None,
figsize=None,
labels=True,
view=None,
)
The robot at theta with each column of its Jacobian drawn as what it is.
Column i of J_s(theta) is joint i's screw axis now, S_i carried by the joints inboard of it; it is drawn as draw_robot(axes="current") draws it, a dashed line through the joint in the joints' blue (with its omega-hat for a revolute joint), labelled J_si. At the tool, an arrow per joint, numbered, gives the velocity of the tool point when that joint alone turns at unit rate, R_sb J_bv[:, i]. With dtheta the arrows are scaled by dtheta_i and laid head to tail, summing to the tool point's velocity p-dot. frame="body" draws in {b} and prints J_b; the lines are the same lines, seen from the tool. planar=True draws a planar arm flat: the arrows are then the columns J_i(theta) of the notes' 2 x n J(theta), which is the matrix printed, and the joint axes, all out of the page, are marked in the joint circles. The matrix is printed beside the robot (only when this function makes the figure). The title gives the rank, and says singular when it is below min(6, n) (min(2, n) in 2D). MR 5.1.1-5.1.2, 5.3; notes 5.1, 5.2, 5.4.
Source code in screws/viz/jacobian.py
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 | |
is_planar ¶
is_planar(robot)
True when the robot can be drawn flat with planar=True: every joint axis along z and every v in the xy plane (Robot.is_planar).
Source code in screws/viz/jacobian.py
49 50 51 52 | |
box ¶
box(lo, hi, colours=WOOD, alpha=1.0)
An axis-aligned box from corner lo to corner hi.
Source code in screws/viz/scenes.py
49 50 51 52 53 54 | |
door ¶
door()
The door of notes 3.10: a vertical hinge through q = (2, 0, 0), {b} on the handle at (2.9, 0, 1), S_s = (0, 0, 1, 0, -2, 0), S_b = (0, 0, 1, 0, 0.9, 0), opened 0 to 100 deg.
Source code in screws/viz/scenes.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
drawer ¶
drawer()
A drawer sliding 0.45 m out of a cabinet along +y: omega = 0, infinite pitch, every point with the same velocity.
Source code in screws/viz/scenes.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
prism ¶
prism(
base,
axis,
radius,
length,
sides=6,
colours=STEEL,
alpha=1.0,
)
A right prism with a regular polygon section: from base along the unit axis.
Source code in screws/viz/scenes.py
57 58 59 60 61 62 63 64 65 66 67 68 69 | |
screw_between ¶
screw_between(
T_start, T_end, *, body=None, name="screw motion"
)
The one screw motion that carries the frame T_start to T_end (both in {s}): from [S] theta = log(T_end T_start^{-1}), with S a unit screw axis (|omega| = 1, or |v| = 1 for a translation) and theta the angle turned (the distance slid). Every rigid-body displacement is a screw motion (Chasles); this shows which one. The frame's origin traces the helix, and the start and end frames are drawn faded. MR 3.3.3.2; notes 3.7.
Source code in screws/viz/scenes.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
screwdriver ¶
screwdriver(pitch=0.01)
A screwdriver driving down: s_hat = -z through the origin, two turns. The pitch, 0.01 m/rad by default (6.3 cm per turn), is exaggerated so the advance shows.
Source code in screws/viz/scenes.py
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
draw_robot ¶
draw_robot(
robot,
theta=None,
*,
frame="space",
axes="home",
joints=None,
show=SHOW_DEFAULT,
ax=None,
axis_length=None,
figsize=(8, 7),
labels=True,
)
Draw a robot's skeleton at theta (default: home) with one screw axis per joint.
axes="home" (default) draws the axes at home whatever theta is: the S_i in {s} (frame="space"), or the B_i in {b} (frame="body"), where {b} at home sits at the origin and the base is wherever M^{-1} puts it. axes="current" draws each axis where the joints inboard of it have carried it at theta: the columns of J_s(theta) in {s}, or of J_b(theta) in {b} at theta. At home the two are the same picture. draw_jacobian is the current one with the velocities the joints give the tool and the matrix beside it.
The skeleton joins the joints' positions when the robot knows them (a URDF, a scene, or joint_frames_home); otherwise only the base and the tool are marked. joints selects which joints get axes (indices from 0; default all). show is any of "skeleton", "frames", "axes" (the axis lines), "omega" (the omega arrows at q_i), "construction" (q_i, -omega_i x q_i, h_i omega_i and v_i at the origin; with axes="current", J_s built column by column, J_si = (omega_i, -omega_i x q_i) at zero pitch, MR 5.1.1). Returns a RobotDrawing whose joints carry the vectors drawn, for checking by hand. MR 4.1.1, 4.1.3, 5.1; notes 4.1, 4.2, 5.1.
Source code in screws/viz/static.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | |
draw_screw ¶
draw_screw(S, *, ax=None, figsize=(6, 6), labels=True)
One screw axis S = (omega, v) on its own: the axis, omega at q, and how v is built from q (the point on the axis nearest the origin), -omega x q and the pitch term h omega. MR 3.3.2.2, eq. 3.77; notes 3.5.
Source code in screws/viz/static.py
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 | |
explore ¶
explore(robot, *, draw=None, **kwargs)
Joint sliders in a notebook: redraws a drawing at the slider values, by default draw_robot; pass draw=viz.draw_jacobian or viz.draw_ellipsoid to watch the Jacobian or the ellipsoids change instead. kwargs go to the drawing (frame, show, force, ...). Ranges come from joint_limits when the robot has them. Needs ipywidgets.
Source code in screws/viz/static.py
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | |