WorkCells

The WorkCell is one of the primary containers in RobWork. A WorkCell should gather all stateless elements/models of a scene. These are primarily:

  • Kinematic structure of frames

  • Devices: robots, grippers and similar.

  • Objects: physical objects that has geometry and models for visualisation.

  • Sensor models: models of sensors such as cameras or scanners.

  • Controller models: models of controllers that takes some input and can control or manipulate something in the scene.

Building a WorkCell

WorkCells are defined using the RobWork XML format. RobWorkStudio has a WorkCell Editor plugin wceditor that provides some help on building and editing a WorkCell. When editing a WorkCell in this plugin, the changes are reflected in the scene immediately after saving the file. Notice that the plugin can only assist the user with a few element types. The editor will be extended in the future to have assistance for a more complete set of the possible XML elements.

../../_images/workcelleditor.png

The WorkCell editor plugin is shown to the left. Changes to the XML definition here is reflected in the scene immediately after saving the file. The “Add Frame” dialog assists the user in inserting a correct Frame tag in the XML file.

When building a new WorkCell it is a good advice to have a look at the example scenes in RobWorkData. See Device & Scene Collection for an overview. You can base your new scene on one of these examples, or you can reuse existing models of robots and grippers.

The WorkCell Format page gives an overview of the possible XML tags. Use this page as a reference when building your scene.

Loading a WorkCell

The programs shown below loads a workcell from the file given as argument on the command line. If loading of the workcell fails, the load(…) function will print an error message and return a smart pointer that is NULL. Always remember to check if the returned pointer is NULL with the isNull() function.

C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <rw/loaders/WorkCellLoader.hpp>
#include <rw/models/WorkCell.hpp>

using rw::loaders::WorkCellLoader;
using rw::models::WorkCell;

int main(int argc, char** argv)
{
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " <workcell>" << std::endl;
        return 1;
    }

    const std::string file = argv[1];
    WorkCell::Ptr workcell = WorkCellLoader::Factory::load(file);
    if (workcell.isNull()) {
        std::cout << "WorkCell could not be loaded." << std::endl;
        return 1;
    }

    std::cout << "Workcell " << workcell->getName();
    std::cout << " successfully loaded." << std::endl;
    return 0;
}

API Reference: rw::loaders::WorkCellLoader::Factory

See C++ Interface for more information about compilation and execution.

Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from rw import *
import sys

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: python3 " + sys.argv[0] + " <workcell>")
        sys.exit(1)

    workcell = WorkCellLoaderFactory.load(sys.argv[1]);
    if workcell.isNull():
        print("WorkCell could not be loaded.")
        sys.exit(1)

    print("Workcell " + workcell.getName() + " successfully loaded.")
    sys.exit(0)

API Reference: rw.WorkCellLoaderFactory

See Python interface for more information about execution.

Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.robwork.LoaderRW;
import org.robwork.rw.*;

public class ExLoadWorkCell {

    public static void main(String[] args) throws Exception {
        LoaderRW.load("rw");

        if (args.length != 1) {
            System.out.print("Usage: java " + ExLoadWorkCell.class.getSimpleName());
            System.out.println(" <workcell>");
            System.exit(1);
        }

        WorkCellPtr workcell = WorkCellLoaderFactory.load(args[0]);
        if (workcell.isNull()) {
            System.out.println("WorkCell could not be loaded.");
            System.exit(1);
        }

        System.out.print("Workcell " + workcell.getName());
        System.out.println(" successfully loaded.");
    }

}

API Reference:

See Java Interface for more information about compilation and execution.

LUA

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
require("rw")
using("rw")

if #arg ~= 1 then
    print("Usage: lua ex-load-workcell.lua <workcell>")
    return 1
end

workcell = WorkCellLoaderFactory.load(arg[1])
if workcell:isNull() then
    print("WorkCell could not be loaded")
    return 1
end

print("Workcell " .. workcell:getName() .. " successfully loaded.");

See Lua Interface for more information about execution of the script.

Traversing the devices of a WorkCell

A WorkCell contains a number of devices. A device of a specific name can be retrieved from a WorkCell with rw::models::WorkCell::findDevice(). You can add a device type to the search such that only a device of name name and type type will be found: rw::models::WorkCell::findDevice<type>(name)

You can for example traverse the devices stored in a WorkCell and print their names like this:

C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <rw/models/Device.hpp>
#include <rw/models/WorkCell.hpp>

using namespace rw::models;

void printDeviceNames(const WorkCell& workcell)
{
    std::cout << "Workcell " << workcell << " contains devices:" << std::endl;
    for(const Device::CPtr device : workcell.getDevices()) {
        std::cout << "- " << device->getName() << std::endl;
    }
}

Python

1
2
3
4
5
6
from rw import *

def printDeviceNames(workcell):
    print("Workcell " + workcell.getName() + " contains devices:");
    for device in workcell.getDevices():
        print("- " + device.getName())

Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.Iterator;

import org.robwork.rw.*;

public class ExPrintDevices {

    public static void printDeviceNames(WorkCellPtr workcell)
    {
        System.out.println("Workcell " + workcell.getName() + " contains devices:");
        
        // Method 1
        DevicePtrVector devices = workcell.getDevices();
        DevicePtr[] array = new DevicePtr[devices.size()];
        array = devices.toArray(array);
        for(DevicePtr device : array) {
            System.out.println("- " + device.getName());
        }
        
        // Method 2
        Iterator<DevicePtr> iterator = devices.iterator();
        while(iterator.hasNext()) {
            DevicePtr device = iterator.next();
            System.out.println("* " + device.getName());
        }
        
        // Method 3
        for (int i = 0; i < devices.size(); i++) {
            System.out.println(". " + devices.get(i).getName());
        }
    }

}

LUA

1
2
3
4
5
6
7
function printDeviceNames(workcell)
    print("Workcell " .. workcell:getName() .. " contains devices:");
    devices = workcell:getDevices() 
    for i = 0,devices:size()-1 do
        print("- " .. devices[i]:getName())
    end
end

Devices, Frames and States

The default State of a WorkCell contains the initial configuration of devices and placement of movable frames. Getting a default State from the WorkCell is a commonly used operation, as well as getting Frames and Devices from the WorkCell. An example is shown below of how this can be done:

C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <rw/common/Log.hpp>
#include <rw/kinematics/FixedFrame.hpp>
#include <rw/kinematics/MovableFrame.hpp>
#include <rw/kinematics/State.hpp>
#include <rw/models/SerialDevice.hpp>
#include <rw/models/WorkCell.hpp>

using rw::common::Log;
using namespace rw::kinematics;
using namespace rw::models;

void findFromWorkCell(WorkCell::Ptr wc)
{
    // get the default state
    State state = wc->getDefaultState();
    Frame* worldFrame = wc->getWorldFrame();
    // find a frame by name, remember NULL is a valid return
    Frame* frame = wc->findFrame("FixedFrameName");
    // find a frame by name, but with a specific frame type
    FixedFrame* fframe = wc->findFrame<FixedFrame>("FixedFrameName");
    MovableFrame* mframe = wc->findFrame<MovableFrame>("MovableFrameName");
    // find a device by name
    Device::Ptr device = wc->findDevice("SerialDeviceName");
    SerialDevice::Ptr sdevice = wc->findDevice<SerialDevice>("SerialDeviceName");
}

Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from rw import *

def findFromWorkCell(wc):
    # get the default state
    state = wc.getDefaultState();
    worldFrame = wc.getWorldFrame();
    # find a frame by name, remember NULL is a valid return
    frame = wc.findFrame("FixedFrameName");
    # find a frame by name, but with a specific frame type
    fframe = wc.findFixedFrame("FixedFrameName");
    mframe = wc.findMovableFrame("MovableFrameName");
    # find a device by name
    device = wc.findDevice("SerialDeviceName");
    sdevice = wc.findSerialDevice("SerialDeviceName");

Java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import org.robwork.rw.*;

public class ExFindFromWorkCell {
    public static void findFromWorkCell(WorkCellPtr wc)
    {
        // get the default state
        State state = wc.getDefaultState();
        Frame worldFrame = wc.getWorldFrame();
        // find a frame by name, remember NULL is a valid return
        Frame frame = wc.findFrame("FixedFrameName");
        // find a frame by name, but with a specific frame type
        FixedFrame fframe = wc.findFixedFrame("FixedFrameName");
        MovableFrame mframe = wc.findMovableFrame("MovableFrameName");
        // find a device by name
        DevicePtr device = wc.findDevice("SerialDeviceName");
        SerialDevicePtr sdevice = wc.findSerialDevice("SerialDeviceName");
    }
}

LUA

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function findFromWorkCell(wc)
    -- get the default state
    local state = wc:getDefaultState();
    local worldFrame = wc:getWorldFrame();
    -- find a frame by name, remember NULL is a valid return
    local frame = wc:findFrame("FixedFrameName");
    -- find a frame by name, but with a specific frame type
    local fframe = wc:findFixedFrame("FixedFrameName");
    local mframe = wc:findMovableFrame("MovableFrameName");
    -- find a device by name
    local device = wc:findDevice("SerialDeviceName");
    local sdevice = wc:findSerialDevice("SerialDeviceName");
end

Notice that the templated versions of findFrame and findDevice makes it possible to get a specific type of Frame of Device directly. Always remember to use the isNull() function on the smart pointer to make sure that the Frame or Device is actually found in the WorkCell. You might encounter segmentation fault errors if you try to use a null pointer.

In Python, Java and Lua the templated functions can not be used. The system for doing this is clear from the examples.

Stateless models

A very important aspect when working with RobWork is the understanding of its use of Stateless models. To illustrate stateful and stateless we give two small code examples:

struct StateFull {
 double getQ(){ return _q; }
 void setQ(double q){ _q=q; }
 double _q;
}

struct StateLess {
 double getQ(State& state){ return state.getDouble(this); }
 void setQ(double q, State& state){ state.setDouble(q, this); }
}

Now in the first struct: StateFull, the Q value is stored local as a member value. In the StateLess struct the Q value is stored in a separate class State. How the state stores this value is currently not important but to see how this is implemented in RobWork you should look into rw::kinematics::State, rw::kinematics::StateData and rw::kinematics::StateStructure.

The benefit of a stateless design is primarily that multiple threads or multiple methods can use the same Device model at the same time. E.g. methods for visualisation can visualize a device in one state, while a user is setting the configuration of a device in another state. This effectively reduce thread related issues and also limits the need to copy the models around.

Only few variables of a stateless Classes in RobWork are actually saved in the state, they are not completely stateless. The variables that are saved in the state are the dynamically changing states such as the configuration of a robot device e.g. joint configurations. The more static variables such as joint boundaries are still saved lokally in the object.

Devices

Algorithms for workcells often do not operate on the level of frames and the values for frames. Instead they operate on devices (rw::models::Device) and configurations (rw::math::Q) for devices.

A device controls a subset of frames of the workcell. Different devices may overlap in the frames that they control and one device may contain one or more other devices (rw::models::CompositeDevice). A workcell for a factory application can for example have one device for a 6-axis industrial robot and another 2-axis device that controls the position of the base of the robot. These two device may be combined into one large 8-axis device (rw::models::CompositeDevice).

A configuration is an vector of values for the frames of a device. Configurations support standard vector operations such as addition, scalar multiplication, inner product, etc. The configuration space of a device is the set of valid configurations of a device. For the rw::models::Device type, the configuration space is always box shaped and described by a tuple containing the lower and upper corner (see rw::models::Device::QBox and rw::models::Device::getBounds()).

Algorithms for devices often assume that only the configuration for the device is changed while the state (rw::kinematics::State) of the rest of the workcell stays fixed. A path-planner may for example return a path in the form of a sequence of configurations together with the common workcell state for which the planning was done. When writing or using such algorithms you will often have translate from a configuration for the device to a state of the workcell. This is accomplished by the methods rw::models::Device::setQ() and rw::models::Device::getQ(). This is example shows to convert a sequence of configurations for a common state into a sequence of states:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <rw/kinematics/State.hpp>
#include <rw/math/Q.hpp>
#include <rw/models/Device.hpp>

#include <vector>

using rw::math::Q;
using rw::models::Device;
using rw::kinematics::State;

std::vector<State> getStatePath(
    const Device& device,
    const std::vector<Q>& path,
    const State& common_state)
{
    State state = common_state;

    std::vector<State> result;
    for(const Q& q : path) {
        device.setQ(q, state);
        result.push_back(state);
    }
    return result;
}

This utility function is also available as rw::models::Models::getStatePath().

Note that rw::models::Device::setQ() and rw::models::Device::getQ() do not store a configuration within the device: The configuration is read from and written to a state value. The device itself is stateless.