2020-11-03

Embedding Python in C++ with boost::python - importing modules

Boost offers quite nice functionality for embedding Python in C++ and exporting C++ to Python: boost python.

I'm not going to discuss the library in depth, but instead I'd like to focus on a specific problem. Let's consider a simple example. We have a Python module in our project that defines a function, which we'd like to execute in our C++ code:

def sample_function(x):
  """Returns twice the x value"""
  return 2.0 * x

It can be done using boost::python in the following way:

#include <iostream>
#include <boost/python.hpp>

using namespace std;
namespace python = boost::python;

int main(int argc, char* argv[]) {
  Py_Initialize(); 
  python::object sample_module = python::import("sample_module");
  python::object sample_function = sample_module.attr("sample_function");
  python::object result = sample_function(5.0);
  float y = python::extract<float>(result);
  cout << y << endl;
  return 0;
}

If you try to run it, you are going to encounter an issue: Python doesn't know where to look for the module! It's not in the PYTHONPATH. It won't even try the current working directory.

A quick Google search suggests several possible solutions.

1. You may try to set PYTHONPATH using setenv():

#include <cstdlib>

...

setenv("PYTHONPATH", ".", 1);

Py_Initialize();

This will tell Python to look for the module in the directory you run the program from. So it will only work if you always launch it from that directory. Furthermore, you discard everything else that may have happened to be in PYTHONPATH, e.g. location of other imported modules. It is also a bit ugly to have to rely on an environment variable.

2. Execute sys.path.append() before importing module. You can do that with '.' or the full path, e.g:

Py_Initialize(); 

PyRun_SimpleString("import sys\nsys.path.append(\"/home/user/path/to/module\")");

That doesn't discard PYTHONPATH. If you use '.', the drawback is that it still won't work if you run your program from a different directory. If you provide the full path - well, hardcoding is ugly.

3. The solution that I found that seems to work is to do the following.

Reading /proc/self/exe symlink gives you a path to the executable file. You can then use this path to build your relative path:

string exe_dir = fs::read_symlink("/proc/self/exe").parent_path().string();

string module_path = exe_dir + "/../scripts";

cout << module_path << endl;

Py_Initialize();

PyRun_SimpleString(("import sys\nsys.path.append(\"" + module_path + "\")").c_str());


The code is available: HERE

2020-11-02

URSim on Ubuntu 18

Universal Robots has made a nice offline simulator for their UR series robots: HERE.

I use it for my robotics course, since you can practice communicating with it just as it were a real robot.

I have recently endeavored to upgrade computers in the lab to Ubuntu 18, which has unfortunately broken a lot of stuff. I have in particular had some trouble running URSim on that Ubuntu version and I'm aware that a lot of people have the same problem.

This is what has helped me running URSim 3.14.2.1031212 on Ubuntu 18:

1. It seems that the install.sh script doesn't recognize the newest Java version. It is also trying to install libcurl3, which, if you also happen to have ROS installed, will almost certainly result in removing a lot of packages and breaking the installation. These changes to install.sh script seem to help (use this as patch, or just change the indicated lines):

--- /home/dagothar/Downloads/URSim_Linux-3.14.2.1031212/ursim-3.14.2.1031212/install.sh
+++ /home/dagothar/Downloads/ursim-3.14.2.1031212/install.sh
@@ -34,6 +34,7 @@
     # source https://stackoverflow.com/questions/7334754/correct-way-to-check-java-version-from-bash-script
         version=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}')
         echo version "$version"
+        return 0
         if [[ "$version" > "1.6" ]]; then
         echo "java version accepted"
             return 0
@@ -67,7 +68,7 @@
 
 set -e
 
-commonDependencies='libcurl3 libjava3d-* ttf-dejavu* fonts-ipafont fonts-baekmuk fonts-nanum fonts-arphic-uming fonts-arphic-ukai'
+commonDependencies='libcurl4 libjava3d-* ttf-dejavu* fonts-ipafont fonts-baekmuk fonts-nanum fonts-arphic-uming fonts-arphic-ukai'
 if [[ $(getconf LONG_BIT) == "32" ]]
 then
     packages=`ls $PWD/ursim-dependencies/*i386.deb`

2. Run the install script:

./install.sh

3. Install missing RPC library and other dependencies if prompted:

sudo apt install libxmlrpc-c++8-dev:i386

4. Install Java 8:

 sudo apt install openjdk-8-jre openjdk-8-jdk

5. Set Java 8 as default:

sudo update-java-alternatives -s java-1.8.0-openjdk-amd64


When running URSim, first start URSim in one terminal:

./start-ursim.sh

It will prompt you that no UR control process is running. Start it manually in another terminal:

./URControl





2020-11-01

RobWorkStudio + ROS plugin template

I sometimes use RobWorkStudio to visualize the scene, robots, trajectories, and for the task interface. It's a nice tool to gather everything in the same place. This often requires enabling some ROS interaction, and it's not terribly straightforward to configure the build system for both ROS and RWS.

In this repository you can find a blank ROS package template containing a RWS plugin: RWS ROS plugin template.

Simply place the package in your catkin workspace and use catkin build or catkin_make to build it. It obviously requires RobWorkStudio to be installed and the environment variables RW_ROOT and RWS_ROOT pointing to RobWork package and RobWorkStudio package respectively. Please check the previous post to see how to do it.

The plugin template shows how to set up the build system and how to embed ROS node handle, subscribing and publishing in the plugin code. A sample subscriber listens to a /level [std_msgs/Int32] topic and updates a QT label in the GUI with the received data. A sample publisher outputs the data from a QSpinBox widget to a /measured [std_msgs/Int32] topic.