tf2_py.cpp
Go to the documentation of this file.
00001 #include <Python.h>
00002 
00003 #include <tf2/buffer_core.h>
00004 #include <tf2/exceptions.h>
00005 
00006 // Run x (a tf method, catching TF's exceptions and reraising them as Python exceptions)
00007 //
00008 #define WRAP(x) \
00009   do { \
00010   try \
00011   { \
00012     x; \
00013   }  \
00014   catch (const tf2::ConnectivityException &e) \
00015   { \
00016     PyErr_SetString(tf2_connectivityexception, e.what()); \
00017     return NULL; \
00018   } \
00019   catch (const tf2::LookupException &e) \
00020   { \
00021     PyErr_SetString(tf2_lookupexception, e.what()); \
00022     return NULL; \
00023   } \
00024   catch (const tf2::ExtrapolationException &e) \
00025   { \
00026     PyErr_SetString(tf2_extrapolationexception, e.what()); \
00027     return NULL; \
00028   } \
00029   catch (const tf2::InvalidArgumentException &e) \
00030   { \
00031     PyErr_SetString(tf2_invalidargumentexception, e.what()); \
00032     return NULL; \
00033   } \
00034   catch (const tf2::TimeoutException &e) \
00035   { \
00036     PyErr_SetString(tf2_timeoutexception, e.what()); \
00037     return NULL; \
00038   } \
00039   catch (const tf2::TransformException &e) \
00040   { \
00041     PyErr_SetString(tf2_exception, e.what()); \
00042     return NULL; \
00043   } \
00044   } while (0)
00045 
00046 static PyObject *pModulerospy = NULL;
00047 static PyObject *pModulegeometrymsgs = NULL;
00048 static PyObject *tf2_exception = NULL;
00049 static PyObject *tf2_connectivityexception = NULL, *tf2_lookupexception = NULL, *tf2_extrapolationexception = NULL, 
00050                 *tf2_invalidargumentexception = NULL, *tf2_timeoutexception = NULL;
00051 
00052 struct buffer_core_t {
00053   PyObject_HEAD
00054   tf2::BufferCore *bc;
00055 };
00056 
00057 static PyTypeObject buffer_core_Type = {
00058   PyObject_HEAD_INIT(&PyType_Type)
00059   0,                               /*size*/
00060   "_tf2.BufferCore",                /*name*/
00061   sizeof(buffer_core_t),           /*basicsize*/
00062 };
00063 
00064 static PyObject *PyObject_BorrowAttrString(PyObject* o, const char *name)
00065 {
00066     PyObject *r = PyObject_GetAttrString(o, name);
00067     if (r != NULL)
00068       Py_DECREF(r);
00069     return r;
00070 }
00071 
00072 static PyObject *transform_converter(const geometry_msgs::TransformStamped* transform)
00073 {
00074   PyObject *pclass, *pargs, *pinst = NULL;
00075   pclass = PyObject_GetAttrString(pModulegeometrymsgs, "TransformStamped");
00076   if(pclass == NULL)
00077   {
00078     printf("Can't get geometry_msgs.msg.TransformedStamped\n");
00079     return NULL;
00080   }
00081 
00082   pargs = Py_BuildValue("()");
00083   if(pargs == NULL)
00084   {
00085     printf("Can't build argument list\n");
00086     return NULL;
00087   }
00088 
00089   pinst = PyEval_CallObject(pclass, pargs);
00090   Py_DECREF(pclass);
00091   Py_DECREF(pargs);
00092   if(pinst == NULL)
00093   {
00094     printf("Can't create class\n");
00095     return NULL;
00096   }
00097 
00098   //we need to convert the time to python
00099   PyObject *rospy_time = PyObject_GetAttrString(pModulerospy, "Time");
00100   PyObject *args = Py_BuildValue("ii", transform->header.stamp.sec, transform->header.stamp.nsec);
00101   PyObject *time_obj = PyObject_CallObject(rospy_time, args);
00102   Py_DECREF(args);
00103   Py_DECREF(rospy_time);
00104 
00105   PyObject* pheader = PyObject_GetAttrString(pinst, "header");
00106   PyObject_SetAttrString(pheader, "stamp", time_obj);
00107   Py_DECREF(time_obj);
00108 
00109   PyObject_SetAttrString(pheader, "frame_id", PyString_FromString((transform->header.frame_id).c_str()));
00110   Py_DECREF(pheader);
00111 
00112   PyObject *ptransform = PyObject_GetAttrString(pinst, "transform");
00113   PyObject *ptranslation = PyObject_GetAttrString(ptransform, "translation");
00114   PyObject *protation = PyObject_GetAttrString(ptransform, "rotation");
00115   Py_DECREF(ptransform);
00116 
00117   PyObject_SetAttrString(pinst, "child_frame_id", PyString_FromString((transform->child_frame_id).c_str()));
00118 
00119   PyObject_SetAttrString(ptranslation, "x", PyFloat_FromDouble(transform->transform.translation.x));
00120   PyObject_SetAttrString(ptranslation, "y", PyFloat_FromDouble(transform->transform.translation.y));
00121   PyObject_SetAttrString(ptranslation, "z", PyFloat_FromDouble(transform->transform.translation.z));
00122   Py_DECREF(ptranslation);
00123 
00124   PyObject_SetAttrString(protation, "x", PyFloat_FromDouble(transform->transform.rotation.x));
00125   PyObject_SetAttrString(protation, "y", PyFloat_FromDouble(transform->transform.rotation.y));
00126   PyObject_SetAttrString(protation, "z", PyFloat_FromDouble(transform->transform.rotation.z));
00127   PyObject_SetAttrString(protation, "w", PyFloat_FromDouble(transform->transform.rotation.w));
00128   Py_DECREF(protation);
00129 
00130   return pinst;
00131 }
00132 
00133 static int rostime_converter(PyObject *obj, ros::Time *rt)
00134 {
00135   PyObject *tsr = PyObject_CallMethod(obj, (char*)"to_sec", NULL);
00136   if (tsr == NULL) {
00137     PyErr_SetString(PyExc_TypeError, "time must have a to_sec method, e.g. rospy.Time or rospy.Duration");
00138     return 0;
00139   } else {
00140     (*rt).fromSec(PyFloat_AsDouble(tsr));
00141     Py_DECREF(tsr);
00142     return 1;
00143   }
00144 }
00145 
00146 static int rosduration_converter(PyObject *obj, ros::Duration *rt)
00147 {
00148   PyObject *tsr = PyObject_CallMethod(obj, (char*)"to_sec", NULL);
00149   if (tsr == NULL) {
00150     PyErr_SetString(PyExc_TypeError, "time must have a to_sec method, e.g. rospy.Time or rospy.Duration");
00151     return 0;
00152   } else {
00153     (*rt).fromSec(PyFloat_AsDouble(tsr));
00154     Py_DECREF(tsr);
00155     return 1;
00156   }
00157 }
00158 
00159 static int BufferCore_init(PyObject *self, PyObject *args, PyObject *kw)
00160 {
00161   ros::Duration cache_time;
00162 
00163   cache_time.fromSec(tf2::BufferCore::DEFAULT_CACHE_TIME);
00164 
00165   if (!PyArg_ParseTuple(args, "|O&", rosduration_converter, &cache_time))
00166     return -1;
00167 
00168   ((buffer_core_t*)self)->bc = new tf2::BufferCore(cache_time);
00169 
00170   return 0;
00171 }
00172 
00173 /* This may need to be implemented later if we decide to have it in the core
00174 static PyObject *getTFPrefix(PyObject *self, PyObject *args)
00175 {
00176   if (!PyArg_ParseTuple(args, ""))
00177     return NULL;
00178   tf::Transformer *t = ((transformer_t*)self)->t;
00179   return PyString_FromString(t->getTFPrefix().c_str());
00180 }
00181 */
00182 
00183 static PyObject *allFramesAsYAML(PyObject *self, PyObject *args)
00184 {
00185   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00186   return PyString_FromString(bc->allFramesAsYAML().c_str());
00187 }
00188 
00189 static PyObject *allFramesAsString(PyObject *self, PyObject *args)
00190 {
00191   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00192   return PyString_FromString(bc->allFramesAsString().c_str());
00193 }
00194 
00195 static PyObject *canTransformCore(PyObject *self, PyObject *args, PyObject *kw)
00196 {
00197   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00198   char *target_frame, *source_frame;
00199   ros::Time time;
00200   static const char *keywords[] = { "target_frame", "source_frame", "time", NULL };
00201 
00202   if (!PyArg_ParseTupleAndKeywords(args, kw, "ssO&", (char**)keywords, &target_frame, &source_frame, rostime_converter, &time))
00203     return NULL;
00204   std::string error_msg;
00205   bool can_transform = bc->canTransform(target_frame, source_frame, time, &error_msg);
00206   //return PyBool_FromLong(t->canTransform(target_frame, source_frame, time));
00207   return Py_BuildValue("bs", can_transform, error_msg.c_str());
00208 }
00209 
00210 static PyObject *canTransformFullCore(PyObject *self, PyObject *args, PyObject *kw)
00211 {
00212   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00213   char *target_frame, *source_frame, *fixed_frame;
00214   ros::Time target_time, source_time;
00215   static const char *keywords[] = { "target_frame", "target_time", "source_frame", "source_time", "fixed_frame", NULL };
00216 
00217   if (!PyArg_ParseTupleAndKeywords(args, kw, "sO&sO&s", (char**)keywords,
00218                         &target_frame,
00219                         rostime_converter,
00220                         &target_time,
00221                         &source_frame,
00222                         rostime_converter,
00223                         &source_time,
00224                         &fixed_frame))
00225     return NULL;
00226   std::string error_msg;
00227   bool can_transform = bc->canTransform(target_frame, target_time, source_frame, source_time, fixed_frame, &error_msg);
00228   //return PyBool_FromLong(t->canTransform(target_frame, target_time, source_frame, source_time, fixed_frame));
00229   return Py_BuildValue("bs", can_transform, error_msg.c_str());
00230 }
00231 
00232 /* Debugging stuff that may need to be implemented later
00233 static PyObject *asListOfStrings(std::vector< std::string > los)
00234 {
00235   PyObject *r = PyList_New(los.size());
00236   size_t i;
00237   for (i = 0; i < los.size(); i++) {
00238     PyList_SetItem(r, i, PyString_FromString(los[i].c_str()));
00239   }
00240   return r;
00241 }
00242 
00243 static PyObject *chain(PyObject *self, PyObject *args, PyObject *kw)
00244 {
00245   tf::Transformer *t = ((transformer_t*)self)->t;
00246   char *target_frame, *source_frame, *fixed_frame;
00247   ros::Time target_time, source_time;
00248   std::vector< std::string > output;
00249   static const char *keywords[] = { "target_frame", "target_time", "source_frame", "source_time", "fixed_frame", NULL };
00250 
00251   if (!PyArg_ParseTupleAndKeywords(args, kw, "sO&sO&s", (char**)keywords,
00252                         &target_frame,
00253                         rostime_converter,
00254                         &target_time,
00255                         &source_frame,
00256                         rostime_converter,
00257                         &source_time,
00258                         &fixed_frame))
00259     return NULL;
00260 
00261   WRAP(t->chainAsVector(target_frame, target_time, source_frame, source_time, fixed_frame, output));
00262   return asListOfStrings(output);
00263 }
00264 
00265 static PyObject *getLatestCommonTime(PyObject *self, PyObject *args, PyObject *kw)
00266 {
00267   tf::Transformer *t = ((transformer_t*)self)->t;
00268   char *source, *dest;
00269   std::string error_string;
00270   ros::Time time;
00271 
00272   if (!PyArg_ParseTuple(args, "ss", &source, &dest))
00273     return NULL;
00274   int r = t->getLatestCommonTime(source, dest, time, &error_string);
00275   if (r == 0) {
00276     PyObject *rospy_time = PyObject_GetAttrString(pModulerospy, "Time");
00277     PyObject *args = Py_BuildValue("ii", time.sec, time.nsec);
00278     PyObject *ob = PyObject_CallObject(rospy_time, args);
00279     Py_DECREF(args);
00280     Py_DECREF(rospy_time);
00281     return ob;
00282   } else {
00283     PyErr_SetString(tf_exception, error_string.c_str());
00284     return NULL;
00285   }
00286 }
00287 */
00288 
00289 static PyObject *lookupTransformCore(PyObject *self, PyObject *args, PyObject *kw)
00290 {
00291   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00292   char *target_frame, *source_frame;
00293   ros::Time time;
00294   static const char *keywords[] = { "target_frame", "source_frame", "time", NULL };
00295 
00296   if (!PyArg_ParseTupleAndKeywords(args, kw, "ssO&", (char**)keywords, &target_frame, &source_frame, rostime_converter, &time))
00297     return NULL;
00298   geometry_msgs::TransformStamped transform;
00299   WRAP(transform = bc->lookupTransform(target_frame, source_frame, time));
00300   geometry_msgs::Vector3 origin = transform.transform.translation;
00301   geometry_msgs::Quaternion rotation = transform.transform.rotation;
00302   //TODO: Create a converter that will actually return a python message
00303   return Py_BuildValue("O&", transform_converter, &transform);
00304   //return Py_BuildValue("(ddd)(dddd)",
00305   //    origin.x, origin.y, origin.z,
00306   //    rotation.x, rotation.y, rotation.z, rotation.w);
00307 }
00308 
00309 static PyObject *lookupTransformFullCore(PyObject *self, PyObject *args, PyObject *kw)
00310 {
00311   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00312   char *target_frame, *source_frame, *fixed_frame;
00313   ros::Time target_time, source_time;
00314   static const char *keywords[] = { "target_frame", "target_time", "source_frame", "source_time", "fixed_frame", NULL };
00315 
00316   if (!PyArg_ParseTupleAndKeywords(args, kw, "sO&sO&s", (char**)keywords,
00317                         &target_frame,
00318                         rostime_converter,
00319                         &target_time,
00320                         &source_frame,
00321                         rostime_converter,
00322                         &source_time,
00323                         &fixed_frame))
00324     return NULL;
00325   geometry_msgs::TransformStamped transform;
00326   WRAP(transform = bc->lookupTransform(target_frame, target_time, source_frame, source_time, fixed_frame));
00327   geometry_msgs::Vector3 origin = transform.transform.translation;
00328   geometry_msgs::Quaternion rotation = transform.transform.rotation;
00329   //TODO: Create a converter that will actually return a python message
00330   return Py_BuildValue("O&", transform_converter, &transform);
00331 }
00332 /*
00333 static PyObject *lookupTwistCore(PyObject *self, PyObject *args, PyObject *kw)
00334 {
00335   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00336   char *tracking_frame, *observation_frame;
00337   ros::Time time;
00338   ros::Duration averaging_interval;
00339   static const char *keywords[] = { "tracking_frame", "observation_frame", "time", "averaging_interval", NULL };
00340 
00341   if (!PyArg_ParseTupleAndKeywords(args, kw, "ssO&O&", (char**)keywords, &tracking_frame, &observation_frame, rostime_converter, &time, rosduration_converter, &averaging_interval))
00342     return NULL;
00343   geometry_msgs::Twist twist;
00344   WRAP(twist = bc->lookupTwist(tracking_frame, observation_frame, time, averaging_interval));
00345 
00346   return Py_BuildValue("(ddd)(ddd)",
00347       twist.linear.x, twist.linear.y, twist.linear.z,
00348       twist.angular.x, twist.angular.y, twist.angular.z);
00349 }
00350 
00351 static PyObject *lookupTwistFullCore(PyObject *self, PyObject *args)
00352 {
00353   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00354   char *tracking_frame, *observation_frame, *reference_frame, *reference_point_frame;
00355   ros::Time time;
00356   ros::Duration averaging_interval;
00357   double px, py, pz;
00358 
00359   if (!PyArg_ParseTuple(args, "sss(ddd)sO&O&",
00360                         &tracking_frame,
00361                         &observation_frame,
00362                         &reference_frame,
00363                         &px, &py, &pz,
00364                         &reference_point_frame,
00365                         rostime_converter, &time,
00366                         rosduration_converter, &averaging_interval))
00367     return NULL;
00368   geometry_msgs::Twist twist;
00369   tf::Point pt(px, py, pz);
00370   WRAP(twist = bc->lookupTwist(tracking_frame, observation_frame, reference_frame, pt, reference_point_frame, time, averaging_interval));
00371 
00372   return Py_BuildValue("(ddd)(ddd)",
00373       twist.linear.x, twist.linear.y, twist.linear.z,
00374       twist.angular.x, twist.angular.y, twist.angular.z);
00375 }
00376 */
00377 static PyObject *setTransform(PyObject *self, PyObject *args)
00378 {
00379   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00380   PyObject *py_transform;
00381   char *authority;
00382 
00383   if (!PyArg_ParseTuple(args, "Os", &py_transform, &authority))
00384     return NULL;
00385 
00386   geometry_msgs::TransformStamped transform;
00387   PyObject *header = PyObject_BorrowAttrString(py_transform, "header");
00388   transform.child_frame_id = PyString_AsString(PyObject_BorrowAttrString(py_transform, "child_frame_id"));
00389   transform.header.frame_id = PyString_AsString(PyObject_BorrowAttrString(header, "frame_id"));
00390   if (rostime_converter(PyObject_BorrowAttrString(header, "stamp"), &transform.header.stamp) != 1)
00391     return NULL;
00392 
00393   PyObject *mtransform = PyObject_BorrowAttrString(py_transform, "transform");
00394   PyObject *translation = PyObject_BorrowAttrString(mtransform, "translation");
00395   transform.transform.translation.x = PyFloat_AsDouble(PyObject_BorrowAttrString(translation, "x"));
00396   transform.transform.translation.y = PyFloat_AsDouble(PyObject_BorrowAttrString(translation, "y"));
00397   transform.transform.translation.z = PyFloat_AsDouble(PyObject_BorrowAttrString(translation, "z"));
00398   PyObject *rotation = PyObject_BorrowAttrString(mtransform, "rotation");
00399   transform.transform.rotation.x = PyFloat_AsDouble(PyObject_BorrowAttrString(rotation, "x"));
00400   transform.transform.rotation.y = PyFloat_AsDouble(PyObject_BorrowAttrString(rotation, "y"));
00401   transform.transform.rotation.z = PyFloat_AsDouble(PyObject_BorrowAttrString(rotation, "z"));
00402   transform.transform.rotation.w = PyFloat_AsDouble(PyObject_BorrowAttrString(rotation, "w"));
00403 
00404   bc->setTransform(transform, authority);
00405   Py_RETURN_NONE;
00406 }
00407 
00408 static PyObject *clear(PyObject *self, PyObject *args)
00409 {
00410   tf2::BufferCore *bc = ((buffer_core_t*)self)->bc;
00411   bc->clear();
00412   Py_RETURN_NONE;
00413 }
00414 
00415 /* May come back eventually, but not in public api right now
00416 static PyObject *frameExists(PyObject *self, PyObject *args)
00417 {
00418   tf::Transformer *t = ((transformer_t*)self)->t;
00419   char *frame_id_str;
00420   if (!PyArg_ParseTuple(args, "s", &frame_id_str))
00421     return NULL;
00422   return PyBool_FromLong(t->frameExists(frame_id_str));
00423 }
00424 
00425 static PyObject *getFrameStrings(PyObject *self, PyObject *args)
00426 {
00427   tf::Transformer *t = ((transformer_t*)self)->t;
00428   std::vector< std::string > ids;
00429   t->getFrameStrings(ids);
00430   return asListOfStrings(ids);
00431 }
00432 */
00433 
00434 static struct PyMethodDef buffer_core_methods[] =
00435 {
00436   {"all_frames_as_yaml", allFramesAsYAML, METH_VARARGS},
00437   {"all_frames_as_string", allFramesAsString, METH_VARARGS},
00438   {"set_transform", setTransform, METH_VARARGS},
00439   {"can_transform_core", (PyCFunction)canTransformCore, METH_KEYWORDS},
00440   {"can_transform_full_core", (PyCFunction)canTransformFullCore, METH_KEYWORDS},
00441   //{"chain", (PyCFunction)chain, METH_KEYWORDS},
00442   {"clear", (PyCFunction)clear, METH_KEYWORDS},
00443   //{"frameExists", (PyCFunction)frameExists, METH_VARARGS},
00444   //{"getFrameStrings", (PyCFunction)getFrameStrings, METH_VARARGS},
00445   //{"getLatestCommonTime", (PyCFunction)getLatestCommonTime, METH_VARARGS},
00446   {"lookup_transform_core", (PyCFunction)lookupTransformCore, METH_KEYWORDS},
00447   {"lookup_transform_full_core", (PyCFunction)lookupTransformFullCore, METH_KEYWORDS},
00448   //{"lookupTwistCore", (PyCFunction)lookupTwistCore, METH_KEYWORDS},
00449   //{"lookupTwistFullCore", lookupTwistFullCore, METH_VARARGS},
00450   //{"getTFPrefix", (PyCFunction)getTFPrefix, METH_VARARGS},
00451   {NULL,          NULL}
00452 };
00453 
00454 static PyMethodDef module_methods[] = {
00455   // {"Transformer", mkTransformer, METH_VARARGS},
00456   {NULL, NULL, 0},
00457 };
00458 
00459 extern "C" void init_tf2()
00460 {
00461   PyObject *item, *m, *d;
00462 
00463 #if PYTHON_API_VERSION >= 1007
00464   tf2_exception = PyErr_NewException((char*)"tf2.TransformException", NULL, NULL);
00465   tf2_connectivityexception = PyErr_NewException((char*)"tf2.ConnectivityException", tf2_exception, NULL);
00466   tf2_lookupexception = PyErr_NewException((char*)"tf2.LookupException", tf2_exception, NULL);
00467   tf2_extrapolationexception = PyErr_NewException((char*)"tf2.ExtrapolationException", tf2_exception, NULL);
00468   tf2_invalidargumentexception = PyErr_NewException((char*)"tf2.InvalidArgumentException", tf2_exception, NULL);
00469   tf2_timeoutexception = PyErr_NewException((char*)"tf2.TimeoutException", tf2_exception, NULL);
00470 #else
00471   tf2_exception = PyString_FromString("tf2.error");
00472   tf2_connectivityexception = PyString_FromString("tf2.ConnectivityException");
00473   tf2_lookupexception = PyString_FromString("tf2.LookupException");
00474   tf2_extrapolationexception = PyString_FromString("tf2.ExtrapolationException");
00475   tf2_invalidargumentexception = PyString_FromString("tf2.InvalidArgumentException");
00476   tf2_timeoutexception = PyString_FromString("tf2.TimeoutException");
00477 #endif
00478 
00479   pModulerospy = PyImport_Import(item= PyString_FromString("rospy")); Py_DECREF(item);
00480   pModulegeometrymsgs = PyImport_ImportModule("geometry_msgs.msg");
00481 
00482   if(pModulegeometrymsgs == NULL)
00483   {
00484     printf("Cannot load geometry_msgs module");
00485     return;
00486   }
00487 
00488   buffer_core_Type.tp_alloc = PyType_GenericAlloc;
00489   buffer_core_Type.tp_new = PyType_GenericNew;
00490   buffer_core_Type.tp_init = BufferCore_init;
00491   buffer_core_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
00492   buffer_core_Type.tp_methods = buffer_core_methods;
00493   if (PyType_Ready(&buffer_core_Type) != 0)
00494     return;
00495 
00496   m = Py_InitModule("_tf2", module_methods);
00497   PyModule_AddObject(m, "BufferCore", (PyObject *)&buffer_core_Type);
00498   d = PyModule_GetDict(m);
00499   PyDict_SetItemString(d, "TransformException", tf2_exception);
00500   PyDict_SetItemString(d, "ConnectivityException", tf2_connectivityexception);
00501   PyDict_SetItemString(d, "LookupException", tf2_lookupexception);
00502   PyDict_SetItemString(d, "ExtrapolationException", tf2_extrapolationexception);
00503   PyDict_SetItemString(d, "InvalidArgumentException", tf2_invalidargumentexception);
00504   PyDict_SetItemString(d, "TimeoutException", tf2_timeoutexception);
00505 }


tf2
Author(s): Tully Foote, Eitan Marder-Eppstein, Wim Meeussen
autogenerated on Mon Oct 6 2014 00:12:43