diff --git a/CMakeLists.txt b/CMakeLists.txt index b22d65c554255e409d691018c72bf7acc633f3d9..307d5bebae0e17cfd6ddbdf1f48c037ac7b177e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,33 +30,38 @@ PROJECT(${PROJECT_NAME} ${PROJECT_ARGS}) FINDPYTHON() ADD_PROJECT_DEPENDENCY(dynamic-graph REQUIRED PKG_CONFIG_REQUIRES dynamic-graph) -SET(BOOST_COMPONENTS filesystem system thread program_options unit_test_framework python) -SEARCH_FOR_BOOST() - +ADD_PROJECT_DEPENDENCY(eigenpy REQUIRED PKG_CONFIG_REQUIRES eigenpy) +SEARCH_FOR_BOOST_PYTHON(REQUIRED) +IF(BUILD_TESTING) + FIND_PACKAGE(Boost REQUIRED COMPONENTS unit_test_framework) +ENDIF(BUILD_TESTING) # Main Library SET(${PROJECT_NAME}_HEADERS include/${CUSTOM_HEADER_DIR}/api.hh include/${CUSTOM_HEADER_DIR}/convert-dg-to-py.hh include/${CUSTOM_HEADER_DIR}/dynamic-graph-py.hh - include/${CUSTOM_HEADER_DIR}/exception.hh - include/${CUSTOM_HEADER_DIR}/exception-python.hh include/${CUSTOM_HEADER_DIR}/interpreter.hh + include/${CUSTOM_HEADER_DIR}/module.hh include/${CUSTOM_HEADER_DIR}/python-compat.hh + include/${CUSTOM_HEADER_DIR}/signal.hh include/${CUSTOM_HEADER_DIR}/signal-wrapper.hh ) SET(${PROJECT_NAME}_SOURCES src/interpreter.cc src/dynamic_graph/python-compat.cc + src/dynamic_graph/entity-py.cc + src/dynamic_graph/convert-dg-to-py.cc ) ADD_LIBRARY(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_HEADERS}) -TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} SYSTEM PUBLIC ${PYTHON_INCLUDE_DIRS}) +TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} SYSTEM PUBLIC ${PYTHON_INCLUDE_DIR}) TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PUBLIC $<INSTALL_INTERFACE:include>) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${Boost_LIBRARIES} - ${PYTHON_LIBRARY} ${Boost_PYTHON_LIBRARIES} dynamic-graph::dynamic-graph) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC ${PYTHON_LIBRARY} + dynamic-graph::dynamic-graph) +TARGET_LINK_BOOST_PYTHON(${PROJECT_NAME} PRIVATE) IF(SUFFIX_SO_VERSION) SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) @@ -67,7 +72,9 @@ TARGET_COMPILE_DEFINITIONS(${PROJECT_NAME} PRIVATE PYTHON_LIBRARY="${PYTHON_LIBR INSTALL(TARGETS ${PROJECT_NAME} EXPORT ${TARGETS_EXPORT_NAME} DESTINATION lib) ADD_SUBDIRECTORY(src) -ADD_SUBDIRECTORY(tests) +IF(BUILD_TESTING) + ADD_SUBDIRECTORY(tests) +ENDIF(BUILD_TESTING) PKG_CONFIG_APPEND_LIBS(${PROJECT_NAME}) INSTALL(FILES package.xml DESTINATION share/${PROJECT_NAME}) diff --git a/cmake b/cmake index fb4c22c319ec5320f9a85527eb1a4130954846f5..91f97c1c31608f48d697a6b11037f13e878b9837 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit fb4c22c319ec5320f9a85527eb1a4130954846f5 +Subproject commit 91f97c1c31608f48d697a6b11037f13e878b9837 diff --git a/include/dynamic-graph/python/convert-dg-to-py.hh b/include/dynamic-graph/python/convert-dg-to-py.hh index fe8d53c44d38eba863e655bd8dec5e1cfe69d445..d849fdc15be0256d624204c909948ffcee09aef7 100644 --- a/include/dynamic-graph/python/convert-dg-to-py.hh +++ b/include/dynamic-graph/python/convert-dg-to-py.hh @@ -1,18 +1,16 @@ // Copyright 2010, Florent Lamiraux, Thomas Moulard, LAAS-CNRS. +#include <boost/python.hpp> + #include <dynamic-graph/linear-algebra.h> #include <dynamic-graph/value.h> -#include <dynamic-graph/python/exception-python.hh> namespace dynamicgraph { namespace python { namespace convert { -command::Value pythonToValue(PyObject* pyObject, const command::Value::Type& valueType); -PyObject* vectorToPython(const Vector& vector); -PyObject* matrixToPython(const ::dynamicgraph::Matrix& matrix); -PyObject* matrix4dToPython(const Eigen::Matrix4d& matrix); -PyObject* valueToPython(const ::dynamicgraph::command::Value& value); +command::Value toValue(boost::python::object o, const command::Value::Type& type); +boost::python::object fromValue(const command::Value& value); } // namespace convert } // namespace python diff --git a/include/dynamic-graph/python/dynamic-graph-py.hh b/include/dynamic-graph/python/dynamic-graph-py.hh index e218f256f3f7f5695575801d4317ef761128fb65..df7eb669ad72b8008f6c76f02a63c03363e32740 100644 --- a/include/dynamic-graph/python/dynamic-graph-py.hh +++ b/include/dynamic-graph/python/dynamic-graph-py.hh @@ -4,189 +4,70 @@ #include <iostream> #include <sstream> +#include <boost/python.hpp> + #include <dynamic-graph/debug.h> #include <dynamic-graph/exception-factory.h> #include <dynamic-graph/signal-base.h> #include "dynamic-graph/python/signal-wrapper.hh" +namespace bp = boost::python; + namespace dynamicgraph { namespace python { +template <typename Iterator> +inline bp::list to_py_list(Iterator begin, Iterator end) { + typedef typename Iterator::value_type T; + bp::list lst; + std::for_each(begin, end, [&](const T& t) { lst.append(t); }); + return lst; +} + +template <typename Iterator> +inline bp::tuple to_py_tuple(Iterator begin, Iterator end) { + return bp::tuple(to_py_list(begin, end)); +} + +template <typename T> +inline std::vector<T> to_std_vector(const bp::object& iterable) { + return std::vector<T>(bp::stl_input_iterator<T>(iterable), bp::stl_input_iterator<T>()); +} + +void exposeSignals(); + // Declare functions defined in other source files namespace signalBase { -PyObject* create(PyObject* self, PyObject* args); -PyObject* createSignalWrapper(PyObject* self, PyObject* args); -PyObject* getTime(PyObject* self, PyObject* args); -PyObject* setTime(PyObject* self, PyObject* args); -PyObject* getName(PyObject* self, PyObject* args); -PyObject* getClassName(PyObject* self, PyObject* args); -PyObject* display(PyObject* self, PyObject* args); -PyObject* displayDependencies(PyObject* self, PyObject* args); -PyObject* getValue(PyObject* self, PyObject* args); -PyObject* setValue(PyObject* self, PyObject* args); -PyObject* recompute(PyObject* self, PyObject* args); -PyObject* unplug(PyObject* self, PyObject* args); -PyObject* isPlugged(PyObject* self, PyObject* args); -PyObject* getPlugged(PyObject* self, PyObject* args); +SignalBase<int>* createSignalWrapper(const char* name, const char* type, bp::object object); } // namespace signalBase namespace entity { -PyObject* create(PyObject* self, PyObject* args); -PyObject* display(PyObject* self, PyObject* args); -PyObject* display(PyObject* self, PyObject* args); -PyObject* getName(PyObject* self, PyObject* args); -PyObject* getClassName(PyObject* self, PyObject* args); -PyObject* hasSignal(PyObject* self, PyObject* args); -PyObject* getSignal(PyObject* self, PyObject* args); -PyObject* listSignals(PyObject* self, PyObject* args); -PyObject* executeCommand(PyObject* self, PyObject* args); -PyObject* listCommands(PyObject* self, PyObject* args); -PyObject* getCommandDocstring(PyObject* self, PyObject* args); -PyObject* getDocString(PyObject* self, PyObject* args); -PyObject* setLoggerVerbosityLevel(PyObject* self, PyObject* args); -PyObject* getLoggerVerbosityLevel(PyObject* self, PyObject* args); -PyObject* setTimeSample(PyObject* self, PyObject* args); -PyObject* getTimeSample(PyObject* self, PyObject* args); -PyObject* setStreamPrintPeriod(PyObject* self, PyObject* args); -PyObject* getStreamPrintPeriod(PyObject* self, PyObject* args); + +/// \param obj an Entity object +void addCommands(boost::python::object obj); +void addSignals(boost::python::object obj); + +Entity* create(const char* type, const char* name); +bp::object executeCmd(bp::tuple args, bp::dict); } // namespace entity namespace factory { -PyObject* getEntityClassList(PyObject* self, PyObject* args); -} -namespace signalCaster { -PyObject* getSignalTypeList(PyObject* self, PyObject* args); +bp::tuple getEntityClassList(); } namespace pool { -PyObject* writeGraph(PyObject* self, PyObject* args); -PyObject* getEntityList(PyObject* self, PyObject* args); +void writeGraph(const char* filename); +bp::list getEntityList(); +const std::map<std::string, Entity*>* getEntityMap(); } // namespace pool namespace debug { -PyObject* addLoggerFileOutputStream(PyObject* self, PyObject* args); -PyObject* addLoggerCoutOutputStream(PyObject* self, PyObject* args); -PyObject* closeLoggerFileOutputStream(PyObject* self, PyObject* args); -PyObject* realTimeLoggerSpinOnce(PyObject* self, PyObject* args); -PyObject* realTimeLoggerDestroy(PyObject* self, PyObject* args); -PyObject* realTimeLoggerInstance(PyObject* self, PyObject* args); +void addLoggerFileOutputStream(const char* filename); +void addLoggerCoutOutputStream(); +void closeLoggerFileOutputStream(); +void realTimeLoggerSpinOnce(); +void realTimeLoggerDestroy(); +void realTimeLoggerInstance(); } // namespace debug -struct module_state { - PyObject* dgpyError; -}; - -PyObject* plug(PyObject* /*self*/, PyObject* args); - -PyObject* enableTrace(PyObject* /*self*/, PyObject* args); - -PyObject* error_out( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* -#else - PyObject*, PyObject* -#endif -); - -/** - \brief List of python functions -*/ -__attribute__((unused)) static PyMethodDef dynamicGraphMethods[] = { - {"w_plug", dynamicgraph::python::plug, METH_VARARGS, "plug an output signal into an input signal"}, - {"enableTrace", dynamicgraph::python::enableTrace, METH_VARARGS, "Enable or disable tracing debug info in a file"}, - // Signals - {"create_signal_base", dynamicgraph::python::signalBase::create, METH_VARARGS, "create a SignalBase C++ object"}, - {"create_signal_wrapper", dynamicgraph::python::signalBase::createSignalWrapper, METH_VARARGS, - "create a SignalWrapper C++ object"}, - {"signal_base_get_time", dynamicgraph::python::signalBase::getTime, METH_VARARGS, "Get time of a SignalBase"}, - {"signal_base_set_time", dynamicgraph::python::signalBase::setTime, METH_VARARGS, "Set time of a SignalBase"}, - {"signal_base_get_name", dynamicgraph::python::signalBase::getName, METH_VARARGS, "Get the name of a signal"}, - {"signal_base_get_class_name", dynamicgraph::python::signalBase::getClassName, METH_VARARGS, - "Get the class name of a signal"}, - {"signal_base_display", dynamicgraph::python::signalBase::display, METH_VARARGS, "Print the signal in a string"}, - {"signal_base_display_dependencies", dynamicgraph::python::signalBase::displayDependencies, METH_VARARGS, - "Print the signal dependencies in a string"}, - {"signal_base_get_value", dynamicgraph::python::signalBase::getValue, METH_VARARGS, "Read the value of a signal"}, - {"signal_base_set_value", dynamicgraph::python::signalBase::setValue, METH_VARARGS, "Set the value of a signal"}, - {"signal_base_recompute", dynamicgraph::python::signalBase::recompute, METH_VARARGS, - "Recompute the signal at given time"}, - {"signal_base_unplug", dynamicgraph::python::signalBase::unplug, METH_VARARGS, "Unplug the signal"}, - {"signal_base_isPlugged", dynamicgraph::python::signalBase::isPlugged, METH_VARARGS, - "Whether the signal is plugged"}, - {"signal_base_getPlugged", dynamicgraph::python::signalBase::getPlugged, METH_VARARGS, - "To which signal the signal is plugged"}, - // Entity - {"create_entity", dynamicgraph::python::entity::create, METH_VARARGS, "create an Entity C++ object"}, - {"display_entity", dynamicgraph::python::entity::display, METH_VARARGS, "print an Entity C++ object"}, - {"entity_get_name", dynamicgraph::python::entity::getName, METH_VARARGS, "get the name of an Entity"}, - {"entity_get_class_name", dynamicgraph::python::entity::getClassName, METH_VARARGS, - "get the class name of an Entity"}, - {"entity_has_signal", dynamicgraph::python::entity::hasSignal, METH_VARARGS, - "return True if the entity has a signal with the given name"}, - {"entity_get_signal", dynamicgraph::python::entity::getSignal, METH_VARARGS, "get signal by name from an Entity"}, - {"entity_list_signals", dynamicgraph::python::entity::listSignals, METH_VARARGS, - "Return the list of signals of an entity."}, - {"entity_execute_command", dynamicgraph::python::entity::executeCommand, METH_VARARGS, "execute a command"}, - {"entity_list_commands", dynamicgraph::python::entity::listCommands, METH_VARARGS, - "list the commands of an entity"}, - {"entity_get_command_docstring", dynamicgraph::python::entity::getCommandDocstring, METH_VARARGS, - "get the docstring of an entity command"}, - {"entity_get_docstring", dynamicgraph::python::entity::getDocString, METH_VARARGS, - "get the doc string of an entity type"}, - {"factory_get_entity_class_list", dynamicgraph::python::factory::getEntityClassList, METH_VARARGS, - "return the list of entity classes"}, - {"signal_caster_get_type_list", dynamicgraph::python::signalCaster::getSignalTypeList, METH_VARARGS, - "return the list of signal type names"}, - {"writeGraph", dynamicgraph::python::pool::writeGraph, METH_VARARGS, "Write the graph of entities in a filename."}, - {"get_entity_list", dynamicgraph::python::pool::getEntityList, METH_VARARGS, - "return the list of instanciated entities"}, - {"entity_set_logger_verbosity", dynamicgraph::python::entity::setLoggerVerbosityLevel, METH_VARARGS, - "set the verbosity level of the entity"}, - {"entity_get_logger_verbosity", dynamicgraph::python::entity::getLoggerVerbosityLevel, METH_VARARGS, - "get the verbosity level of the entity"}, - {"addLoggerFileOutputStream", dynamicgraph::python::debug::addLoggerFileOutputStream, METH_VARARGS, - "add a output file stream to the logger by filename"}, - {"addLoggerCoutOutputStream", dynamicgraph::python::debug::addLoggerCoutOutputStream, METH_VARARGS, - "add std::cout as output stream to the logger"}, - {"closeLoggerFileOutputStream", dynamicgraph::python::debug::closeLoggerFileOutputStream, METH_VARARGS, - "close all the loggers file output streams."}, - {"entity_set_time_sample", dynamicgraph::python::entity::setTimeSample, METH_VARARGS, - "set the time sample for printing debugging information"}, - {"entity_get_time_sample", dynamicgraph::python::entity::getTimeSample, METH_VARARGS, - "get the time sample for printing debugging information"}, - {"entity_set_stream_print_period", dynamicgraph::python::entity::setStreamPrintPeriod, METH_VARARGS, - "set the period at which debugging information are printed"}, - {"entity_get_stream_print_period", dynamicgraph::python::entity::getStreamPrintPeriod, METH_VARARGS, - "get the period at which debugging information are printed"}, - {"real_time_logger_destroy", dynamicgraph::python::debug::realTimeLoggerDestroy, METH_VARARGS, - "Destroy the real time logger."}, - {"real_time_logger_spin_once", dynamicgraph::python::debug::realTimeLoggerSpinOnce, METH_VARARGS, - "Destroy the real time logger."}, - {"real_time_logger_instance", dynamicgraph::python::debug::realTimeLoggerInstance, METH_VARARGS, - "Starts the real time logger."}, - {"error_out", (PyCFunction)dynamicgraph::python::error_out, METH_NOARGS, NULL}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -#if PY_MAJOR_VERSION >= 3 -__attribute__((unused)) static struct PyModuleDef dynamicGraphModuleDef = { - PyModuleDef_HEAD_INIT, - "wrap", - NULL, - sizeof(struct dynamicgraph::python::module_state), - dynamicGraphMethods, - NULL, - NULL, - NULL, - NULL}; -#define GETSTATE(m) ((struct dynamicgraph::python::module_state*)PyModule_GetState(m)) -#define DGPYERROR(m) GETSTATE(m)->dgpyError -#define INITERROR return NULL -#else -__attribute__((unused)) static struct module_state _state; -#define GETSTATE(m) (&dynamicgraph::python::_state) -#define DGPYERROR(m) dynamicgraph::python::dgpyError -#define INITERROR return -#endif - } // namespace python } // namespace dynamicgraph diff --git a/include/dynamic-graph/python/exception-python.hh b/include/dynamic-graph/python/exception-python.hh deleted file mode 100644 index 9dd7d2fc6f2298363b123fc90732172d7048c49d..0000000000000000000000000000000000000000 --- a/include/dynamic-graph/python/exception-python.hh +++ /dev/null @@ -1,47 +0,0 @@ -// -*- mode: c++ -*- -// Copyright 2010, François Bleibel, Thomas Moulard, Olivier Stasse, -// JRL, CNRS/AIST. - -#ifndef DYNAMIC_GRAPH_PYTHON_EXCEPTION_PYTHON_H -#define DYNAMIC_GRAPH_PYTHON_EXCEPTION_PYTHON_H - -#include <dynamic-graph/fwd.hh> -#include <dynamic-graph/exception-abstract.h> -#include "dynamic-graph/python/python-compat.hh" - -// Depending on whether one is building or using the -// library define DLLAPI to import or export. -#if defined(WIN32) -#if defined(wrap_EXPORTS) -#define WRAP_DLLAPI __declspec(dllexport) -#else -#define WRAP_DLLAPI __declspec(dllimport) -#endif -#else -#define WRAP_DLLAPI -#endif - -namespace dynamicgraph { -namespace python { - -/// \ingroup error -/// -/// \brief Generic error class. -class WRAP_DLLAPI ExceptionPython : public ExceptionAbstract { - public: - enum ErrorCodeEnum { GENERIC, VALUE_PARSING, VECTOR_PARSING, MATRIX_PARSING, CLASS_INCONSISTENT }; - - static const std::string EXCEPTION_NAME; - - explicit ExceptionPython(const ExceptionPython::ErrorCodeEnum& errcode, const std::string& msg = ""); - - ExceptionPython(const ExceptionPython::ErrorCodeEnum& errcode, const std::string& msg, const char* format, ...); - - virtual ~ExceptionPython() throw() {} - - virtual const std::string& getExceptionName() const { return ExceptionPython::EXCEPTION_NAME; } -}; -} // end of namespace python -} // end of namespace dynamicgraph - -#endif //! DYNAMIC_GRAPH_PYTHON_EXCEPTION_PYTHON_H diff --git a/include/dynamic-graph/python/exception.hh b/include/dynamic-graph/python/exception.hh deleted file mode 100644 index 522c45c4e2efd06012829d397ae2f35a9009dbfa..0000000000000000000000000000000000000000 --- a/include/dynamic-graph/python/exception.hh +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2010, Florent Lamiraux, Thomas Moulard, LAAS-CNRS. - -#ifndef DYNAMIC_GRAPH_PYTHON_EXCEPTION -#define DYNAMIC_GRAPH_PYTHON_EXCEPTION - -#include "dynamic-graph/python/dynamic-graph-py.hh" - -/// \brief Catch all exceptions which may be sent when C++ code is -/// called. -#define CATCH_ALL_EXCEPTIONS(m) \ - catch (const std::exception& exc) { \ - PyErr_SetString(DGPYERROR(m), exc.what()); \ - return NULL; \ - } \ - catch (const char* s) { \ - PyErr_SetString(DGPYERROR(m), s); \ - return NULL; \ - } \ - catch (...) { \ - PyErr_SetString(DGPYERROR(m), "Unknown exception"); \ - return NULL; \ - } \ - struct e_n_d__w_i_t_h__s_e_m_i_c_o_l_o_n - -#endif //! DYNAMIC_GRAPH_PYTHON_EXCEPTION diff --git a/include/dynamic-graph/python/module.hh b/include/dynamic-graph/python/module.hh new file mode 100644 index 0000000000000000000000000000000000000000..b93787b8c3937056474ddeae6b0534b4f4d12274 --- /dev/null +++ b/include/dynamic-graph/python/module.hh @@ -0,0 +1,81 @@ +#ifndef DYNAMIC_GRAPH_PYTHON_MODULE_HH +#define DYNAMIC_GRAPH_PYTHON_MODULE_HH + +#ifdef PINOCCHIO_WITH_URDFDOM +// If pinocchio is used, we have to include pinocchio header before boost mpl +#include <pinocchio/fwd.hpp> +#endif + +#include <boost/python.hpp> +#include <boost/mpl/for_each.hpp> + +#include <dynamic-graph/entity.h> +#include <dynamic-graph/python/dynamic-graph-py.hh> + +namespace dynamicgraph { +namespace python { + +constexpr int AddSignals = 1; +constexpr int AddCommands = 2; + +namespace internal { + +template <typename T, int Options = AddCommands | AddSignals> +bp::object makeEntity1(const char* name) { + Entity* ent = entity::create(T::CLASS_NAME.c_str(), name); + assert(dynamic_cast<T*>(ent) != NULL); + bp::object obj(bp::ptr(static_cast<T*>(ent))); + if (Options & AddCommands) entity::addCommands(obj); + if (Options & AddSignals) entity::addSignals(obj); + return obj; +} +template <typename T, int Options = AddCommands | AddSignals> +bp::object makeEntity2() { + return makeEntity1<T, Options>(""); +} + +} // namespace internal + +/// \tparam Options by default, all the signals and commands are added as +/// attribute to the Python object. This behaviour works fine for +/// entities that have static commands and signals. +/// If some commands or signals are added or removed dynamiccally, then +/// it is better to disable the default behaviour and handle it +/// specifically. +template <typename T, typename bases = boost::python::bases<dynamicgraph::Entity>, + int Options = AddCommands | AddSignals> +inline auto exposeEntity() { + // std::string hiddenClassName ("_" + T::CLASS_NAME); + std::string hiddenClassName(T::CLASS_NAME); + namespace bp = boost::python; + bp::class_<T, bases, boost::noncopyable> obj(hiddenClassName.c_str(), bp::init<std::string>()); + /* TODO at the moment, I couldn't easily find a way to define a Python constructor + * that would create the entity via the factory and then populate the + * python object with its commands. + * This is achieved with a factory function of the same name. + obj.def ("__init__", bp::raw_function(+[](bp::object args, bp::dict) { + if (bp::len(args) != 2) + throw std::length_error("Expected 2 arguments."); + bp::object self = args[0]; + self.attr("__init__")(bp::extract<std::string>(args[1])); + Entity* ent = entity::create(T::CLASS_NAME.c_str(), name); + if (dynamic_cast<T*>(ent) == NULL) + std::cout << "foo" << std::endl; + assert(dynamic_cast<T*>(ent) != NULL); + self = bp::object(bp::ptr(static_cast<T*>(ent))); + //dynamicgraph::Entity& unused = bp::extract<dynamicgraph::Entity&>(self); + //entity::addCommands(self); + }) + ; + */ + bp::def(T::CLASS_NAME.c_str(), &internal::makeEntity1<T, Options>); + bp::def(T::CLASS_NAME.c_str(), &internal::makeEntity2<T, Options>); + if (!(Options & AddCommands)) obj.def("add_commands", &entity::addCommands); + if (!(Options & AddSignals)) obj.def("add_signals", &entity::addSignals); + return obj; +} + +} // namespace python +} // namespace dynamicgraph + +#endif // DYNAMIC_GRAPH_PYTHON_MODULE_HH diff --git a/include/dynamic-graph/python/signal-wrapper.hh b/include/dynamic-graph/python/signal-wrapper.hh index 3fd4cb3eeeb8c642be139536d60483981ca0ee71..941ca970d3a517aec0448922d35af16918813388 100644 --- a/include/dynamic-graph/python/signal-wrapper.hh +++ b/include/dynamic-graph/python/signal-wrapper.hh @@ -4,6 +4,8 @@ #ifndef DGPY_SIGNAL_WRAPPER #define DGPY_SIGNAL_WRAPPER +#include <boost/python.hpp> + #include <dynamic-graph/linear-algebra.h> #include <dynamic-graph/signal.h> #include <dynamic-graph/entity.h> @@ -11,22 +13,12 @@ namespace dynamicgraph { namespace python { -namespace signalWrapper { -void convert(PyObject* o, int& v); -void convert(PyObject* o, bool& v); -void convert(PyObject* o, float& v); -void convert(PyObject* o, double& v); -// void convert (PyObject* o, std::string& v); -void convert(PyObject* o, Vector& v); -// void convert (PyObject* o, Eigen::MatrixXd& v); -// void convert (PyObject* o, Eigen::Matrix4d& v); -} // namespace signalWrapper class PythonSignalContainer : public Entity { DYNAMIC_GRAPH_ENTITY_DECL(); public: - PythonSignalContainer(const std::string& name); + using Entity::Entity; void signalRegistration(const SignalArray<int>& signals); @@ -37,17 +29,17 @@ template <class T, class Time> class SignalWrapper : public Signal<T, Time> { public: typedef Signal<T, Time> parent_t; + typedef boost::python::object pyobject; - static bool checkCallable(PyObject* c, std::string& error); + static bool checkCallable(pyobject c, std::string& error); - SignalWrapper(std::string name, PyObject* _callable) : parent_t(name), callable(_callable) { + SignalWrapper(std::string name, pyobject callable) : parent_t(name), callable(callable) { typedef boost::function2<T&, T&, Time> function_t; - Py_INCREF(callable); function_t f = boost::bind(&SignalWrapper::call, this, _1, _2); this->setFunction(f); } - virtual ~SignalWrapper() { Py_DECREF(callable); }; + virtual ~SignalWrapper(){}; private: T& call(T& value, Time t) { @@ -56,18 +48,12 @@ class SignalWrapper : public Signal<T, Time> { if (PyGILState_GetThisThreadState() == NULL) { dgDEBUG(10) << "python thread not initialized" << std::endl; } - char format[] = "i"; - PyObject* obj = PyObject_CallFunction(callable, format, t); - if (obj == NULL) { - dgERROR << "Could not call callable" << std::endl; - } else { - signalWrapper::convert(obj, value); - Py_DECREF(obj); - } + pyobject obj = callable(t); + value = boost::python::extract<T>(obj); PyGILState_Release(gstate); return value; } - PyObject* callable; + pyobject callable; }; } // namespace python diff --git a/include/dynamic-graph/python/signal.hh b/include/dynamic-graph/python/signal.hh new file mode 100644 index 0000000000000000000000000000000000000000..ea115e655df193dbee1f9c8c57cf8230ea005685 --- /dev/null +++ b/include/dynamic-graph/python/signal.hh @@ -0,0 +1,73 @@ +// Copyright 2020, Joseph Mirabel, LAAS-CNRS. + +#include <sstream> + +#include <boost/python.hpp> + +#include <dynamic-graph/signal-base.h> +#include <dynamic-graph/signal-ptr.h> +#include <dynamic-graph/signal-time-dependent.h> +#include <dynamic-graph/signal.h> + +#include "dynamic-graph/python/signal-wrapper.hh" + +namespace dynamicgraph { +namespace python { + +template <typename T, typename Time> +auto exposeSignal(const std::string& name) { + namespace bp = boost::python; + + typedef Signal<T, Time> S_t; + bp::class_<S_t, bp::bases<SignalBase<Time> >, boost::noncopyable> obj( + name.c_str(), bp::init<std::string>()); + obj.add_property( + "value", + bp::make_function(&S_t::accessCopy, + bp::return_value_policy<bp::copy_const_reference>()), + &S_t::setConstant, // TODO check the setter + "the signal value.\n" + "warning: for Eigen objects, sig.value[0] = 1. may not work)."); + return obj; +} + +template <typename T, typename Time> +auto exposeSignalWrapper(const std::string& name) { + namespace bp = boost::python; + + typedef SignalWrapper<T, Time> S_t; + bp::class_<S_t, bp::bases<Signal<T, Time> >, boost::noncopyable> obj( + name.c_str(), bp::no_init); + return obj; +} + +template <typename T, typename Time> +auto exposeSignalPtr(const std::string& name) { + namespace bp = boost::python; + + typedef SignalPtr<T, Time> S_t; + bp::class_<S_t, bp::bases<Signal<T, Time> >, boost::noncopyable> obj( + name.c_str(), bp::no_init); + return obj; +} + +template <typename T, typename Time> +auto exposeSignalTimeDependent(const std::string& name) { + namespace bp = boost::python; + + typedef SignalTimeDependent<T, Time> S_t; + bp::class_<S_t, bp::bases<Signal<T, Time> >, boost::noncopyable> obj( + name.c_str(), bp::no_init); + return obj; +} + +template <typename T, typename Time> +void exposeSignalsOfType(const std::string& name) { + exposeSignal<T, Time>("Signal" + name); + exposeSignalPtr<T, Time>("SignalPtr" + name); + exposeSignalWrapper<T, Time>("SignalWrapper" + name); + exposeSignalTimeDependent<T, Time>("SignalTimeDependent" + name); +} + +} // namespace python +} // namespace dynamicgraph diff --git a/package.xml b/package.xml index 5338cfc13ac95a3536742efa07fdfd10678b610a..60a02df01607ba3c067d688e8687ebb5daa77d99 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ <?xml version="1.0"?> <package format="3"> <name>dynamic-graph-python</name> - <version>3.5.3</version> + <version>4.0.0</version> <description> Dynamic graph library Python bindings </description> @@ -20,7 +20,8 @@ <exec_depend condition="$ROS_VERSION == 2">ament_cmake</exec_depend> <depend>dynamic-graph</depend> <depend>boost</depend> - + <depend>eigenpy</depend> + <buildtool_depend>cmake</buildtool_depend> <export> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b8cf40d3eedd630f6dbe398e3772f5ed366a9e0f..98a95a689d37565dd64eba19566ac635219576fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,8 @@ FOREACH(source ${PYTHON_SOURCES}) ENDFOREACH(source) # --- ADD the wrap on the dg modules -DYNAMIC_GRAPH_PYTHON_MODULE("tracer" dynamic-graph::tracer tracer-wrap) -DYNAMIC_GRAPH_PYTHON_MODULE("tracer_real_time" dynamic-graph::tracer-real-time - tracer_real_time-wrap) +LINK_DIRECTORIES(${DYNAMIC_GRAPH_PLUGINDIR}) +DYNAMIC_GRAPH_PYTHON_MODULE("tracer" dynamic-graph::tracer tracer-wrap + SOURCE_PYTHON_MODULE ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_graph/tracer/wrap.cc) +DYNAMIC_GRAPH_PYTHON_MODULE("tracer_real_time" dynamic-graph::tracer-real-time tracer_real_time-wrap + SOURCE_PYTHON_MODULE ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_graph/tracer_real_time/wrap.cc) diff --git a/src/dynamic_graph/CMakeLists.txt b/src/dynamic_graph/CMakeLists.txt index adc8bd7f4eb17f79ee008e08ca7ea8e9bd158bbf..19e38cb49759cd9b40fbbce582e8428bc7f981b1 100644 --- a/src/dynamic_graph/CMakeLists.txt +++ b/src/dynamic_graph/CMakeLists.txt @@ -3,22 +3,16 @@ SET(PYTHON_MODULE wrap) ADD_LIBRARY(${PYTHON_MODULE} MODULE - convert-dg-to-py.cc debug-py.cc dynamic-graph-py.cc - entity-py.cc - exception-python.cc factory-py.cc pool-py.cc - python-compat.cc signal-base-py.cc - signal-caster-py.cc signal-wrapper.cc ) -TARGET_INCLUDE_DIRECTORIES(${PYTHON_MODULE} SYSTEM PUBLIC ${PYTHON_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES(${PYTHON_MODULE} ${PYTHON_LIBRARY} - dynamic-graph::dynamic-graph) +TARGET_LINK_LIBRARIES(${PYTHON_MODULE} PUBLIC ${PROJECT_NAME} eigenpy::eigenpy) +TARGET_LINK_BOOST_PYTHON(${PYTHON_MODULE} PRIVATE) # Remove prefix lib SET_TARGET_PROPERTIES(${PYTHON_MODULE} PROPERTIES PREFIX "") diff --git a/src/dynamic_graph/__init__.py b/src/dynamic_graph/__init__.py index 0fe54015dafc853beff835183dcb7c8d8fffd0f5..c9a0924adb870e49b87d10fa78a4ceb022740382 100644 --- a/src/dynamic_graph/__init__.py +++ b/src/dynamic_graph/__init__.py @@ -10,26 +10,4 @@ import sys from . import entity # noqa from . import signal_base # noqa -try: - from DLFCN import RTLD_NOW, RTLD_GLOBAL -except ModuleNotFoundError: # Python 3 - from os import RTLD_NOW, RTLD_GLOBAL - -flags = sys.getdlopenflags() - -# Import C++ symbols in a global scope -# This is necessary for signal compiled in different modules to be compatible - -sys.setdlopenflags(RTLD_NOW | RTLD_GLOBAL) from .wrap import * # noqa - -# Recover previous flags -sys.setdlopenflags(flags) - - -def plug(signalOut, signalIn): - """ - Plug an output signal into an input signal - """ - # get signals and entities - w_plug(signalOut.obj, signalIn.obj) # noqa diff --git a/src/dynamic_graph/convert-dg-to-py.cc b/src/dynamic_graph/convert-dg-to-py.cc index 7ad8f4cf5af1a8bc7787e4150af9fea12eaf3230..b9ff9e9b5e5e8615199045426c44bfe935abb895 100644 --- a/src/dynamic_graph/convert-dg-to-py.cc +++ b/src/dynamic_graph/convert-dg-to-py.cc @@ -3,6 +3,9 @@ #include <iostream> #include <sstream> +#include <boost/python.hpp> +#include <boost/python/stl_iterator.hpp> + #include <dynamic-graph/signal-base.h> #include <dynamic-graph/signal.h> #include <dynamic-graph/signal-caster.h> @@ -17,209 +20,38 @@ using ::dynamicgraph::SignalBase; namespace python { namespace convert { -void fillMatrixRow(Matrix& m, unsigned iRow, PyObject* sequence) { - if (PySequence_Size(sequence) != (int)m.cols()) { - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, "lines of matrix have different sizes."); - } - for (int iCol = 0; iCol < m.cols(); iCol++) { - PyObject* pyDouble = PySequence_GetItem(sequence, iCol); - if (PyFloat_Check(pyDouble)) - m(iRow, iCol) = PyFloat_AsDouble(pyDouble); - else if (PyLong_Check(pyDouble)) - m(iRow, iCol) = (int)PyLong_AsLong(pyDouble) + 0.0; - else - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, - "element of matrix should be " - "a floating point number."); - } -} -void fillMatrixRow(Eigen::Matrix4d& m, unsigned iRow, PyObject* sequence) { - if (PySequence_Size(sequence) != (int)m.cols()) { - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, "lines of matrix have different sizes."); - } - for (int iCol = 0; iCol < m.cols(); iCol++) { - PyObject* pyDouble = PySequence_GetItem(sequence, iCol); - if (PyFloat_Check(pyDouble)) - m(iRow, iCol) = PyFloat_AsDouble(pyDouble); - else if (PyLong_Check(pyDouble)) - m(iRow, iCol) = (int)PyLong_AsLong(pyDouble) + 0.0; - else - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, - "element of matrix should be " - "a floating point number."); - } -} +namespace bp = boost::python; -command::Value pythonToValue(PyObject* pyObject, const command::Value::Type& valueType) { +command::Value toValue(bp::object o, const command::Value::Type& valueType) { using command::Value; - bool bvalue; - unsigned uvalue; - int ivalue; - float fvalue; - double dvalue; - std::string svalue; - Vector v; - Matrix m; - Eigen::Matrix4d m4; - Py_ssize_t nCols; - Py_ssize_t size; - PyObject* row; - Py_ssize_t nRows; - switch (valueType) { case (Value::BOOL): - if (!PyBool_Check(pyObject)) { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "bool"); - } - bvalue = PyObject_IsTrue(pyObject); - return Value(bvalue); - break; + return Value(bp::extract<bool>(o)); case (Value::UNSIGNED): - if (PyLong_Check(pyObject)) { - uvalue = (unsigned int)PyLong_AsUnsignedLongMask(pyObject); -#if PY_MAJOR_VERSION == 2 - } else if (PyInt_Check(pyObject)) { - uvalue = (unsigned int)PyInt_AsUnsignedLongMask(pyObject); -#endif - } else { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "unsigned int"); - } - return Value(uvalue); - break; + return Value(bp::extract<unsigned>(o)); case (Value::INT): - if (PyLong_Check(pyObject)) { - ivalue = (int)PyLong_AsLong(pyObject); -#if PY_MAJOR_VERSION == 2 - } else if (PyInt_Check(pyObject)) { - ivalue = (int)PyInt_AsLong(pyObject); -#endif - } else { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "int"); - } - return Value(ivalue); - break; + return Value(bp::extract<int>(o)); case (Value::FLOAT): - if (PyFloat_Check(pyObject)) { - fvalue = (float)PyFloat_AsDouble(pyObject); - } else if (PyLong_Check(pyObject)) { - fvalue = (float)PyLong_AsLong(pyObject); -#if PY_MAJOR_VERSION == 2 - } else if (PyInt_Check(pyObject)) { - fvalue = (float)PyInt_AsLong(pyObject); -#endif - } else { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "float"); - } - return Value(fvalue); - break; + return Value(bp::extract<float>(o)); case (Value::DOUBLE): - if (PyFloat_Check(pyObject)) { - dvalue = PyFloat_AsDouble(pyObject); - } else if (PyLong_Check(pyObject)) { - dvalue = (double)PyLong_AsLong(pyObject); -#if PY_MAJOR_VERSION == 2 - } else if (PyInt_Check(pyObject)) { - dvalue = (double)PyInt_AsLong(pyObject); -#endif - } else { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "double"); - } - return Value(dvalue); - break; + return Value(bp::extract<double>(o)); case (Value::STRING): - if (!PyUnicode_Check(pyObject) -#if PY_MAJOR_VERSION == 2 - && !PyString_Check(pyObject) -#endif - ) { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "string"); - } - svalue = obj_to_str(pyObject); - return Value(svalue); - break; + return Value(bp::extract<std::string>(o)); case (Value::VECTOR): - // Check that argument is a tuple - if (!PySequence_Check(pyObject)) { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "vector"); - } - size = PySequence_Size(pyObject); - v.resize(size); - for (Py_ssize_t i = 0; i < size; i++) { - PyObject* pyDouble = PySequence_GetItem(pyObject, i); - if (PyFloat_Check(pyDouble)) - v(i) = PyFloat_AsDouble(pyDouble); - else if (PyLong_Check(pyDouble)) - v(i) = (int)PyLong_AsLong(pyDouble) + 0.0; -#if PY_MAJOR_VERSION == 2 - else if (PyInt_Check(pyDouble)) - v(i) = (int)PyInt_AsLong(pyDouble) + 0.0; -#endif - else - throw ExceptionPython(ExceptionPython::VECTOR_PARSING, - "element of vector should be a floating " - "point number."); - } - return Value(v); - break; + // TODO for backward compatibility, support tuple or list ? + // I don't think so + return Value(bp::extract<Vector>(o)); case (Value::MATRIX): - // Check that argument is a tuple - if (!PySequence_Check(pyObject)) { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "matrix"); - } - nRows = PySequence_Size(pyObject); - if (nRows == 0) { - return Value(Matrix()); - } - row = PySequence_GetItem(pyObject, 0); - if (!PySequence_Check(row)) { - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, "matrix"); - } - nCols = PySequence_Size(row); - - m.resize((unsigned int)nRows, (unsigned int)nCols); - fillMatrixRow(m, 0, row); - - for (Py_ssize_t iRow = 1; iRow < nRows; iRow++) { - row = PySequence_GetItem(pyObject, iRow); - if (!PySequence_Check(row)) { - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, "matrix"); - } - fillMatrixRow(m, static_cast<unsigned>(iRow), row); - } - return Value(m); - break; + // TODO for backward compatibility, support tuple or list ? + // I don't think so + return Value(bp::extract<Matrix>(o)); case (Value::MATRIX4D): - // Check that argument is a tuple - if (!PySequence_Check(pyObject)) { - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "matrix4d"); - } - nRows = PySequence_Size(pyObject); - if (nRows == 0) { - return Value(Eigen::Matrix4d()); - } - row = PySequence_GetItem(pyObject, 0); - if (!PySequence_Check(row)) { - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, "matrix4d"); - } - nCols = PySequence_Size(row); - - m4.resize(nRows, nCols); - fillMatrixRow(m4, 0, row); - - for (Py_ssize_t iRow = 1; iRow < nRows; iRow++) { - row = PySequence_GetItem(pyObject, iRow); - if (!PySequence_Check(row)) { - throw ExceptionPython(ExceptionPython::MATRIX_PARSING, "matrix"); - } - fillMatrixRow(m4, static_cast<unsigned>(iRow), row); - } - return Value(m4); - break; + return Value(bp::extract<Eigen::Matrix4d>(o)); case (Value::VALUES): // TODO the vector of values cannot be built since // - the value type inside the vector are not know // - inferring the value type from the Python type is not implemented. - throw ExceptionPython(ExceptionPython::VALUE_PARSING, "not implemented: cannot create a vector of values"); + throw std::invalid_argument("not implemented: cannot create a vector of values"); break; default: std::cerr << "Only int, double and string are supported." << std::endl; @@ -227,98 +59,36 @@ command::Value pythonToValue(PyObject* pyObject, const command::Value::Type& val return Value(); } -PyObject* vectorToPython(const Vector& vector) { - PyObject* tuple = PyTuple_New(vector.size()); - for (int index = 0; index < vector.size(); index++) { - PyObject* pyDouble = PyFloat_FromDouble(vector(index)); - PyTuple_SET_ITEM(tuple, index, pyDouble); - } - return tuple; -} - -PyObject* matrixToPython(const Matrix& matrix) { - PyObject* tuple = PyTuple_New(matrix.rows()); - for (int iRow = 0; iRow < matrix.rows(); iRow++) { - PyObject* row = PyTuple_New(matrix.cols()); - for (int iCol = 0; iCol < matrix.cols(); iCol++) { - PyObject* pyDouble = PyFloat_FromDouble(matrix(iRow, iCol)); - PyTuple_SET_ITEM(row, iCol, pyDouble); - } - PyTuple_SET_ITEM(tuple, iRow, row); - } - return tuple; -} - -PyObject* matrix4dToPython(const Eigen::Matrix4d& matrix) { - PyObject* tuple = PyTuple_New(matrix.rows()); - for (int iRow = 0; iRow < matrix.rows(); iRow++) { - PyObject* row = PyTuple_New(matrix.cols()); - for (int iCol = 0; iCol < matrix.cols(); iCol++) { - PyObject* pyDouble = PyFloat_FromDouble(matrix(iRow, iCol)); - PyTuple_SET_ITEM(row, iCol, pyDouble); - } - PyTuple_SET_ITEM(tuple, iRow, row); - } - return tuple; -} - -PyObject* valuesToPython(const dynamicgraph::command::Values& vector) { - PyObject* tuple = PyTuple_New(vector.size()); - for (std::size_t index = 0; index < vector.size(); index++) { - PyObject* item = valueToPython(vector[index]); - PyTuple_SET_ITEM(tuple, index, item); - } - return tuple; -} - -PyObject* valueToPython(const command::Value& value) { +bp::object fromValue(const command::Value& value) { using command::Value; - bool boolValue; - unsigned unsignedValue; - int intValue; - float floatValue; - double doubleValue; - std::string stringValue; - Vector vectorValue; - Matrix matrixValue; - Eigen::Matrix4d matrix4dValue; switch (value.type()) { case (Value::BOOL): - boolValue = value.value(); - if (boolValue) { - return PyBool_FromLong(1); - } - return PyBool_FromLong(0); + return bp::object(value.boolValue()); case (Value::UNSIGNED): - unsignedValue = value.value(); - return Py_BuildValue("I", unsignedValue); + return bp::object(value.unsignedValue()); case (Value::INT): - intValue = value.value(); - return Py_BuildValue("i", intValue); + return bp::object(value.intValue()); case (Value::FLOAT): - floatValue = value.value(); - return Py_BuildValue("f", floatValue); + return bp::object(value.floatValue()); case (Value::DOUBLE): - doubleValue = value.value(); - return Py_BuildValue("d", doubleValue); + return bp::object(value.doubleValue()); case (Value::STRING): - stringValue = (std::string)value.value(); - return Py_BuildValue("s", stringValue.c_str()); + return bp::object(value.stringValue()); case (Value::VECTOR): - vectorValue = value.value(); - return vectorToPython(vectorValue); + return bp::object(value.vectorValue()); case (Value::MATRIX): - matrixValue = value.value(); - return matrixToPython(matrixValue); + return bp::object(value.matrixXdValue()); case (Value::MATRIX4D): - matrix4dValue = value.value(); - return matrix4dToPython(matrix4dValue); - case (Value::VALUES): - return valuesToPython(value.constValuesValue()); + return bp::object(value.matrix4dValue()); + case (Value::VALUES): { + bp::list list; + for (const Value& v : value.constValuesValue()) list.append(fromValue(v)); + return list; + } + case (Value::NONE): default: - return Py_BuildValue(""); + return bp::object(); } - return Py_BuildValue(""); } } // namespace convert diff --git a/src/dynamic_graph/debug-py.cc b/src/dynamic_graph/debug-py.cc index e612f0d2858bb1527749968d2e66e1c0d24c9f8d..dc72df831f2d7904642569f46dfdcdd755139f8c 100644 --- a/src/dynamic_graph/debug-py.cc +++ b/src/dynamic_graph/debug-py.cc @@ -11,7 +11,6 @@ #include <dynamic-graph/pool.h> #include <dynamic-graph/entity.h> #include <vector> -#include "dynamic-graph/python/exception.hh" #include <boost/shared_ptr.hpp> #include "dynamic-graph/python/dynamic-graph-py.hh" @@ -21,108 +20,31 @@ typedef boost::shared_ptr<std::ofstream> ofstreamShrPtr; namespace dynamicgraph { namespace python { -#if PY_MAJOR_VERSION == 2 -extern PyObject* dgpyError; -#endif - namespace debug { std::map<std::string, ofstreamShrPtr> mapOfFiles_; -PyObject* addLoggerFileOutputStream( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - char* filename; - if (!PyArg_ParseTuple(args, "s", &filename)) return NULL; - std::string sfilename(filename); - try { - std::ofstream* aofs = new std::ofstream; - ofstreamShrPtr ofs_shrptr = boost::shared_ptr<std::ofstream>(aofs); - aofs->open(filename, std::ofstream::out); - dynamicgraph::RealTimeLogger::instance(); - dgADD_OSTREAM_TO_RTLOG(*aofs); - dgRTLOG() << "Added " << filename << " as an output stream \n"; - mapOfFiles_[sfilename] = ofs_shrptr; - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); +void addLoggerFileOutputStream(const char* filename) { + std::ofstream* aofs = new std::ofstream; + ofstreamShrPtr ofs_shrptr = boost::shared_ptr<std::ofstream>(aofs); + aofs->open(filename, std::ofstream::out); + dynamicgraph::RealTimeLogger::instance(); + dgADD_OSTREAM_TO_RTLOG(*aofs); + dgRTLOG() << "Added " << filename << " as an output stream \n"; + mapOfFiles_[filename] = ofs_shrptr; } -PyObject* closeLoggerFileOutputStream( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* -#else - PyObject*, PyObject* -#endif -) { - try { - for (std::map<std::string, ofstreamShrPtr>::iterator it = mapOfFiles_.begin(); it != mapOfFiles_.end(); ++it) { - it->second->close(); - } - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); +void closeLoggerFileOutputStream() { + for (const auto& el : mapOfFiles_) el.second->close(); } -PyObject* addLoggerCoutOutputStream( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* -#else - PyObject*, PyObject* -#endif -) { - try { - dgADD_OSTREAM_TO_RTLOG(std::cout); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} +void addLoggerCoutOutputStream() { dgADD_OSTREAM_TO_RTLOG(std::cout); } -PyObject* realTimeLoggerDestroy( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* -#else - PyObject*, PyObject* -#endif -) { - try { - RealTimeLogger::destroy(); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} +void realTimeLoggerDestroy() { RealTimeLogger::destroy(); } -PyObject* realTimeLoggerSpinOnce( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* -#else - PyObject*, PyObject* -#endif -) { - try { - RealTimeLogger::instance().spinOnce(); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} +void realTimeLoggerSpinOnce() { RealTimeLogger::instance().spinOnce(); } -PyObject* realTimeLoggerInstance( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* -#else - PyObject*, PyObject* -#endif -) { - try { - RealTimeLogger::instance(); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} +void realTimeLoggerInstance() { RealTimeLogger::instance(); } } // namespace debug } // namespace python diff --git a/src/dynamic_graph/dynamic-graph-py.cc b/src/dynamic_graph/dynamic-graph-py.cc index e26b8bdc1abeb337f8462c900de1bc8f968e0c87..7a6f0f6d479faa2ad6d6372db4bb1d8e1703eba3 100644 --- a/src/dynamic_graph/dynamic-graph-py.cc +++ b/src/dynamic_graph/dynamic-graph-py.cc @@ -3,165 +3,258 @@ #include <iostream> #include <sstream> +#include <boost/python.hpp> +#include <boost/python/suite/indexing/map_indexing_suite.hpp> + +#include <eigenpy/eigenpy.hpp> +#include <Eigen/Geometry> +#include <eigenpy/geometry.hpp> + #include <dynamic-graph/debug.h> #include <dynamic-graph/exception-factory.h> #include <dynamic-graph/signal-base.h> +#include <dynamic-graph/signal.h> +#include <dynamic-graph/signal-time-dependent.h> +#include <dynamic-graph/entity.h> +#include <dynamic-graph/command.h> +#include <dynamic-graph/factory.h> +#include <dynamic-graph/pool.h> + +#include <dynamic-graph/tracer.h> -#include "dynamic-graph/python/exception.hh" #include "dynamic-graph/python/dynamic-graph-py.hh" #include "dynamic-graph/python/signal-wrapper.hh" +#include "dynamic-graph/python/convert-dg-to-py.hh" +#include "dynamic-graph/python/module.hh" namespace dynamicgraph { namespace python { -#if PY_MAJOR_VERSION == 2 -PyObject* dgpyError; -#endif - /** \brief plug a signal into another one. */ -PyObject* plug( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* objOut = NULL; - PyObject* objIn = NULL; - void* pObjOut; - void* pObjIn; - - if (!PyArg_ParseTuple(args, "OO", &objOut, &objIn)) return NULL; - - if (!PyCapsule_CheckExact(objOut)) { - PyErr_SetString(PyExc_TypeError, - "first argument should be a pointer to" - " signalBase<int>."); - return NULL; - } - if (!PyCapsule_CheckExact(objIn)) { - PyErr_SetString(PyExc_TypeError, - "second argument should be a pointer to" - " signalBase<int>."); - return NULL; - } +void plug(SignalBase<int>* signalOut, SignalBase<int>* signalIn) { signalIn->plug(signalOut); } - pObjIn = PyCapsule_GetPointer(objIn, "dynamic_graph.Signal"); - SignalBase<int>* signalIn = (SignalBase<int>*)pObjIn; - if (signalIn == NULL) { - std::ostringstream oss; - oss << "dynamic_graph.plug(a, b): Argument 'b' must be of type 'dynamic_graph.Signal', but got " - << PyCapsule_GetName(objIn); - PyErr_SetString(PyExc_TypeError, oss.str().c_str()); - return NULL; - } - pObjOut = PyCapsule_GetPointer(objOut, "dynamic_graph.Signal"); - SignalBase<int>* signalOut = (SignalBase<int>*)pObjOut; - if (signalOut == NULL) { - std::ostringstream oss; - oss << "dynamic_graph.plug(a, b): Argument 'a' must be of type 'dynamic_graph.Signal', but got " - << PyCapsule_GetName(objOut); - PyErr_SetString(PyExc_TypeError, oss.str().c_str()); - return NULL; - } - std::ostringstream os; - - try { - signalIn->plug(signalOut); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); +void enableTrace(bool enable, const char* filename) { + if (enable) + DebugTrace::openFile(filename); + else + DebugTrace::closeFile(filename); } -PyObject* enableTrace( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* boolean; - char* filename = NULL; - - if (PyArg_ParseTuple(args, "Os", &boolean, &filename)) { - if (!PyBool_Check(boolean)) { - PyErr_SetString(PyExc_TypeError, - "enableTrace takes as first " - "argument True or False,\n" - " and as " - "second argument a filename."); - return NULL; - } - if (PyObject_IsTrue(boolean)) { - try { - DebugTrace::openFile(filename); - } - CATCH_ALL_EXCEPTIONS(m); - } else { - try { - DebugTrace::closeFile(filename); - } - CATCH_ALL_EXCEPTIONS(m); - } - } else { - return NULL; +} // namespace python +} // namespace dynamicgraph + +namespace bp = boost::python; +namespace dg = dynamicgraph; + +typedef bp::return_value_policy<bp::manage_new_object> manage_new_object; +typedef bp::return_value_policy<bp::reference_existing_object> reference_existing_object; + +typedef dg::PoolStorage::Entities MapOfEntities; + +struct MapOfEntitiesPairToPythonConverter { + static PyObject* convert(const MapOfEntities::value_type& pair) { + return bp::incref(bp::make_tuple(pair.first, bp::ptr(pair.second)).ptr()); } - return Py_BuildValue(""); +}; + +MapOfEntities* getEntityMap() { return const_cast<MapOfEntities*>(&dg::PoolStorage::getInstance()->getEntityMap()); } + +dg::SignalBase<int>* getSignal(dg::Entity& e, const std::string& name) { return &e.getSignal(name); } + +class PythonEntity : public dg::Entity { + DYNAMIC_GRAPH_ENTITY_DECL(); + + public: + using dg::Entity::Entity; + + void signalRegistration(dg::SignalBase<int>& signal) { dg::Entity::signalRegistration(signal); } + void signalDeregistration(const std::string& name) { dg::Entity::signalDeregistration(name); } +}; + +DYNAMICGRAPH_FACTORY_ENTITY_PLUGIN(PythonEntity, "PythonEntity"); + +void exposeEntityBase() { + using namespace dynamicgraph; + bp::enum_<LoggerVerbosity>("LoggerVerbosity") + .value("VERBOSITY_ALL", VERBOSITY_ALL) + .value("VERBOSITY_INFO_WARNING_ERROR", VERBOSITY_INFO_WARNING_ERROR) + .value("VERBOSITY_WARNING_ERROR", VERBOSITY_WARNING_ERROR) + .value("VERBOSITY_ERROR", VERBOSITY_ERROR) + .value("VERBOSITY_NONE", VERBOSITY_NONE) + .export_values(); + + bp::class_<Entity, boost::noncopyable>("Entity", bp::no_init) + .add_property("name", bp::make_function(&Entity::getName, bp::return_value_policy<bp::copy_const_reference>())) + .add_property("className", + bp::make_function(&Entity::getClassName, bp::return_value_policy<bp::copy_const_reference>()), + "the class name of the Entity") + .add_property("__doc__", &Entity::getDocString) + + .def("setLoggerVerbosityLevel", &Entity::setLoggerVerbosityLevel) + .def("getLoggerVerbosityLevel", &Entity::getLoggerVerbosityLevel) + .add_property("loggerVerbosityLevel", &Entity::setLoggerVerbosityLevel, &Entity::getLoggerVerbosityLevel, + "the verbosity level of the entity") + .def("setTimeSample", &Entity::setTimeSample) + .def("getTimeSample", &Entity::getTimeSample) + .add_property("timeSample", &Entity::getTimeSample, &Entity::setTimeSample, + "the time sample for printing debugging information") + .def("setStreamPrintPeriod", &Entity::setStreamPrintPeriod) + .def("getStreamPrintPeriod", &Entity::getStreamPrintPeriod) + .add_property("streamPrintPeriod", &Entity::getStreamPrintPeriod, &Entity::setStreamPrintPeriod, + "set the period at which debugging information are printed") + + .def("__str__", + +[](const Entity& e) -> std::string { + std::ostringstream os; + e.display(os); + return os.str(); + }) + .def("signals", + +[](const Entity& e) -> bp::list { + bp::list ret; + for (auto& el : e.getSignalMap()) ret.append(bp::ptr(el.second)); + return ret; + }, + "Return the list of signals.") + //.def("signal", +[](Entity& e, const std::string &name) { return &e.getSignal(name); }, + // reference_existing_object()) + .def("signal", &getSignal, reference_existing_object(), "get signal by name from an Entity", bp::arg("name")) + .def("hasSignal", &Entity::hasSignal, "return True if the entity has a signal with the given name") + + .def("displaySignals", + +[](const Entity& e) { + Entity::SignalMap signals(e.getSignalMap()); + std::cout << "--- <" << e.getName(); + if (signals.empty()) + std::cout << "> has no signal\n"; + else + std::cout << "> signal list:\n"; + for (const auto& el : signals) el.second->display(std::cout << " |-- <") << '\n'; + }, + "Print the list of signals into standard output: temporary.") + + /* + .def("__getattr__", +[](Entity& e, const std::string &name) -> SignalBase<int>* { return &e.getSignal(name); }, + reference_existing_object()) + def __getattr__(self, name): + try: + return self.signal(name) + except Exception: + try: + object.__getattr__(self, name) + except AttributeError: + raise AttributeError("'%s' entity has no attribute %s\n" % (self.name, name) + + ' entity attributes are usually either\n' + ' - commands,\n' + + ' - signals or,\n' + ' - user defined attributes') + */ + /* + .def("__setattr__", +[](bp::object self, const std::string &name, bp::object value) { + Entity& e = bp::extract<Entity&> (self); + if (e.hasSignal(name)) + throw std::invalid_argument(name + " already designates a signal. " + "It is not advised to set a new attribute of the same name."); + // TODO How do you do that ? I am sure it is possible. + //object.__setattr__(self, name, value) + }) + */ + + /* TODO ? + def boundNewCommand(self, cmdName): + """ + At construction, all existing commands are bound directly in the class. + This method enables to bound new commands dynamically. These new bounds + are not made with the class, but directly with the object instance. + """ + def boundAllNewCommands(self): + """ + For all commands that are not attribute of the object instance nor of the + class, a new attribute of the instance is created to bound the command. + """ + */ + + // For backward compat + .add_static_property("entities", bp::make_function(&getEntityMap, reference_existing_object())); + + python::exposeEntity<PythonEntity, bp::bases<Entity>, 0>() + .def("signalRegistration", &PythonEntity::signalRegistration) + .def("signalDeregistration", &PythonEntity::signalDeregistration); + + python::exposeEntity<python::PythonSignalContainer, bp::bases<Entity>, 0>().def( + "rmSignal", &python::PythonSignalContainer::rmSignal, "Remove a signal", bp::arg("signal_name")); } -PyObject* error_out( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* -#else - PyObject*, PyObject* -#endif -) { - PyErr_SetString(DGPYERROR(m), "something bad happened"); - return NULL; +void exposeCommand() { + using dg::command::Command; + bp::class_<Command, boost::noncopyable>("Command", bp::no_init) + .def("__call__", bp::raw_function(dg::python::entity::executeCmd, 1), "execute the command") + .add_property("__doc__", &Command::getDocstring); } -} // namespace python -} // namespace dynamicgraph +void exposeOldAPI() { + bp::def("plug", dynamicgraph::python::plug, "plug an output signal into an input signal", + (bp::arg("signalOut"), "signalIn")); + bp::def("enableTrace", dynamicgraph::python::enableTrace, "Enable or disable tracing debug info in a file"); + // Signals + bp::def("create_signal_wrapper", dynamicgraph::python::signalBase::createSignalWrapper, reference_existing_object(), + "create a SignalWrapper C++ object"); + // Entity + bp::def("factory_get_entity_class_list", dynamicgraph::python::factory::getEntityClassList, + "return the list of entity classes"); + bp::def("writeGraph", dynamicgraph::python::pool::writeGraph, "Write the graph of entities in a filename."); + bp::def("get_entity_list", dynamicgraph::python::pool::getEntityList, "return the list of instanciated entities"); + bp::def("addLoggerFileOutputStream", dynamicgraph::python::debug::addLoggerFileOutputStream, + "add a output file stream to the logger by filename"); + bp::def("addLoggerCoutOutputStream", dynamicgraph::python::debug::addLoggerCoutOutputStream, + "add std::cout as output stream to the logger"); + bp::def("closeLoggerFileOutputStream", dynamicgraph::python::debug::closeLoggerFileOutputStream, + "close all the loggers file output streams."); + bp::def("real_time_logger_destroy", dynamicgraph::python::debug::realTimeLoggerDestroy, + "Destroy the real time logger."); + bp::def("real_time_logger_spin_once", dynamicgraph::python::debug::realTimeLoggerSpinOnce, + "Destroy the real time logger."); + bp::def("real_time_logger_instance", dynamicgraph::python::debug::realTimeLoggerInstance, + "Starts the real time logger."); +} -#ifdef __cplusplus -extern "C" { -#endif - -#if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC PyInit_wrap(void) -#else -void initwrap(void) -#endif -{ -#if PY_MAJOR_VERSION >= 3 - PyObject* m = PyModule_Create(&dynamicgraph::python::dynamicGraphModuleDef); -#else - PyObject* m = Py_InitModule("wrap", dynamicgraph::python::dynamicGraphMethods); -#endif - - if (m == NULL) INITERROR; - - DGPYERROR(m) = PyErr_NewException(const_cast<char*>("dynamic_graph.dgpyError"), NULL, NULL); - if (DGPYERROR(m) == NULL) { - Py_DECREF(m); - INITERROR; - } +void enableEigenPy() { + eigenpy::enableEigenPy(); - Py_XINCREF(DGPYERROR(m)); - if (PyModule_AddObject(m, "dgpyError", DGPYERROR(m)) < 0) { - Py_XDECREF(DGPYERROR(m)); - Py_CLEAR(DGPYERROR(m)); - Py_DECREF(m); - INITERROR; - } + if (!eigenpy::register_symbolic_link_to_registered_type<Eigen::Quaterniond>()) eigenpy::exposeQuaternion(); + if (!eigenpy::register_symbolic_link_to_registered_type<Eigen::AngleAxisd>()) eigenpy::exposeAngleAxis(); -#if PY_MAJOR_VERSION >= 3 - return m; -#endif + eigenpy::enableEigenPySpecific<Eigen::Matrix4d>(); } -#ifdef __cplusplus -} // extern "C" -#endif +BOOST_PYTHON_MODULE(wrap) { + enableEigenPy(); + + exposeOldAPI(); + + dg::python::exposeSignals(); + exposeEntityBase(); + exposeCommand(); + + typedef dg::PoolStorage::Entities MapOfEntities; + bp::class_<MapOfEntities>("MapOfEntities") + .def("__len__", &MapOfEntities::size) + .def("keys", + +[](const MapOfEntities& m) -> bp::tuple { + bp::list res; + for (const auto& el : m) res.append(el.first); + return bp::tuple(res); + }) + .def("values", + +[](const MapOfEntities& m) -> bp::tuple { + bp::list res; + for (const auto& el : m) res.append(bp::ptr(el.second)); + return bp::tuple(res); + }) + .def("__getitem__", static_cast<dg::Entity*& (MapOfEntities::*)(const std::string& k)>(&MapOfEntities::at), + reference_existing_object()) + .def("__setitem__", +[](MapOfEntities& m, const std::string& n, dg::Entity* e) { m.emplace(n, e); }) + .def("__iter__", bp::iterator<MapOfEntities>()) + .def("__contains__", +[](const MapOfEntities& m, const std::string& n) -> bool { return m.count(n); }); + bp::to_python_converter<MapOfEntities::value_type, MapOfEntitiesPairToPythonConverter>(); +} diff --git a/src/dynamic_graph/entity-py.cc b/src/dynamic_graph/entity-py.cc index b8f2c2441d91929ae2a27dbc904f72622045efd5..f5993fd06f6a85e06c911de7c6827c1a17e692df 100644 --- a/src/dynamic_graph/entity-py.cc +++ b/src/dynamic_graph/entity-py.cc @@ -6,13 +6,12 @@ #include <dynamic-graph/factory.h> #include <dynamic-graph/command.h> -#include <dynamic-graph/value.h> -#include <dynamic-graph/pool.h> #include <dynamic-graph/linear-algebra.h> +#include <dynamic-graph/pool.h> +#include <dynamic-graph/value.h> -#include "dynamic-graph/python/dynamic-graph-py.hh" #include "dynamic-graph/python/convert-dg-to-py.hh" -#include "dynamic-graph/python/exception.hh" +#include "dynamic-graph/python/dynamic-graph-py.hh" // Ignore "dereferencing type-punned pointer will break strict-aliasing rules" // warnings on gcc caused by Py_RETURN_TRUE and Py_RETURN_FALSE. @@ -20,6 +19,8 @@ #pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif +namespace bp = boost::python; + using dynamicgraph::Entity; using dynamicgraph::Matrix; using dynamicgraph::SignalBase; @@ -32,637 +33,55 @@ namespace python { using namespace convert; -#if PY_MAJOR_VERSION == 2 -extern PyObject* dgpyError; -#endif - namespace entity { +/// \param obj an Entity object +void addCommands(bp::object obj) { + Entity& entity = bp::extract<Entity&>(obj); + for (const auto& el : entity.getNewStyleCommandMap()) obj.attr(el.first.c_str()) = bp::object(bp::ptr(el.second)); +} + +/// \param obj an Entity object +void addSignals(bp::object obj) { + Entity& entity = bp::extract<Entity&>(obj); + for (const auto& el : entity.getSignalMap()) + // obj.attr(el.first.c_str()) = bp::make_function( + //+[&entity,el]() { return &entity.getSignal(el.first); }, + // bp::return_value_policy<bp::reference_existing_object>()); + obj.attr(el.first.c_str()) = bp::object(bp::ptr(el.second)); +} + /** \brief Create an instance of Entity */ -PyObject* create( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - char* className = NULL; - char* instanceName = NULL; - - if (!PyArg_ParseTuple(args, "ss", &className, &instanceName)) return NULL; - +Entity* create(const char* className, const char* instanceName) { Entity* obj = NULL; /* Try to find if the corresponding object already exists. */ if (dynamicgraph::PoolStorage::getInstance()->existEntity(instanceName, obj)) { if (obj->getClassName() != className) { - std::string msg("Found an object named " + std::string(instanceName) + - ",\n" - "but this object is of type " + - std::string(obj->getClassName()) + " and not " + std::string(className)); - PyErr_SetString(DGPYERROR(m), msg.c_str()); - return NULL; + throw std::invalid_argument("Found an object named " + std::string(instanceName) + + ",\n" + "but this object is of type " + + std::string(obj->getClassName()) + " and not " + std::string(className)); } } else /* If not, create a new object. */ { - try { - obj = dynamicgraph::FactoryStorage::getInstance()->newEntity(std::string(className), std::string(instanceName)); - } - CATCH_ALL_EXCEPTIONS(m); - } - - // Return the pointer as a PyCapsule - return PyCapsule_New((void*)obj, "dynamic_graph.Entity", NULL); -} - -/** - \brief Get name of entity -*/ -PyObject* getName( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - void* pointer = NULL; - std::string name; - - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "function takes a PyCapsule as argument"); - return NULL; - } - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - try { - name = entity->getName(); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue("s", name.c_str()); -} - -/** - \brief Get class name of entity -*/ -PyObject* getClassName( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - void* pointer = NULL; - std::string name; - - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "function takes a PyCapsule as argument"); - return NULL; - } - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - try { - name = entity->getClassName(); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue("s", name.c_str()); -} - -/** - \brief Check if the entity has a signal with the given name -*/ -PyObject* hasSignal( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - char* name = NULL; - PyObject* object = NULL; - void* pointer = NULL; - - if (!PyArg_ParseTuple(args, "Os", &object, &name)) Py_RETURN_FALSE; - - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "function takes a PyCapsule as argument"); - Py_RETURN_FALSE; - } - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - bool hasSignal = false; - try { - hasSignal = entity->hasSignal(std::string(name)); - } - CATCH_ALL_EXCEPTIONS(m); - - if (hasSignal) - Py_RETURN_TRUE; - else - Py_RETURN_FALSE; -} - -/** - \brief Get a signal by name -*/ -PyObject* getSignal( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - char* name = NULL; - PyObject* object = NULL; - void* pointer = NULL; - - if (!PyArg_ParseTuple(args, "Os", &object, &name)) return NULL; - - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "function takes a PyCapsule as argument"); - return NULL; - } - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - SignalBase<int>* signal = NULL; - try { - signal = &(entity->getSignal(std::string(name))); - } - CATCH_ALL_EXCEPTIONS(m); - - // Return the pointer to the signal without destructor since the signal - // is not owned by the calling object but by the Entity. - return PyCapsule_New((void*)signal, "dynamic_graph.Signal", NULL); -} - -PyObject* listSignals( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - try { - Entity::SignalMap signalMap = entity->getSignalMap(); - // Create a tuple of same size as the command map - PyObject* result = PyTuple_New(signalMap.size()); - unsigned int count = 0; - - for (Entity::SignalMap::iterator it = signalMap.begin(); it != signalMap.end(); it++) { - SignalBase<int>* signal = it->second; - PyObject* pySignal = PyCapsule_New((void*)signal, "dynamic_graph.Signal", NULL); - PyTuple_SET_ITEM(result, count, pySignal); - count++; - } - return result; - } - CATCH_ALL_EXCEPTIONS(m); - return NULL; -} - -PyObject* executeCommand( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - PyObject* argTuple = NULL; - char* commandName = NULL; - void* pointer = NULL; - if (!PyArg_ParseTuple(args, "OsO", &object, &commandName, &argTuple)) { - return NULL; - } - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "first argument is not an object"); - return NULL; - } - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - // Retrieve the argument tuple - if (!PyTuple_Check(argTuple)) { - PyErr_SetString(PyExc_TypeError, "third argument is not a tuple"); - return NULL; - } - Py_ssize_t size = PyTuple_Size(argTuple); - std::map<const std::string, Command*> commandMap = entity->getNewStyleCommandMap(); - if (commandMap.count(std::string(commandName)) != 1) { - std::ostringstream oss; - oss << "'" << entity->getName() << "' entity has no command '" << commandName << "'."; - PyErr_SetString(PyExc_AttributeError, oss.str().c_str()); - return NULL; - } - Command* command = commandMap[std::string(commandName)]; - // Check that tuple size is equal to command number of arguments - const std::vector<Value::Type> typeVector = command->valueTypes(); - if ((unsigned)size != typeVector.size()) { - std::stringstream ss; - ss << "command takes " << typeVector.size() << " parameters, " << size << " given."; - PyErr_SetString(DGPYERROR(m), ss.str().c_str()); - return NULL; - } - std::vector<Value> valueVector; - for (Py_ssize_t iParam = 0; iParam < size; iParam++) { - PyObject* PyValue = PyTuple_GetItem(argTuple, iParam); - Value::Type valueType = typeVector[iParam]; - try { - Value value = pythonToValue(PyValue, valueType); - valueVector.push_back(value); - } catch (const std::exception& exc) { - std::stringstream ss; - ss << "while parsing argument " << iParam + 1 << ": expecting " << exc.what() << "."; - PyErr_SetString(DGPYERROR(m), ss.str().c_str()); - return NULL; - } catch (...) { - PyErr_SetString(DGPYERROR(m), "Unknown exception"); - return NULL; - } - } - command->setParameterValues(valueVector); - try { - Value result = command->execute(); - return valueToPython(result); - } - CATCH_ALL_EXCEPTIONS(m); - return NULL; -} - -PyObject* listCommands(PyObject* /*self*/, PyObject* args) { - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) { - return NULL; + obj = dynamicgraph::FactoryStorage::getInstance()->newEntity(std::string(className), std::string(instanceName)); } - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "function takes a PyCapsule as argument"); - return NULL; - } - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - typedef std::map<const std::string, command::Command*> CommandMap; - CommandMap map = entity->getNewStyleCommandMap(); - Py_ssize_t nbCommands = (Py_ssize_t)map.size(); - // Create a tuple of same size as the command map - PyObject* result = PyTuple_New(nbCommands); - unsigned int count = 0; - for (CommandMap::iterator it = map.begin(); it != map.end(); it++) { - std::string commandName = it->first; - PyObject* pyName = Py_BuildValue("s", commandName.c_str()); - PyTuple_SET_ITEM(result, count, pyName); - count++; - } - return result; + return obj; } -PyObject* getCommandDocstring( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - char* commandName; - if (!PyArg_ParseTuple(args, "Os", &object, &commandName)) { - return NULL; - } - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(DGPYERROR(m), "first argument is not an object"); - return NULL; - } - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - typedef std::map<const std::string, command::Command*> commandMap_t; - typedef std::map<const std::string, command::Command*>::iterator iterator_t; - commandMap_t map = entity->getNewStyleCommandMap(); - command::Command* command = NULL; - iterator_t it = map.find(commandName); - if (it == map.end()) { - std::ostringstream oss; - oss << "'" << entity->getName() << "' entity has no command '" << commandName << "'."; - PyErr_SetString(PyExc_AttributeError, oss.str().c_str()); - return NULL; - } - command = it->second; - std::string docstring = command->getDocstring(); - return Py_BuildValue("s", docstring.c_str()); -} - -PyObject* getDocString( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) { - return NULL; - } - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(DGPYERROR(m), "first argument is not an object"); - return NULL; - } - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - try { - return Py_BuildValue("s", entity->getDocString().c_str()); - } catch (const std::exception& exc) { - PyErr_SetString(DGPYERROR(m), exc.what()); - return NULL; - } catch (...) { - PyErr_SetString(DGPYERROR(m), "Unknown exception"); - return NULL; - } - return NULL; -} - -PyObject* display( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - /* Retrieve the entity instance. */ - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object) || (!PyCapsule_CheckExact(object))) { - PyErr_SetString(DGPYERROR(m), "first argument is not an object"); - return NULL; - } - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - /* Run the display function. */ - std::ostringstream oss; - entity->display(oss); - - /* Return the resulting string. */ - return Py_BuildValue("s", oss.str().c_str()); -} - -/** - \brief Set verbosity Level -*/ -PyObject* setLoggerVerbosityLevel( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - PyObject* objectVerbosityLevel = NULL; - if (!PyArg_ParseTuple(args, "OO", &object, &objectVerbosityLevel)) return NULL; - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "First argument should be an object"); - return NULL; - } - - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - // Retrieve object verbosity level - PyObject* valueOfVerbosityLevel = PyObject_GetAttrString(objectVerbosityLevel, "value"); - long verbosityLevel = PyLong_AsLong(valueOfVerbosityLevel); - - try { - switch (verbosityLevel) { - case 8: - entity->setLoggerVerbosityLevel(VERBOSITY_ALL); - break; - case 4: - entity->setLoggerVerbosityLevel(VERBOSITY_INFO_WARNING_ERROR); - break; - case 2: - entity->setLoggerVerbosityLevel(VERBOSITY_WARNING_ERROR); - break; - case 1: - entity->setLoggerVerbosityLevel(VERBOSITY_ERROR); - break; - case 0: - entity->setLoggerVerbosityLevel(VERBOSITY_NONE); - break; - default: - entity->setLoggerVerbosityLevel(VERBOSITY_NONE); - break; - } - } catch (const std::exception& exc) { - PyErr_SetString(DGPYERROR(m), exc.what()); - return NULL; - } catch (const char* s) { - PyErr_SetString(DGPYERROR(m), s); - return NULL; - } catch (...) { - PyErr_SetString(DGPYERROR(m), "Unknown exception"); - return NULL; - } - - return Py_BuildValue(""); -} - -/** - \brief Get verbosity Level -*/ -PyObject* getLoggerVerbosityLevel( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "first argument is not an object"); - return NULL; - } - - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - LoggerVerbosity alv; - try { - alv = entity->getLoggerVerbosityLevel(); - } - CATCH_ALL_EXCEPTIONS(m); - - int ares = (int)alv; - return Py_BuildValue("i", ares); -} - -/** - \brief Get stream print period -*/ -PyObject* getStreamPrintPeriod( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "first argument is not an object"); - return NULL; - } - - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - double r; - try { - r = entity->getStreamPrintPeriod(); - } - CATCH_ALL_EXCEPTIONS(m); - - return Py_BuildValue("d", r); -} - -/** - \brief Set print period -*/ -PyObject* setStreamPrintPeriod( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - double streamPrintPeriod = 0; - if (!PyArg_ParseTuple(args, "Od", &object, &streamPrintPeriod)) return NULL; - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "First argument should be an object"); - return NULL; - } - - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - try { - entity->setStreamPrintPeriod(streamPrintPeriod); - - } catch (const std::exception& exc) { - PyErr_SetString(DGPYERROR(m), exc.what()); - return NULL; - } catch (const char* s) { - PyErr_SetString(DGPYERROR(m), s); - return NULL; - } catch (...) { - PyErr_SetString(DGPYERROR(m), "Unknown exception"); - return NULL; - } - - return Py_BuildValue(""); -} - -/** - \brief Get stream print period -*/ -PyObject* getTimeSample( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "first argument is not an object"); - return NULL; - } - - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - double r; - try { - r = entity->getTimeSample(); - } - CATCH_ALL_EXCEPTIONS(m); - - return Py_BuildValue("d", r); -} - -/** - \brief Set time sample -*/ -PyObject* setTimeSample( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - PyObject* object = NULL; - double timeSample; - if (!PyArg_ParseTuple(args, "Od", &object, &timeSample)) return NULL; - - // Retrieve the entity instance - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(PyExc_TypeError, "First argument should be an object"); - return NULL; - } - - void* pointer = PyCapsule_GetPointer(object, "dynamic_graph.Entity"); - Entity* entity = (Entity*)pointer; - - try { - entity->setTimeSample(timeSample); - - } catch (const std::exception& exc) { - PyErr_SetString(DGPYERROR(m), exc.what()); - return NULL; - } catch (const char* s) { - PyErr_SetString(DGPYERROR(m), s); - return NULL; - } catch (...) { - PyErr_SetString(DGPYERROR(m), "Unknown exception"); - return NULL; - } - return Py_BuildValue(""); +bp::object executeCmd(bp::tuple args, bp::dict) { + Command& command = bp::template extract<Command&>(args[0]); + if (bp::len(args) != int(command.valueTypes().size() + 1)) + // TODO Put expected and given number of args + throw std::out_of_range("Wrong number of arguments"); + std::vector<Value> values; + values.reserve(command.valueTypes().size()); + for (int i = 1; i < bp::len(args); ++i) values.push_back(convert::toValue(args[i], command.valueTypes()[i - 1])); + command.setParameterValues(values); + return convert::fromValue(command.execute()); } } // namespace entity diff --git a/src/dynamic_graph/entity.py b/src/dynamic_graph/entity.py index c59cfc168ca0b50ba230bafe9fc5f7c758576560..6374dc4e2be1221b51233c5cf2c8e63b4a32d718 100644 --- a/src/dynamic_graph/entity.py +++ b/src/dynamic_graph/entity.py @@ -1,306 +1,9 @@ -""" - Copyright (C) 2010 CNRS +# Copyright (C) 2020 CNRS +# +# Author: Florent Lamiraux, Nicolas Mansard - Author: Florent Lamiraux, Nicolas Mansard -""" from __future__ import print_function -import types -from enum import Enum - -from . import signal_base, wrap -from .attrpath import setattrpath - -if 'display' not in globals().keys(): - - def display(s): - print(s) - - -# --- FACTORY ------------------------------------------------------------------ - - -class PyEntityFactoryClass(type): - """ - The class build dynamically a new class type, and return the reference - on the class-type object. The class type is not added to any context. - """ - def __new__(factory, className, bases=(), dict={}): - if len(bases) == 0: - # Initialize a basic Entity class - EntityClass = type.__new__(factory, className, (Entity, ), dict) - EntityClass.className = className - EntityClass.__init__ = Entity.initEntity - else: - # Initialize a heritated class - EntityClass = type.__new__(factory, className, bases, dict) - for c in bases: - if issubclass(c, Entity): - EntityClass.className = c.className - break - EntityClass.commandCreated = False - return EntityClass - - -def PyEntityFactory(className, context): - """ - Build a new class type by calling the factory, and add it - to the given context. - """ - EntityClass = PyEntityFactoryClass(className) - context[className] = EntityClass - return EntityClass - - -def updateEntityClasses(dictionary): - """ - For all c++entity types that are not in the pyentity class list - (entityClassNameList) run the factory and store the new type in the given - context (dictionary). - """ - cxx_entityList = wrap.factory_get_entity_class_list() - for e in filter(lambda x: x not in Entity.entityClassNameList, cxx_entityList): - # Store new class in dictionary with class name - PyEntityFactory(e, dictionary) - # Store class name in local list - Entity.entityClassNameList.append(e) - - -# --- ENTITY ------------------------------------------------------------------- - - -class VerbosityLevel(Enum): - """ - Enum class for setVerbosityLevel - """ - VERBOSITY_ALL = 8 - VERBOSITY_INFO_WARNING_ERROR = 4 - VERBOSITY_WARNING_ERROR = 2 - VERBOSITY_ERROR = 1 - VERBOSITY_NONE = 0 - - -class Entity(object): - """ - This class binds dynamicgraph::Entity C++ class - """ - - obj = None - """ - Store list of entities created via python - """ - entities = dict() - - def __init__(self, className, instanceName): - """ - Constructor: if not called by a child class, create and store a pointer - to a C++ Entity object. - """ - object.__setattr__(self, 'obj', wrap.create_entity(className, instanceName)) - Entity.entities[instanceName] = self - - @staticmethod - def initEntity(self, name): - """ - Common constructor of specialized Entity classes. This function is bound - by the factory to each new class derivated from the Entity class as the - constructor of the new class. - """ - Entity.__init__(self, self.className, name) - if not self.__class__.commandCreated: - self.boundClassCommands() - self.__class__.__doc__ = wrap.entity_get_docstring(self.obj) - self.__class__.commandCreated = True - - @property - def name(self): - return wrap.entity_get_name(self.obj) - - @property - def className(self): - return wrap.entity_get_class_name(self.obj) - - def __str__(self): - return wrap.display_entity(self.obj) - - def signal(self, name): - """ - Get a signal of the entity from signal name - """ - signalPt = wrap.entity_get_signal(self.obj, name) - return signal_base.SignalBase(name="", obj=signalPt) - - def hasSignal(self, name): - """ - Indicates if a signal with the given name exists in the entity - """ - return wrap.entity_has_signal(self.obj, name) - - def displaySignals(self): - """ - Print the list of signals into standard output: temporary. - """ - signals = list(self.signals()) - if len(signals) == 0: - display("--- <" + self.name + "> has no signal") - else: - display("--- <" + self.name + "> signal list: ") - for s in signals[:-1]: - display(" |-- <" + str(s)) - display(" `-- <" + str(signals[-1])) - - def signals(self): - """ - Return the list of signals - """ - sl = wrap.entity_list_signals(self.obj) - return map(lambda pyObj: signal_base.SignalBase(obj=pyObj), sl) - - def commands(self): - """ - Return the list of commands. - """ - return wrap.entity_list_commands(self.obj) - - def globalHelp(self): - """ - Print a short description of each command. - """ - if self.__doc__: - print(self.__doc__) - print("List of commands:") - print("-----------------") - for cstr in self.commands(): - ctitle = cstr + ':' - for i in range(len(cstr), 15): - ctitle += ' ' - for docstr in wrap.entity_get_command_docstring(self.obj, cstr).split('\n'): - if (len(docstr) > 0) and (not docstr.isspace()): - display(ctitle + "\t" + docstr) - break - - def help(self, comm=None): - """ - With no arg, print the global help. With arg the name of - a specific command, print the help associated to the command. - """ - if comm is None: - self.globalHelp() - else: - display(comm + ":\n" + wrap.entity_get_command_docstring(self.obj, comm)) - - def __getattr__(self, name): - try: - return self.signal(name) - except Exception: - try: - object.__getattr__(self, name) - except AttributeError: - raise AttributeError("'%s' entity has no attribute %s\n" % (self.name, name) + - ' entity attributes are usually either\n' + ' - commands,\n' + - ' - signals or,\n' + ' - user defined attributes') - - def __setattr__(self, name, value): - if name in map(lambda s: s.getName().split(':')[-1], self.signals()): - raise NameError(name + " already designates a signal. " - "It is not advised to set a new attribute of the same name.") - object.__setattr__(self, name, value) - - # --- COMMANDS BINDER ----------------------------------------------------- - # List of all the entity classes from the c++ factory, that have been bound - # bind the py factory. - entityClassNameList = [] - - # This function dynamically create the function object that runs the command. - @staticmethod - def createCommandBind(name, docstring): - def commandBind(self, *arg): - return wrap.entity_execute_command(self.obj, name, arg) - - commandBind.__doc__ = docstring - return commandBind - - def boundClassCommands(self): - """ - This static function has to be called from a class heritating from Entity. - It should be called only once. It parses the list of commands obtained from - c++, and bind each of them to a python class method. - """ - # Get list of commands of the Entity object - commands = wrap.entity_list_commands(self.obj) - # for each command, add a method with the name of the command - for cmdstr in commands: - docstr = wrap.entity_get_command_docstring(self.obj, cmdstr) - cmdpy = Entity.createCommandBind(cmdstr, docstr) - setattrpath(self.__class__, cmdstr, cmdpy) - - def boundNewCommand(self, cmdName): - """ - At construction, all existing commands are bound directly in the class. - This method enables to bound new commands dynamically. These new bounds - are not made with the class, but directly with the object instance. - """ - if (cmdName in self.__dict__) | (cmdName in self.__class__.__dict__): - print("Warning: command ", cmdName, " will overwrite an object attribute.") - docstring = wrap.entity_get_command_docstring(self.obj, cmdName) - cmd = Entity.createCommandBind(cmdName, docstring) - # Limitation (todo): does not handle for path attribute name (see setattrpath). - setattr(self, cmdName, types.MethodType(cmd, self)) - - def boundAllNewCommands(self): - """ - For all commands that are not attribute of the object instance nor of the - class, a new attribute of the instance is created to bound the command. - """ - cmdList = wrap.entity_list_commands(self.obj) - cmdList = filter(lambda x: x not in self.__dict__, cmdList) - cmdList = filter(lambda x: x not in self.__class__.__dict__, cmdList) - for cmd in cmdList: - self.boundNewCommand(cmd) - - def setLoggerVerbosityLevel(self, verbosity): - """ - Specify for the entity the verbosity level. - - param verbosity should be one of the attribute of the enum - dynamic_graph.entity.VerbosityLevel - """ - return wrap.entity_set_logger_verbosity(self.obj, verbosity) - - def getLoggerVerbosityLevel(self): - """ - Returns the entity's verbosity level (as a dynamic_graph.entity.VerbosityLevel) - """ - r = wrap.entity_get_logger_verbosity(self.obj) - if r == 8: - return VerbosityLevel.VERBOSITY_ALL - elif r == 4: - return VerbosityLevel.VERBOSITY_INFO_WARNING_ERROR - elif r == 2: - return VerbosityLevel.VERBOSITY_WARNING_ERROR - elif r == 1: - return VerbosityLevel.VERBOSITY_ERROR - return VerbosityLevel.VERBOSITY_NONE - - def setTimeSample(self, timeSample): - """ - Specify for the entity the time at which call is counted. - """ - return wrap.entity_set_time_sample(self.obj, timeSample) - - def getTimeSample(self): - """ - Returns for the entity the time at which call is counted. - """ - return wrap.entity_get_time_sample(self.obj) - - def setStreamPrintPeriod(self, streamPrintPeriod): - """ - Specify for the entity the period at which debugging information is printed - """ - return wrap.entity_set_stream_print_period(self.obj, streamPrintPeriod) - - def getStreamPrintPeriod(self): - """ - Returns for the entity the period at which debugging information is printed - """ - return wrap.entity_get_stream_print_period(self.obj) +# for backward compat +from .wrap import LoggerVerbosity as VerbosityLevel +from .wrap import Entity diff --git a/src/dynamic_graph/exception-python.cc b/src/dynamic_graph/exception-python.cc deleted file mode 100644 index 490cda381f47d4c1b16bb98f4674eb7bbfc170a6..0000000000000000000000000000000000000000 --- a/src/dynamic_graph/exception-python.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2010, - * François Bleibel, - * Olivier Stasse, - * - * CNRS/AIST - * - */ - -#include "dynamic-graph/python/exception-python.hh" -#include <dynamic-graph/debug.h> -#include <stdarg.h> -#include <cstdio> - -namespace dynamicgraph { -namespace python { - -/* --------------------------------------------------------------------- */ -/* --- CLASS ----------------------------------------------------------- */ -/* --------------------------------------------------------------------- */ - -const std::string ExceptionPython::EXCEPTION_NAME = "Python"; - -ExceptionPython::ExceptionPython(const ExceptionPython::ErrorCodeEnum& errcode, const std::string& msg) - : ExceptionAbstract(errcode, msg) { - dgDEBUGF(15, "Created with message <%s>.", msg.c_str()); - dgDEBUG(1) << "Created with message <%s>." << msg << std::endl; -} - -ExceptionPython::ExceptionPython(const ExceptionPython::ErrorCodeEnum& errcode, const std::string& msg, - const char* format, ...) - : ExceptionAbstract(errcode, msg) { - va_list args; - va_start(args, format); - - const unsigned int SIZE = 256; - char buffer[SIZE]; - vsnprintf(buffer, SIZE, format, args); - - dgDEBUG(15) << "Created " - << " with message <" << msg << "> and buffer <" << buffer << ">. " << std::endl; - - message += buffer; - - va_end(args); - - dgDEBUG(1) << "Throw exception " << EXCEPTION_NAME << "[#" << errcode << "]: " - << "<" << message << ">." << std::endl; -} - -} // namespace python -} // namespace dynamicgraph - -/* - * Local variables: - * c-basic-offset: 2 - * End: - */ diff --git a/src/dynamic_graph/factory-py.cc b/src/dynamic_graph/factory-py.cc index d6ce919b148fdc8527a702134604a8303bba793d..8fbdbcafb71110df48d4b8439bec2cd65484bac8 100644 --- a/src/dynamic_graph/factory-py.cc +++ b/src/dynamic_graph/factory-py.cc @@ -16,22 +16,10 @@ namespace factory { /** \brief Get name of entity */ -PyObject* getEntityClassList(PyObject* /*self*/, PyObject* args) { - if (!PyArg_ParseTuple(args, "")) return NULL; - +bp::tuple getEntityClassList() { std::vector<std::string> classNames; dynamicgraph::FactoryStorage::getInstance()->listEntities(classNames); - - Py_ssize_t classNumber = classNames.size(); - // Build a tuple object - PyObject* classTuple = PyTuple_New(classNumber); - - for (Py_ssize_t iEntity = 0; iEntity < (Py_ssize_t)classNames.size(); ++iEntity) { - PyObject* className = Py_BuildValue("s", classNames[iEntity].c_str()); - PyTuple_SetItem(classTuple, iEntity, className); - } - - return Py_BuildValue("O", classTuple); + return to_py_tuple(classNames.begin(), classNames.end()); } } // namespace factory diff --git a/src/dynamic_graph/pool-py.cc b/src/dynamic_graph/pool-py.cc index 68f0ce698f3f3c9811d5b40b4642a4dcea913e8b..2c1659deed9e31aa6f706f7c90c372ab13ac20b2 100644 --- a/src/dynamic_graph/pool-py.cc +++ b/src/dynamic_graph/pool-py.cc @@ -4,67 +4,27 @@ #include <dynamic-graph/entity.h> #include <vector> -#include "dynamic-graph/python/exception.hh" #include "dynamic-graph/python/dynamic-graph-py.hh" namespace dynamicgraph { namespace python { -#if PY_MAJOR_VERSION == 2 -extern PyObject* dgpyError; -#endif - namespace pool { -PyObject* writeGraph( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - char* filename; - if (!PyArg_ParseTuple(args, "s", &filename)) return NULL; - try { - PoolStorage::getInstance()->writeGraph(filename); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} +void writeGraph(const char* filename) { PoolStorage::getInstance()->writeGraph(filename); } + +const std::map<std::string, Entity*>* getEntityMap() { return &PoolStorage::getInstance()->getEntityMap(); } /** \brief Get list of entities */ -PyObject* getEntityList( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - if (!PyArg_ParseTuple(args, "")) return NULL; - +bp::list getEntityList() { std::vector<std::string> entityNames; - try { - const PoolStorage::Entities& listOfEntities = dynamicgraph::PoolStorage::getInstance()->getEntityMap(); - - Py_ssize_t classNumber = listOfEntities.size(); - // Build a tuple object - PyObject* classTuple = PyTuple_New(classNumber); - - Py_ssize_t iEntity = 0; - for (PoolStorage::Entities::const_iterator entity_it = listOfEntities.begin(); entity_it != listOfEntities.end(); - ++entity_it) { - const std::string& aname = entity_it->second->getName(); + bp::list res; + const PoolStorage::Entities& listOfEntities = PoolStorage::getInstance()->getEntityMap(); - PyObject* className = Py_BuildValue("s", aname.c_str()); - PyTuple_SetItem(classTuple, iEntity, className); - iEntity++; - } - return Py_BuildValue("O", classTuple); - } - CATCH_ALL_EXCEPTIONS(m); - return NULL; + for (const auto& el : listOfEntities) res.append(el.second->getName()); + return res; } } // namespace pool diff --git a/src/dynamic_graph/signal-base-py.cc b/src/dynamic_graph/signal-base-py.cc index 743cb1f7aba7274142510503df6b4ccdc2049fc3..c839742df2b7f7c0c4159766c5415dbd5eca0b21 100644 --- a/src/dynamic_graph/signal-base-py.cc +++ b/src/dynamic_graph/signal-base-py.cc @@ -3,51 +3,119 @@ #include <iostream> #include <sstream> +#include <boost/python.hpp> + +#include "dynamic-graph/python/signal.hh" + #include <dynamic-graph/signal-base.h> #include <dynamic-graph/signal.h> #include <dynamic-graph/signal-ptr.h> -#include <dynamic-graph/signal-caster.h> +#include <dynamic-graph/signal-time-dependent.h> #include <dynamic-graph/linear-algebra.h> -#include <dynamic-graph/pool.h> -#include <dynamic-graph/factory.h> +#include <dynamic-graph/value.h> #include "dynamic-graph/python/dynamic-graph-py.hh" -#include "dynamic-graph/python/convert-dg-to-py.hh" -#include "dynamic-graph/python/exception.hh" #include "dynamic-graph/python/signal-wrapper.hh" using dynamicgraph::SignalBase; +namespace bp = boost::python; + namespace dynamicgraph { namespace python { -#if PY_MAJOR_VERSION == 2 -extern PyObject* dgpyError; -#endif - -using namespace convert; - -namespace signalBase { +typedef int time_type; + +typedef Eigen::AngleAxis<double> VectorUTheta; +typedef Eigen::Quaternion<double> Quaternion; + +typedef Eigen::VectorXd Vector; +typedef Eigen::Vector3d Vector3; +typedef Eigen::Matrix<double, 7, 1> Vector7; + +typedef Eigen::MatrixXd Matrix; +typedef Eigen::Matrix<double, 3, 3> MatrixRotation; +typedef Eigen::Matrix<double, 4, 4> Matrix4; +typedef Eigen::Transform<double, 3, Eigen::Affine> MatrixHomogeneous; +typedef Eigen::Matrix<double, 6, 6> MatrixTwist; + +template <typename Time> +void exposeSignalBase(const char* name) { + typedef SignalBase<Time> S_t; + bp::class_<S_t, boost::noncopyable>(name, bp::no_init) + .add_property("time", bp::make_function(&S_t::getTime, bp::return_value_policy<bp::copy_const_reference>()), + &S_t::setTime) + .add_property("name", bp::make_function(&S_t::getName, bp::return_value_policy<bp::copy_const_reference>())) + + .def("getName", &S_t::getName, bp::return_value_policy<bp::copy_const_reference>()) + .def("getClassName", + +[](const S_t& s) -> std::string { + std::string ret; + s.getClassName(ret); + return ret; + }) + + .def("plug", &S_t::plug, "Plug the signal to another signal") + .def("unplug", &S_t::unplug, "Unplug the signal") + .def("isPlugged", &S_t::isPlugged, "Whether the signal is plugged") + .def("getPlugged", &S_t::getPluged, bp::return_value_policy<bp::reference_existing_object>(), + "To which signal the signal is plugged") + + .def("recompute", &S_t::recompute, "Recompute the signal at given time") + + .def("__str__", + +[](const S_t& s) -> std::string { + std::ostringstream oss; + s.display(oss); + return oss.str(); + }) + .def("displayDependencies", + +[](const S_t& s, int time) -> std::string { + std::ostringstream oss; + s.displayDependencies(oss, time); + return oss.str(); + }, + "Print the signal dependencies in a string"); +} + +template <> +auto exposeSignal<MatrixHomogeneous, time_type>(const std::string& name) { + typedef Signal<MatrixHomogeneous, time_type> S_t; + bp::class_<S_t, bp::bases<SignalBase<time_type> >, boost::noncopyable> obj(name.c_str(), bp::init<std::string>()); + obj.add_property("value", +[](const S_t& signal) -> Matrix4 { return signal.accessCopy().matrix(); }, + +[](S_t& signal, const Matrix4& v) { + // TODO it isn't hard to support pinocchio::SE3 type here. + // However, this adds a dependency to pinocchio. + signal.setConstant(MatrixHomogeneous(v)); + }, + "the signal value."); + return obj; +} -static void destroy(PyObject* self); +void exposeSignals() { + exposeSignalBase<time_type>("SignalBase"); -/** - \brief Create an instance of SignalBase -*/ -PyObject* create(PyObject* /*self*/, PyObject* args) { - char* name = NULL; + exposeSignalsOfType<bool, time_type>("Bool"); + exposeSignalsOfType<int, time_type>("Int"); + exposeSignalsOfType<double, time_type>("Double"); - if (!PyArg_ParseTuple(args, "s", &name)) return NULL; + exposeSignalsOfType<Vector, time_type>("Vector"); + exposeSignalsOfType<Vector3, time_type>("Vector3"); + exposeSignalsOfType<Vector7, time_type>("Vector7"); - SignalBase<int>* obj = NULL; - obj = new SignalBase<int>(std::string(name)); + exposeSignalsOfType<Matrix, time_type>("Matrix"); + exposeSignalsOfType<MatrixRotation, time_type>("MatrixRotation"); + exposeSignalsOfType<MatrixHomogeneous, time_type>("MatrixHomogeneous"); + exposeSignalsOfType<MatrixTwist, time_type>("MatrixTwist"); - // Return the pointer - return PyCapsule_New((void*)obj, "dynamic_graph.Signal", destroy); + exposeSignalsOfType<Quaternion, time_type>("Quaternion"); + exposeSignalsOfType<VectorUTheta, time_type>("VectorUTheta"); } +namespace signalBase { + template <class T> -SignalWrapper<T, int>* createSignalWrapperTpl(const char* name, PyObject* o, std::string& error) { +SignalWrapper<T, int>* createSignalWrapperTpl(const char* name, bp::object o, std::string& error) { typedef SignalWrapper<T, int> SignalWrapper_t; if (!SignalWrapper_t::checkCallable(o, error)) { return NULL; @@ -58,27 +126,7 @@ SignalWrapper<T, int>* createSignalWrapperTpl(const char* name, PyObject* o, std } PythonSignalContainer* getPythonSignalContainer() { - const std::string instanceName = "python_signals"; - const std::string className = "PythonSignalContainer"; - Entity* obj; -#if PY_MAJOR_VERSION >= 3 - PyObject* m = PyState_FindModule(&dynamicgraph::python::dynamicGraphModuleDef); -#endif - if (PoolStorage::getInstance()->existEntity(instanceName, obj)) { - if (obj->getClassName() != className) { - std::string msg("Found an object named " + std::string(instanceName) + - ",\n" - "but this object is of type " + - std::string(obj->getClassName()) + " and not " + std::string(className)); - PyErr_SetString(DGPYERROR(m), msg.c_str()); - return NULL; - } - } else { - try { - obj = FactoryStorage::getInstance()->newEntity(std::string(className), std::string(instanceName)); - } - CATCH_ALL_EXCEPTIONS(m); - } + Entity* obj = entity::create("PythonSignalContainer", "python_signals"); return dynamic_cast<PythonSignalContainer*>(obj); } @@ -90,22 +138,10 @@ PythonSignalContainer* getPythonSignalContainer() { /** \brief Create an instance of SignalWrapper */ -PyObject* createSignalWrapper( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { +SignalBase<int>* createSignalWrapper(const char* name, const char* type, bp::object object) { PythonSignalContainer* psc = getPythonSignalContainer(); if (psc == NULL) return NULL; - char* name = NULL; - char* type = NULL; - PyObject* object = NULL; - - if (!PyArg_ParseTuple(args, "ssO", &name, &type, &object)) return NULL; - SignalBase<int>* obj = NULL; std::string error; SIGNAL_WRAPPER_TYPE(if, BOOL, bool) @@ -121,380 +157,14 @@ PyObject* createSignalWrapper( error = "Type not understood"; } - if (obj == NULL) { - PyErr_SetString(DGPYERROR(m), error.c_str()); - return NULL; - } + if (obj == NULL) throw std::runtime_error(error); // Register signal into the python signal container psc->signalRegistration(*obj); // Return the pointer - return PyCapsule_New((void*)obj, "dynamic_graph.SignalWrapper", destroy); -} - -/** - \brief Destroy an instance of InvertedPendulum -*/ -static void destroy(PyObject* self) { - SignalBase<int>* obj = (SignalBase<int>*)self; - delete obj; -} - -PyObject* getTime(PyObject* /*self*/, PyObject* args) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* obj = (SignalBase<int>*)pointer; - - int time = obj->getTime(); - return Py_BuildValue("i", time); -} - -PyObject* setTime( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - int time; - if (!PyArg_ParseTuple(args, "Oi", &object, &time)) return NULL; - if (!PyCapsule_CheckExact(object)) { - PyErr_SetString(DGPYERROR(m), "object should be a C object"); - return NULL; - } - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* obj = (SignalBase<int>*)pointer; - - obj->setTime(time); - return Py_BuildValue(""); -} - -PyObject* display( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* obj = (SignalBase<int>*)pointer; - - std::ostringstream oss; - try { - obj->display(oss); - } - CATCH_ALL_EXCEPTIONS(m); - - return Py_BuildValue("s", oss.str().c_str()); -} - -PyObject* displayDependencies( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - int time; - if (!PyArg_ParseTuple(args, "OI", &object, &time)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* obj = (SignalBase<int>*)pointer; - - std::ostringstream oss; - try { - obj->displayDependencies(oss, time); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue("s", oss.str().c_str()); -} - -PyObject* getValue( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - - try { - { // --- VECTOR SIGNALS ----------------- - // Two cases: the signal embeds directly a vector, or embeds - // an object deriving from vector.In the first case, - // the signal is directly cast into sig<vector>. - // In the second case, the derived object can be access as a vector - // using the signal-ptr<vector> type. - Signal<dynamicgraph::Vector, int>* sigvec = dynamic_cast<Signal<dynamicgraph::Vector, int>*>(signal); - if (NULL != sigvec) { - return vectorToPython(sigvec->accessCopy()); - } - - // Extraction of object derinving from vector: plug signal into - // a vector signal and get the value from the signal-ptr instead - // of the original vector. - SignalPtr<dynamicgraph::Vector, int> sigptr(NULL, "vector-caster"); - try { - sigptr.plug(signal); - return vectorToPython(sigptr.accessCopy()); - } catch (dynamicgraph::ExceptionSignal& ex) { - if (ex.getCode() != dynamicgraph::ExceptionSignal::PLUG_IMPOSSIBLE) throw; - } - } - - { // --- MATRIX SIGNALS -------------------- - // Two cases: the signal embeds directly a matrix, or embeds - // an object deriving from matrix.In the first case, - // the signal is directly cast into sig<matrix>. - // In the second case, the derived object can be access as a matrix - // using the signal-ptr<matrix> type. - Signal<dynamicgraph::Matrix, int>* sigmat = dynamic_cast<Signal<dynamicgraph::Matrix, int>*>(signal); - if (NULL != sigmat) { - return matrixToPython(sigmat->accessCopy()); - } - - SignalPtr<dynamicgraph::Matrix, int> sigptr(NULL, "matrix-caster"); - try { - sigptr.plug(signal); - return matrixToPython(sigptr.accessCopy()); - } catch (dynamicgraph::ExceptionSignal& ex) { - if (ex.getCode() != dynamicgraph::ExceptionSignal::PLUG_IMPOSSIBLE) throw; - } - } - - { // --- HOMOGENEOUS MATRIX SIGNALS -------------------- - // Two cases: the signal embeds directly a matrix, or embeds - // an object deriving from matrix.In the first case, - // the signal is directly cast into sig<matrix>. - // In the second case, the derived object can be access as a matrix - // using the signal-ptr<matrix> type. - - // TODO: See if matrix homogeneous can be properly put in linear-algebra.h - typedef Eigen::Transform<double, 3, Eigen::Affine> MatrixHomogeneous; - Signal<MatrixHomogeneous, int>* sigmat = dynamic_cast<Signal<MatrixHomogeneous, int>*>(signal); - if (NULL != sigmat) { - return matrixToPython(sigmat->accessCopy().matrix()); - } - - SignalPtr<Eigen::Transform<double, 3, Eigen::Affine>, int> sigptr(NULL, "matrix-caster"); - try { - sigptr.plug(signal); - return matrixToPython(sigptr.accessCopy().matrix()); - } catch (dynamicgraph::ExceptionSignal& ex) { - if (ex.getCode() != dynamicgraph::ExceptionSignal::PLUG_IMPOSSIBLE) throw; - } - } - - Signal<double, int>* sigdouble = dynamic_cast<Signal<double, int>*>(signal); - if (NULL != sigdouble) { - return Py_BuildValue("d", sigdouble->accessCopy()); - } - } - CATCH_ALL_EXCEPTIONS(m); - - /* Non specific signal: use a generic way. */ - std::ostringstream value; - try { - signal->get(value); - } - CATCH_ALL_EXCEPTIONS(m); - - std::string valueString = value.str(); - return Py_BuildValue("s", valueString.c_str()); -} - -PyObject* getName( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - - std::string name; - try { - name = signal->getName(); - } - CATCH_ALL_EXCEPTIONS(m); - - return Py_BuildValue("s", name.c_str()); -} - -PyObject* getClassName( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - - std::string name; - try { - signal->getClassName(name); - } - CATCH_ALL_EXCEPTIONS(m); - - return Py_BuildValue("s", name.c_str()); -} - -PyObject* setValue( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - char* valueString = NULL; - - if (!PyArg_ParseTuple(args, "Os", &object, &valueString)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - std::ostringstream os; - os << valueString; - std::istringstream value(os.str()); - - try { - signal->set(value); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} - -PyObject* recompute( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - unsigned int time; - if (!PyArg_ParseTuple(args, "OI", &object, &time)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - try { - signal->recompute(time); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} - -PyObject* unplug( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - try { - signal->unplug(); - } - CATCH_ALL_EXCEPTIONS(m); - return Py_BuildValue(""); -} - -PyObject* isPlugged( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - bool plugged = false; - try { - plugged = signal->isPlugged(); - } - CATCH_ALL_EXCEPTIONS(m); - if (plugged) - return PyBool_FromLong(1); - else - return PyBool_FromLong(0); + return obj; } -PyObject* getPlugged( -#if PY_MAJOR_VERSION >= 3 - PyObject* m, PyObject* args -#else - PyObject*, PyObject* args -#endif -) { - void* pointer = NULL; - PyObject* object = NULL; - if (!PyArg_ParseTuple(args, "O", &object)) return NULL; - if (!PyCapsule_CheckExact(object)) return NULL; - - pointer = PyCapsule_GetPointer(object, "dynamic_graph.Signal"); - SignalBase<int>* signal = (SignalBase<int>*)pointer; - SignalBase<int>* otherSignal = 0; - try { - bool plugged = signal->isPlugged(); - otherSignal = signal->getPluged(); - if (!plugged || otherSignal == 0) { - std::string msg = std::string("Signal ") + signal->getName() + std::string(" is not plugged."); - throw std::runtime_error(msg); - } - } - CATCH_ALL_EXCEPTIONS(m); - // Return the pointer to the signal without destructor since the signal - // is not owned by the calling object. - return PyCapsule_New((void*)otherSignal, "dynamic_graph.Signal", NULL); -} } // namespace signalBase } // namespace python } // namespace dynamicgraph diff --git a/src/dynamic_graph/signal-caster-py.cc b/src/dynamic_graph/signal-caster-py.cc deleted file mode 100644 index 73b854c0b68adbabdc13009b3ec8f0aa66e6b36b..0000000000000000000000000000000000000000 --- a/src/dynamic_graph/signal-caster-py.cc +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2010, Florent Lamiraux, Thomas Moulard, LAAS-CNRS. - -#include <iostream> -#include <sstream> - -#include <dynamic-graph/signal-caster.h> - -#include "dynamic-graph/python/dynamic-graph-py.hh" - -namespace dynamicgraph { -namespace python { - -namespace signalCaster { -PyObject* getSignalTypeList(PyObject* /*self*/, PyObject* args) { - if (!PyArg_ParseTuple(args, "")) return NULL; - std::vector<std::string> typeList = dynamicgraph::SignalCaster::getInstance()->listTypenames(); - Py_ssize_t typeNumber = typeList.size(); - // Build a tuple object - PyObject* typeTuple = PyTuple_New(typeNumber); - - for (Py_ssize_t iType = 0; iType < typeNumber; ++iType) { - PyObject* className = Py_BuildValue("s", typeList[iType].c_str()); - PyTuple_SetItem(typeTuple, iType, className); - } - - return Py_BuildValue("O", typeTuple); -} -} // namespace signalCaster -} // namespace python -} // namespace dynamicgraph diff --git a/src/dynamic_graph/signal-wrapper.cc b/src/dynamic_graph/signal-wrapper.cc index a6c7df28f88698820824feef68c92cc33208ae1b..731e898e74d2b43e7e2f95b6c78bddedad04b689 100644 --- a/src/dynamic_graph/signal-wrapper.cc +++ b/src/dynamic_graph/signal-wrapper.cc @@ -8,30 +8,6 @@ namespace dynamicgraph { namespace python { -namespace signalWrapper { -void convert(PyObject* o, bool& v) { v = (o == Py_True); } -void convert(PyObject* o, int& v) { v = (int)PyLong_AsLong(o); } -void convert(PyObject* o, float& v) { v = (float)PyFloat_AS_DOUBLE(o); } -void convert(PyObject* o, double& v) { v = PyFloat_AS_DOUBLE(o); } -void convert(PyObject* o, Vector& v) { - v.resize(PyTuple_Size(o)); - for (int i = 0; i < v.size(); ++i) convert(PyTuple_GetItem(o, i), v[i]); -} -} // namespace signalWrapper - -PythonSignalContainer::PythonSignalContainer(const std::string& name) : Entity(name) { - std::string docstring; - - docstring = - " \n" - " Remove a signal\n" - " \n" - " Input:\n" - " - name of the signal\n" - " \n"; - addCommand("rmSignal", command::makeCommandVoid1(*this, &PythonSignalContainer::rmSignal, docstring)); -} - void PythonSignalContainer::signalRegistration(const SignalArray<int>& signals) { Entity::signalRegistration(signals); } @@ -41,12 +17,10 @@ void PythonSignalContainer::rmSignal(const std::string& name) { Entity::signalDe DYNAMICGRAPH_FACTORY_ENTITY_PLUGIN(PythonSignalContainer, "PythonSignalContainer"); template <class T, class Time> -bool SignalWrapper<T, Time>::checkCallable(PyObject* c, std::string& error) { - if (PyCallable_Check(c) == 0) { - PyObject* str = PyObject_Str(c); - error = obj_to_str(str); +bool SignalWrapper<T, Time>::checkCallable(pyobject c, std::string& error) { + if (PyCallable_Check(c.ptr()) == 0) { + error = boost::python::extract<std::string>(c.attr("__str__")()); error += " is not callable"; - Py_DECREF(str); return false; } return true; diff --git a/src/dynamic_graph/signal_base.py b/src/dynamic_graph/signal_base.py index e48838c1bb5a15ecb408d490e3c462c4c8e54061..ba40575e0fac4631d57c7d668fb6b060d20d0bc8 100644 --- a/src/dynamic_graph/signal_base.py +++ b/src/dynamic_graph/signal_base.py @@ -1,18 +1,14 @@ -""" - Copyright (C) 2010 CNRS - - Author: Florent Lamiraux -""" +# Copyright (C) 2020 CNRS +# +# Author: Joseph Mirabel from __future__ import print_function -import re - -from .wrap import (create_signal_wrapper, signal_base_display, signal_base_display_dependencies, - signal_base_get_class_name, signal_base_get_name, signal_base_get_time, signal_base_get_value, - signal_base_getPlugged, signal_base_isPlugged, signal_base_recompute, signal_base_set_time, - signal_base_set_value, signal_base_unplug) +from .wrap import SignalBase, create_signal_wrapper as SignalWrapper +# I kept what follows for backward compatibility but I think it should be +# removed +import re def stringToTuple(vector): """ @@ -153,136 +149,3 @@ def stringToObject(string): return float(string) except Exception: return string - - -class SignalBase(object): - """ - This class binds dynamicgraph::SignalBase<int> C++ class - """ - - obj = None - - def __init__(self, name="", obj=None): - """ - Constructor: if not called by a child class, create and store a pointer - to a C++ SignalBase<int> object. - """ - if obj: - self.obj = obj - else: - raise RuntimeError("A pointer is required to create SignalBase object.") - - if obj is None: - self.className = self.getClassName() - self.name = self.getName() - - @property - def time(self): - """ - Get time of signal - """ - return signal_base_get_time(self.obj) - - @time.setter - def time(self, val): - """ - Set Time of signal - - Input: - - an integer - """ - return signal_base_set_time(self.obj, val) - - @property - def value(self): - """ - Setter and getter for the value of a signal - - Binds C++ SignalBase<int>::get() and set() methods. Values are passed - through string streams. - A string is interpreted as respectively: - * a matrix (tuple of tuple) if string fits '[n,m]((x_11,x_12,...,x_1m),...,(x_n1,x_n2,...,x_nm))' format where - n and m are integers, x_ij are floating point numbers, - * a tuple if string fits '[n](x_1, x_2, ..., x_n)' format, - * an integer, - * a floating point number. - - If string fits none of the above formats, no conversion is performed. - - For instance, is s binds a signal of type vector, - >>> s.value = (2.5, .1, 1e2) - will call SignalBase<int>::set("[3](2.5,0.1,100.0)") and - >>> s.value - (2.5, 0.1, 100.0) - """ - string = signal_base_get_value(self.obj) - return stringToObject(string) - - @value.setter - def value(self, val): - """ - Set the signal as a constant signal with given value. - If the signal is plugged, it will be unplugged - """ - string = objectToString(val) - return signal_base_set_value(self.obj, string) - - def getName(self): - """ - Get name of signal - """ - return signal_base_get_name(self.obj) - - @property - def name(self): - """ - Get name of signal - """ - return signal_base_get_name(self.obj) - - def getClassName(self): - """ - Get class name of signal - """ - return signal_base_get_class_name(self.obj) - - def recompute(self, time): - """ - Force signal to recompute the value at given time. - """ - return signal_base_recompute(self.obj, time) - - def unplug(self): - """ - Unplug a PTR signal. - """ - return signal_base_unplug(self.obj) - - def isPlugged(self): - """ - Return whether a signal is plugged. - """ - return signal_base_isPlugged(self.obj) - - def getPlugged(self): - """ - Return the plugged signal. - """ - return SignalBase(obj=signal_base_getPlugged(self.obj)) - - def __str__(self): - """ - Print signal in a string - """ - return signal_base_display(self.obj) - - def displayDependencies(self, iter): - """ - Print signal dependencies in a string - """ - return (signal_base_display_dependencies(self.obj, iter)) - - -class SignalWrapper(SignalBase): - def __init__(self, name, type, func): - super(SignalWrapper, self).__init__(name, create_signal_wrapper(name, type, func)) diff --git a/src/dynamic_graph/tracer/wrap.cc b/src/dynamic_graph/tracer/wrap.cc new file mode 100644 index 0000000000000000000000000000000000000000..a790c98bfd7e5845e973983c0349531865eb6604 --- /dev/null +++ b/src/dynamic_graph/tracer/wrap.cc @@ -0,0 +1,10 @@ +#include "dynamic-graph/python/module.hh" + +#include <dynamic-graph/tracer.h> + +BOOST_PYTHON_MODULE(wrap) { + using dynamicgraph::Tracer; + + bp::import("dynamic_graph"); + dynamicgraph::python::exposeEntity<Tracer>().def("addSignal", &Tracer::addSignalToTrace); +} diff --git a/src/dynamic_graph/tracer_real_time/wrap.cc b/src/dynamic_graph/tracer_real_time/wrap.cc new file mode 100644 index 0000000000000000000000000000000000000000..9fb24ec86b802d1c163fba74ed36d0954e9ddab1 --- /dev/null +++ b/src/dynamic_graph/tracer_real_time/wrap.cc @@ -0,0 +1,11 @@ +#include "dynamic-graph/python/module.hh" + +#include <dynamic-graph/tracer-real-time.h> + +BOOST_PYTHON_MODULE(wrap) { + using dynamicgraph::Tracer; + using dynamicgraph::TracerRealTime; + + bp::import("dynamic_graph.tracer"); + dynamicgraph::python::exposeEntity<TracerRealTime, bp::bases<Tracer> >(); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7f74b238181809148d5264abee72eb59901fba2a..fe5754567262d996af870ff60d5cd6a282b061d3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,11 +2,11 @@ # Test the interpreter ADD_UNIT_TEST(interpreter-test interpreter-test.cc) -TARGET_LINK_LIBRARIES(interpreter-test ${PROJECT_NAME}) +TARGET_LINK_LIBRARIES(interpreter-test PRIVATE ${PROJECT_NAME}) # Test runfile ADD_UNIT_TEST(interpreter-test-runfile interpreter-test-runfile.cc) -TARGET_LINK_LIBRARIES(interpreter-test-runfile ${PYTHON_LIBRARY} ${Boost_LIBRARIES} ${PROJECT_NAME}) +TARGET_LINK_LIBRARIES(interpreter-test-runfile PRIVATE ${PROJECT_NAME} Boost::unit_test_framework) TARGET_COMPILE_DEFINITIONS(interpreter-test-runfile PRIVATE PATH="${CMAKE_CURRENT_LIST_DIR}/") # Test the module generation @@ -19,7 +19,7 @@ IF(SUFFIX_SO_VERSION) PROPERTIES SOVERSION ${PROJECT_VERSION}) ENDIF(SUFFIX_SO_VERSION) -target_link_libraries(${LIBRARY_NAME} dynamic-graph::dynamic-graph) +target_link_libraries(${LIBRARY_NAME} PRIVATE dynamic-graph::dynamic-graph) ## Create its bindings ## This mimics DYNAMIC_GRAPH_PYTHON_MODULE(${LIBRARY_NAME} ${LIBRARY_NAME} "${LIBRARY_NAME}-wrap") @@ -29,18 +29,26 @@ CONFIGURE_FILE( ${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}/__init__.py ) SET(PYTHON_MODULE "${LIBRARY_NAME}-wrap") -SET(SOURCE_PYTHON_MODULE "cmake/dynamic_graph/python-module-py.cc") -ADD_LIBRARY(${PYTHON_MODULE} MODULE ${PROJECT_SOURCE_DIR}/${SOURCE_PYTHON_MODULE}) +set(DYNAMICGRAPH_MODULE_HEADER "${CMAKE_SOURCE_DIR}/tests/custom_entity_module.h") +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/dynamic_graph/python-module-py.cc.in + ${CMAKE_CURRENT_BINARY_DIR}/python-module-py.cc + @ONLY + ) +ADD_LIBRARY(${PYTHON_MODULE} MODULE ${CMAKE_CURRENT_BINARY_DIR}/python-module-py.cc) SET_TARGET_PROPERTIES(${PYTHON_MODULE} PROPERTIES PREFIX "" OUTPUT_NAME ${LIBRARY_NAME}/wrap) IF(UNIX AND NOT APPLE) - TARGET_LINK_LIBRARIES(${PYTHON_MODULE} "-Wl,--no-as-needed") + TARGET_LINK_LIBRARIES(${PYTHON_MODULE} PRIVATE "-Wl,--no-as-needed") ENDIF(UNIX AND NOT APPLE) -TARGET_LINK_LIBRARIES(${PYTHON_MODULE} ${LIBRARY_NAME} ${PYTHON_LIBRARY}) -TARGET_INCLUDE_DIRECTORIES(${PYTHON_MODULE} SYSTEM PUBLIC ${PYTHON_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(${PYTHON_MODULE} PRIVATE + ${LIBRARY_NAME} dynamic-graph-python + ${PYTHON_LIBRARY}) +TARGET_LINK_BOOST_PYTHON(${PYTHON_MODULE} PRIVATE) +TARGET_INCLUDE_DIRECTORIES(${PYTHON_MODULE} SYSTEM PRIVATE ${PYTHON_INCLUDE_DIRS}) ## Test it ADD_PYTHON_UNIT_TEST("test-custom-entity" "tests/test_custom_entity.py" src tests) diff --git a/tests/custom_entity.cpp b/tests/custom_entity.cpp index 90632d7ada1ad224e7466faf335c87fd39f5bdf6..1d4bf52b71ee84cec945b96150525b169cfe8978 100644 --- a/tests/custom_entity.cpp +++ b/tests/custom_entity.cpp @@ -5,7 +5,9 @@ #define ENABLE_RT_LOG -#include <sstream> +#include "custom_entity.h" + +#include <dynamic-graph/command-bind.h> #include <dynamic-graph/entity.h> #include <dynamic-graph/exception-factory.h> #include <dynamic-graph/factory.h> @@ -13,54 +15,47 @@ #include <dynamic-graph/real-time-logger.h> #include <dynamic-graph/signal-ptr.h> #include <dynamic-graph/signal-time-dependent.h> -#include <dynamic-graph/command-bind.h> namespace dynamicgraph { -class CustomEntity : public Entity { - public: - dynamicgraph::SignalPtr<double, int> m_sigdSIN; - dynamicgraph::SignalTimeDependent<double, int> m_sigdTimeDepSOUT; - - static const std::string CLASS_NAME; - virtual const std::string &getClassName() const { return CLASS_NAME; } - CustomEntity(const std::string n) - : Entity(n), - m_sigdSIN(NULL, "CustomEntity(" + name + ")::input(double)::in_double"), - m_sigdTimeDepSOUT(boost::bind(&CustomEntity::update, this, _1, _2), m_sigdSIN, - "CustomEntity(" + name + ")::input(double)::out_double") - - { - addSignal(); - - using namespace dynamicgraph::command; - - this->addCommand("act", makeCommandVoid0(*this, &CustomEntity::act, docCommandVoid0("act on input signal"))); - } - - void addSignal() { signalRegistration(m_sigdSIN << m_sigdTimeDepSOUT); } - - void rmValidSignal() { - signalDeregistration("in_double"); - signalDeregistration("out_double"); - } - double &update(double &res, const int &inTime) { - const double &aDouble = m_sigdSIN(inTime); - res = aDouble; - logger().stream(MSG_TYPE_ERROR) << "start update " << res << '\n'; - DYNAMIC_GRAPH_ENTITY_DEBUG(*this) << "This is a message of level MSG_TYPE_DEBUG\n"; - DYNAMIC_GRAPH_ENTITY_INFO(*this) << "This is a message of level MSG_TYPE_INFO\n"; - DYNAMIC_GRAPH_ENTITY_WARNING(*this) << "This is a message of level MSG_TYPE_WARNING\n"; - DYNAMIC_GRAPH_ENTITY_ERROR(*this) << "This is a message of level MSG_TYPE_ERROR\n"; - DYNAMIC_GRAPH_ENTITY_DEBUG_STREAM(*this) << "This is a message of level MSG_TYPE_DEBUG_STREAM\n"; - DYNAMIC_GRAPH_ENTITY_INFO_STREAM(*this) << "This is a message of level MSG_TYPE_INFO_STREAM\n"; - DYNAMIC_GRAPH_ENTITY_WARNING_STREAM(*this) << "This is a message of level MSG_TYPE_WARNING_STREAM\n"; - DYNAMIC_GRAPH_ENTITY_ERROR_STREAM(*this) << "This is a message of level MSG_TYPE_ERROR_STREAM\n"; - logger().stream(MSG_TYPE_ERROR) << "end update\n"; - return res; - } +CustomEntity::CustomEntity(const std::string n) + : Entity(n), + m_sigdSIN(NULL, "CustomEntity(" + name + ")::input(double)::in_double"), + m_sigdTimeDepSOUT(boost::bind(&CustomEntity::update, this, _1, _2), m_sigdSIN, + "CustomEntity(" + name + ")::input(double)::out_double") + +{ + addSignal(); + + using namespace dynamicgraph::command; + + this->addCommand("act", makeCommandVoid0(*this, &CustomEntity::act, docCommandVoid0("act on input signal"))); +} + +void CustomEntity::addSignal() { signalRegistration(m_sigdSIN << m_sigdTimeDepSOUT); } + +void CustomEntity::rmValidSignal() { + signalDeregistration("in_double"); + signalDeregistration("out_double"); +} + +double &CustomEntity::update(double &res, const int &inTime) { + const double &aDouble = m_sigdSIN(inTime); + res = aDouble; + logger().stream(MSG_TYPE_ERROR) << "start update " << res << '\n'; + DYNAMIC_GRAPH_ENTITY_DEBUG(*this) << "This is a message of level MSG_TYPE_DEBUG\n"; + DYNAMIC_GRAPH_ENTITY_INFO(*this) << "This is a message of level MSG_TYPE_INFO\n"; + DYNAMIC_GRAPH_ENTITY_WARNING(*this) << "This is a message of level MSG_TYPE_WARNING\n"; + DYNAMIC_GRAPH_ENTITY_ERROR(*this) << "This is a message of level MSG_TYPE_ERROR\n"; + DYNAMIC_GRAPH_ENTITY_DEBUG_STREAM(*this) << "This is a message of level MSG_TYPE_DEBUG_STREAM\n"; + DYNAMIC_GRAPH_ENTITY_INFO_STREAM(*this) << "This is a message of level MSG_TYPE_INFO_STREAM\n"; + DYNAMIC_GRAPH_ENTITY_WARNING_STREAM(*this) << "This is a message of level MSG_TYPE_WARNING_STREAM\n"; + DYNAMIC_GRAPH_ENTITY_ERROR_STREAM(*this) << "This is a message of level MSG_TYPE_ERROR_STREAM\n"; + logger().stream(MSG_TYPE_ERROR) << "end update\n"; + return res; +} + +void CustomEntity::act() { m_sigdSIN.accessCopy(); } - void act() { m_sigdSIN.accessCopy(); } -}; DYNAMICGRAPH_FACTORY_ENTITY_PLUGIN(CustomEntity, "CustomEntity"); } // namespace dynamicgraph diff --git a/tests/custom_entity.h b/tests/custom_entity.h new file mode 100644 index 0000000000000000000000000000000000000000..b382fe49827582826e4d82c469fd12e8deb20e8c --- /dev/null +++ b/tests/custom_entity.h @@ -0,0 +1,30 @@ +/* Copyright 2020 LAAS, CNRS + * Joseph Mirabel + * + */ + +#define ENABLE_RT_LOG + +#include <sstream> +#include <dynamic-graph/entity.h> +#include <dynamic-graph/signal-ptr.h> +#include <dynamic-graph/signal-time-dependent.h> + +namespace dynamicgraph { +class CustomEntity : public Entity { + public: + dynamicgraph::SignalPtr<double, int> m_sigdSIN; + dynamicgraph::SignalTimeDependent<double, int> m_sigdTimeDepSOUT; + + DYNAMIC_GRAPH_ENTITY_DECL(); + CustomEntity(const std::string n); + + void addSignal(); + + void rmValidSignal(); + + double &update(double &res, const int &inTime); + + void act(); +}; +} // namespace dynamicgraph diff --git a/tests/custom_entity_module.h b/tests/custom_entity_module.h new file mode 100644 index 0000000000000000000000000000000000000000..b4f2169f0ff628f9c3f16c528ff9624470aeb4ea --- /dev/null +++ b/tests/custom_entity_module.h @@ -0,0 +1,3 @@ +#include "custom_entity.h" + +typedef boost::mpl::vector<dynamicgraph::CustomEntity> entities_t; diff --git a/tests/test_bindings.py b/tests/test_bindings.py index 8b022ce88deb4d6ea9e4c41a3777f3af157a170a..92ea4a5ad415ea8b3f05edcc2ea5f09ee5fa89bb 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -1,18 +1,15 @@ import unittest import dynamic_graph as dg - from custom_entity import CustomEntity -ERR = "dynamic_graph.plug(a, b): Argument '%s' must be of type 'dynamic_graph.Signal', but got dynamic_graph.Entity" +ERR = """Python argument types in + dynamic_graph.wrap.plug(%s, %s) +did not match C++ signature: + plug(dynamicgraph::SignalBase<int>* signalOut, dynamicgraph::SignalBase<int>* signalIn)""" class BindingsTests(unittest.TestCase): - def test_bindings(self): - with self.assertRaises(dg.dgpyError) as cm: - dg.error_out() - self.assertEqual(str(cm.exception), "something bad happened") - def test_type_check(self): """ test the type checking in signal plugs @@ -25,12 +22,12 @@ class BindingsTests(unittest.TestCase): # Check that we can't connect first.out to second with self.assertRaises(TypeError) as cm_in: dg.plug(first.signal('out_double'), second) - self.assertEqual(str(cm_in.exception), ERR % 'b') + self.assertEqual(str(cm_in.exception), ERR % ("SignalTimeDependentDouble", "CustomEntity")) # Check that we can't connect first to second.in with self.assertRaises(TypeError) as cm_out: dg.plug(first, second.signal('in_double')) - self.assertEqual(str(cm_out.exception), ERR % 'a') + self.assertEqual(str(cm_out.exception), ERR % ("CustomEntity", "SignalPtrDouble")) def test_dg_exc(self): """ @@ -38,7 +35,7 @@ class BindingsTests(unittest.TestCase): """ ent = CustomEntity('test_dg_exc') # check that accessing a non initialized signal raises - with self.assertRaises(dg.dgpyError) as cm: + with self.assertRaises(RuntimeError) as cm: ent.act() self.assertEqual( str(cm.exception),