Kinematics
The basic building blocks of a WorkCell are Frames. These are ordered in a tree-structure where the root node always is the WORLD frame. All frames has a parent which their position and orientation is relative to. Descending in this tree accumulating frame transformations is basically forward kinematics. The Kinematics class is a utility class for calculating forward kinematics.
Forward Kinematics
The examples below show how to calculate the transformation between two frames in the WorkCell. The final lines shows how a DAF frame can be attached to another frame. In this case the mframe is attached to the end frame of a serial device.
C++
1#include <rw/kinematics/MovableFrame.hpp>
2#include <rw/kinematics/Kinematics.hpp>
3#include <rw/kinematics/State.hpp>
4#include <rw/math/Transform3D.hpp>
5#include <rw/models/SerialDevice.hpp>
6
7using namespace rw::kinematics;
8using rw::math::Transform3D;
9using rw::models::SerialDevice;
10
11void fwdKinematics(SerialDevice::Ptr sdevice,
12 Frame* frame, MovableFrame* mframe, State& state)
13{
14 // calculate the transform from one frame to another
15 Transform3D<> fTmf = Kinematics::frameTframe(frame, mframe, state);
16 // calculate the transform from world to frame
17 Transform3D<> wTmf = Kinematics::worldTframe( mframe, state );
18 // we can find the world to frame transform by a little jogling
19 Transform3D<> wTf = wTmf * inverse(fTmf);
20 // test if frame is a dynamic attachable frame
21 if( Kinematics::isDAF( mframe ) ){
22 // attach mframe to end of serial device
23 Kinematics::gripFrame(mframe, sdevice->getEnd(), state);
24 }
25}
See C++ Interface for more information about compilation and execution.
Python
1from sdurw import *
2
3def fwdKinematics(device, frame, mframe, state):
4 # calculate the transform from one frame to another
5 fTmf = Kinematics.frameTframe(frame, mframe, state);
6 # calculate the transform from world to frame
7 wTmf = Kinematics.worldTframe( mframe, state );
8 # we can find the world to frame transform by a little jogling
9 wTf = wTmf * inverse(fTmf);
10 # test if frame is a dynamic attachable frame
11 if Kinematics.isDAF( mframe ):
12 # attach mframe to end of serial device
13 Kinematics.gripFrame(mframe, sdevice.getEnd(), state);
See Python interface for more information about execution.
Java
1import org.robwork.sdurw.*;
2import static org.robwork.sdurw.sdurw.inverse;
3
4public class ExFwdKinematics {
5 public static void fwdKinematics(SerialDevicePtr sdevice,
6 Frame frame, MovableFrame mframe, State state)
7 {
8 // calculate the transform from one frame to another
9 Transform3Dd fTmf = Kinematics.frameTframe(frame, mframe, state);
10 // calculate the transform from world to frame
11 Transform3Dd wTmf = Kinematics.worldTframe( mframe, state );
12 // we can find the world to frame transform by a little jogling
13 Transform3Dd wTf = wTmf.multiply(inverse(fTmf));
14 // test if frame is a dynamic attachable frame
15 if( Kinematics.isDAF( mframe ) ){
16 // attach mframe to end of serial device
17 Kinematics.gripFrame(mframe, sdevice.getEnd(), state);
18 }
19 }
20}
See Java Interface for more information about compilation and execution.
LUA
1function fwdKinematics(device, frame, mframe, state)
2 -- calculate the transform from one frame to another
3 fTmf = Kinematics.frameTframe(frame, mframe, state);
4 -- calculate the transform from world to frame
5 wTmf = Kinematics.worldTframe( mframe, state );
6 -- we can find the world to frame transform by a little jogling
7 wTf = wTmf * inverse(fTmf);
8 -- test if frame is a dynamic attachable frame
9 if Kinematics.isDAF( mframe ) then
10 -- attach mframe to end of serial device
11 Kinematics.gripFrame(mframe, sdevice:getEnd(), state);
12 end
13end
See Lua Interface for more information about execution of the script.
Device Kinematics
The device class also define utility functions for calculating forward kinematics, at least those that relate to the device. Additionally the Device has functionality to compute the Device Jacobian, setting and getting the joint configurations and getting the joint limits.
C++
1#include <rw/kinematics/MovableFrame.hpp>
2#include <rw/kinematics/State.hpp>
3#include <rw/math/Q.hpp>
4#include <rw/math/Transform3D.hpp>
5#include <rw/models/SerialDevice.hpp>
6
7using namespace rw::kinematics;
8using namespace rw::math;
9using rw::models::SerialDevice;
10
11void fwdKinematicsDevice(SerialDevice::Ptr sdevice, MovableFrame* mframe, State& state)
12{
13 // get device base frame
14 Frame *base = sdevice->getBase();
15 // get device end effector
16 Frame *end = sdevice->getEnd();
17 // calculate base to end transform
18 Transform3D<> bTe = sdevice->baseTend(state);
19 // or just base to any frame
20 Transform3D<> bTmf = sdevice->baseTframe(mframe, state);
21 // get device name
22 std::string sdevicename = sdevice->getName();
23 // the degrees of freedom of this device
24 int dof = sdevice->getDOF();
25 // set the configuration of the device to zero
26 sdevice->setQ( Q::zero(dof) , state );
27}
Python
1from sdurw import *
2
3def fwdKinematicsDevice(sdevice, mframe, state):
4 # get device base frame
5 base = sdevice.getBase();
6 # get device end effector
7 end = sdevice.getEnd();
8 # calculate base to end transform
9 bTe = sdevice.baseTend(state);
10 # or just base to any frame
11 bTmf = sdevice.baseTframe(mframe, state);
12 # get device name
13 sdevicename = sdevice.getName();
14 # the degrees of freedom of this device
15 dof = sdevice.getDOF();
16 # set the configuration of the device to zero
17 sdevice.setQ( Q(dof,0.0) , state );
Java
1import org.robwork.sdurw.*;
2import org.robwork.sdurw.Q;
3import static org.robwork.sdurw.sdurw.inverse;
4
5public class ExFwdKinematicsDevice {
6 public static void fwdKinematicsDevice(SerialDevicePtr sdevice, MovableFrame mframe, State state)
7 {
8 // get device base frame
9 Frame base = sdevice.getBase();
10 // get device end effector
11 Frame end = sdevice.getEnd();
12 // calculate base to end transform
13 Transform3Dd bTe = sdevice.baseTend(state);
14 // or just base to any frame
15 Transform3Dd bTmf = sdevice.baseTframe(mframe, state);
16 // get device name
17 String sdevicename = sdevice.getName();
18 // the degrees of freedom of this device
19 long dof = sdevice.getDOF();
20 // set the configuration of the device to zero
21 sdevice.setQ( new Q((int)dof,0.0) , state );
22 }
23}
LUA
1function fwdKinematicsDevice(device, mframe, state)
2 -- get device base frame
3 base = sdevice:getBase();
4 -- get device end effector
5 endFrame = sdevice:getEnd();
6 -- calculate base to end transform
7 bTe = sdevice:baseTend(state);
8 -- or just base to any frame
9 bTmf = sdevice:baseTframe(mframe, state);
10 -- get device name
11 sdevicename = sdevice:getName();
12 -- the degrees of freedom of this device
13 dof = sdevice:getDOF();
14 -- set the configuration of the device to zero
15 sdevice:setQ( Q(dof,0.0) , state );
16end
Kinematics trees and states
The kinematic structure of the work cell is represented by a tree of frames (see rw::kinematics::Frame). The root of the kinematic tree is called the world frame (rw::models::WorkCell::getWorldFrame()). Each frame has a transformation (see rw::math::Transform3D) relative to its parent frame and this transformation may change in response to values assigned for the frame. A revolute joint of a device (see rw::models::RevoluteJoint) is for example implemented as a frame that has a single value that rotates the frame relative to its parent. Besides revolute joints RobWork also supports prismatic joints and dependent joints for which the value may depend on other joints.
It is important in RobWork to note that the values for the frames are not stored within the frames, but are instead stored explicitly in a value of type rw::kinematics::State. Given a state for the workcell, the transform of a frame relative to its parent can be calculated with rw::kinematics::Frame::getTransform().
The frames of the workcell are always organized in a tree, but for certain frames the parent that they are connected to can dynamically be changed. These frames are called dynamically attachable frames or DAFs for short. The parent that a DAF is attached to is not stored within the DAF itself, but is instead stored externally in a State value. Different state values can thus correspond to different structures of the tree. Given a state for the workcell the parent and children of a frame can be retrieved with rw::kinematics::Frame::getParent() and rw::kinematics::Frame::getChildren().
Because the values of the frames and the attachments of DAFs are stored outside of the workcell, we say that the workcell is stateless. This enables a workcell and the associated data to be used concurrently in multiple threads as well as to easily communicate the entire state of a workcell.
To illustrate these important ideas, this example shows how to print the structure of the kinematic tree of the workcell and for each frame print also the position of the frame in space:
C++
1#include <rw/kinematics/Frame.hpp>
2#include <rw/models/WorkCell.hpp>
3
4
5#include <string>
6
7using namespace rw::kinematics;
8using rw::math::Transform3D;
9using rw::models::WorkCell;
10
11void printKinematicTree (Frame& frame, const State& state, const Transform3D<>& parentTransform,
12 int level)
13{
14 const Transform3D<> transform = parentTransform * frame.getTransform (state);
15
16 std::cout << std::string (level, ' ') << frame.getName () << " at " << transform.P ()
17 << std::endl;
18
19 for (Frame::Ptr& child : frame.getChildrenList (state)) {
20 printKinematicTree (*child, state, transform, level + 1);
21 }
22}
23
24void printDefaultWorkCellStructure (const WorkCell& workcell)
25{
26 std::cout << workcell << std::endl;
27 printKinematicTree (
28 *workcell.getWorldFrame (), workcell.getDefaultState (), Transform3D<>::identity (), 0);
29}
Python
1from sdurw import *
2
3def printKinematicTree(frame, state, parentTransform, level):
4 transform = parentTransform * frame.getTransform(state);
5
6 for i in range(level):
7 print(' ', end = '')
8
9 print(frame.getName() + " at " + str(transform.P()))
10
11 for child in frame.getChildren(state):
12 printKinematicTree(child, state, transform, level + 1);
13
14def printDefaultWorkCellStructure(workcell):
15 printKinematicTree(
16 workcell.getWorldFrame(),
17 workcell.getDefaultState(),
18 Transform3D.identity(),
19 0);
Java
1import org.robwork.sdurw.*;
2
3public class ExPrintKinematicTree {
4 public static void printKinematicTree(
5 Frame frame,
6 State state,
7 Transform3Dd parentTransform,
8 int level)
9 {
10 final Transform3Dd transform = parentTransform.multiply(frame.getTransform(state));
11
12 for (int i = 0; i < level; i++) {
13 System.out.print(" ");
14 }
15 System.out.println(frame.getName() + " at " + transform.P());
16
17 FrameVector children = frame.getChildren(state);
18 for (int i = 0; i < children.size(); i++) {
19 printKinematicTree(children.get(i), state, transform, level + 1);
20 }
21 }
22
23 public static void printDefaultWorkCellStructure(WorkCellPtr workcell)
24 {
25 printKinematicTree(
26 workcell.getWorldFrame(),
27 workcell.getDefaultState(),
28 Transform3Dd.identity(),
29 0);
30 }
31
32}
LUA
1function printKinematicTree(frame, state, parentTransform, level)
2 transform = parentTransform * frame:getTransform(state);
3
4 indent = ""
5 for i=1,level do
6 indent = indent .. " "
7 end
8
9 print(indent .. frame:getName() .. " at " .. tostring(transform:P()))
10
11 local children = frame:getChildren(state)
12 if not children:empty() then
13 for i = 0,children:size()-1 do
14 child = children[i]
15 printKinematicTree(child, state, transform, level + 1);
16 end
17 end
18end
19
20function printDefaultWorkCellStructure(workcell)
21 printKinematicTree(
22 workcell:getWorldFrame(),
23 workcell:getDefaultState(),
24 Transform3Dd.identity(),
25 0);
26end
We see from this example that given a state, it is straight-forward to compute the transform of every single frame in the workcell. RobWork has some utilities to make calculation of forward kinematics convenient in the day to day work, such a rw::kinematics::FKTable and rw::kinematics::FKRange described below.
World transforms for a set of frames
rw::kinematics::FKTable computes the forward kinematics for a number of frames for a common state. The results of the forward kinematics are stored in the FKTable object so that the transform for a frame is not computed over and over again. This example shows how the transform for a sequence of frames can be efficiently computed:
C++
1#include <rw/kinematics/FKTable.hpp>
2
3#include <vector>
4
5using rw::math::Transform3D;
6using namespace rw::kinematics;
7
8std::vector<Transform3D<> > worldTransforms(
9 const std::vector<const Frame*>& frames, const State& state)
10{
11 FKTable fk(state);
12
13 std::vector<Transform3D<> > result;
14 for(const Frame* const f : frames) {
15 result.push_back(fk.get(*f));
16 }
17 return result;
18}
API Reference: rw::kinematics::FKTable
Python
1from sdurw import *
2
3def worldTransforms(frames, state):
4 fk = FKTable(state);
5
6 result = Transform3DdVector();
7 for frame in frames:
8 result.push_back(fk.get(frame));
9 return result;
API Reference:
rw.FKTable
Java
1import java.util.Iterator;
2
3import org.robwork.sdurw.*;
4
5public class ExWorldTransforms {
6 public static Transform3DdVector worldTransforms(
7 FrameVector frames, State state)
8 {
9 FKTable fk = new FKTable(state);
10
11 Transform3DdVector result = new Transform3DdVector();
12 Iterator<Frame> iterator = frames.iterator();
13 while(iterator.hasNext()) {
14 Frame frame = iterator.next();
15 result.add(fk.get(frame));
16 }
17 return result;
18 }
19}
API Reference:
LUA
1function worldTransforms(frames, state)
2 fk = FKTable(state);
3
4 result = Transform3DdVector()
5 for i = 0,frames:size()-1 do
6 result:push_back(fk:get(frames[i]))
7 end
8 return result
9end
Relative transforms for a pair of frames
rw::kinematics::FKRange computes the relative transform for a pair of frames. To efficiently compute the relative transform for a pair of frames the path in the kinematic tree that connects the frames must be computed. Knowing the path and the relative transform between adjacent frames of the path (rw::kinematics::Frame::getTransform()) the full transform from start to end of the path can be computed. This example shows the use of rw::kinematics::FKRange:
C++
1#include <rw/kinematics/FKRange.hpp>
2
3using rw::math::Transform3D;
4using namespace rw::kinematics;
5
6Transform3D<> frameToFrameTransform(
7 const Frame& a, const Frame& b, const State& state)
8{
9 FKRange fk(&a, &b, state);
10 return fk.get(state);
11}
API Reference: rw::kinematics::FKRange
Python
1from sdurw import *
2
3def frameToFrameTransform(a, b, state):
4 fk = FKRange(a, b, state);
5 return fk.get(state);
API Reference:
rw.FKRange
Java
1import org.robwork.sdurw.*;
2
3public class ExFrameToFrameTransform {
4 public static Transform3Dd frameToFrameTransform(
5 Frame a, Frame b, State state)
6 {
7 FKRange fk = new FKRange(a, b, state);
8 return fk.get(state);
9 }
10}
API Reference:
LUA
1function frameToFrameTransform(a, b, state)
2 fk = FKRange(a, b, state)
3 return fk:get(state)
4end
If you repeatedly compute the forward kinematics for the same pair of frames and the same parent-child structure of the tree, you can reuse the rw::kinematics::FKRange object so that e.g. the path connecting the frames need not be recomputed. For example, given a pair of frames and a set of states the relative transforms that relate the frames can be computed efficiently as follows:
C++
1#include <rw/kinematics/FKRange.hpp>
2#include <rw/kinematics/State.hpp>
3
4#include <vector>
5
6using rw::math::Transform3D;
7using namespace rw::kinematics;
8
9std::vector<Transform3D<> > frameToFrameTransforms(
10 const Frame& a,
11 const Frame& b,
12 const State& tree_structure,
13 const std::vector<State>& states)
14{
15 FKRange fk(&a, &b, tree_structure);
16
17 std::vector<Transform3D<> > result;
18 for(const State& state : states) {
19 result.push_back(fk.get(state));
20 }
21 return result;
22}
Python
1from sdurw import *
2
3def frameToFrameTransforms(a, b, tree_structure, states):
4 fk = FKRange(a, b, tree_structure);
5
6 result = Transform3DdVector();
7 for state in states:
8 result.push_back(fk.get(state));
9 return result;
Java
1import java.util.Iterator;
2
3import org.robwork.sdurw.*;
4
5public class ExFrameToFrameTransforms {
6 public static Transform3DdVector frameToFrameTransforms(
7 Frame a, Frame b, State tree_structure, StateVector states)
8 {
9 FKRange fk = new FKRange(a, b, tree_structure);
10
11 Transform3DdVector result = new Transform3DdVector();
12 Iterator<State> iterator = states.iterator();
13 while(iterator.hasNext()) {
14 State state = iterator.next();
15 result.add(fk.get(state));
16 }
17 return result;
18 }
19}
LUA
1function frameToFrameTransforms(a, b, tree_structure, states)
2 fk = FKRange(a, b, tree_structure);
3
4 result = Transform3DdVector()
5 for i = 0,states:size()-1 do
6 result:push_back(fk:get(states[i]))
7 end
8 return result
9end
The frameToFrameTransform() utility function is available as rw::kinematics::Kinematics::frameTframe().
Dynamically attachable frames and movable frames
A dynamically attachable frame (DAF) is a frame for which the parent frame can be changed. We say that the frame is attached to a new parent frame (rw::kinematics::Frame::attachFrame()). A DAF can be attached to any frame of the workcell except itself. You should avoid attaching a DAF to a child of its subtree as this will create a cycle in the kinematic structure. Frames of any type can be a DAF. You can check if a frame is a DAF like this:
1#include <rw/kinematics/Frame.hpp>
2
3using namespace rw::kinematics;
4
5bool isDaf(const Frame& frame)
6{
7 return frame.getParent() == NULL;
8}
DAFs are used for example to simulate the picking up of an item by a gripper. The item is represented by a DAF and is initially attached to some frame of the workcell. When the gripper is closed, the picking up of the item is simulated by attaching the item frame to the gripper frame.
If the parent frame of a DAF is changed, the world transform of the DAF will generally change also. When simulating the picking up of an item, you do not want the item to instantly change position in space. Therefore a DAF is often a movable frame (rw::kinematics::MovableFrame) also. A movable frame is a frame for which an arbitrary transform can be set for the transform of the frame relative to its parent (rw::kinematics::MovableFrame::setTransform()). To simulate the gripping of the item, the parent of the frame is set to the frame of the gripper, and at the same time the relative transform of the item is assigned a value that equals the transform from gripper to item. This procedure is carried out as follows:
1#include <rw/kinematics/MovableFrame.hpp>
2#include <rw/kinematics/FKRange.hpp>
3#include <rw/math/Transform3D.hpp>
4
5using rw::math::Transform3D;
6using namespace rw::kinematics;
7
8void gripMovableFrame(
9 MovableFrame& item, Frame& gripper, State& state)
10{
11 FKRange fk(&gripper, &item, state);
12 const Transform3D<> transform = fk.get(state);
13 item.setTransform(transform, state);
14 item.attachTo(&gripper, state);
15}
The function receives the current state of the workcell as input and updates this state to reflect the gripping of the item. Recall that the frames themselves are stateless: The attachment of the DAF and its change of relative transform is stored entirely within the state.
RobWork provides utilities for the above in the form of the rw::kinematics::Kinematics::gripFrame() and rw::kinematics::Kinematics::gripMovableFrame() collection of functions.