1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
/*************************************************************************
* 2020 Janek Kozicki *
* *
* This program is free software; it is licensed under the terms of the *
* GNU General Public License v2 or later. See file LICENSE for details. *
*************************************************************************/
/*
* This file provides dropdown menu support in QT GUI for file gui/qt5/SerializableEditor.py
* File lib/serialization/Serializable.hpp is already huge, so better to put this in separate file.
* And use it only when someone wants drop down menus.
*
* For basic usage see documentation: https://yade-dem.org/doc/prog.html#enums
*
*/
#include <lib/base/Logging.hpp>
#include <boost/preprocessor.hpp>
#include <boost/python.hpp>
#ifndef YADE_ENUM_SUPPORT_HPP
#define YADE_ENUM_SUPPORT_HPP
namespace yade {
/*
* NOTE: this one is not necessary:
template <typename ArbitraryEnum> struct ArbitraryEnum_to_python { … };
* because we use to_python conversions provided by boost::python::enum_<…>
*
* But the template below ArbitraryEnum_from_python { … }; is provided to make sure that the enum can be converted from python arguments such as:
*
* 1. enum class ArbitraryEnum, which is represented by the boost::python::enum_<ArbitraryEnum> on python side: 'Boost.Python.enum'
* 2. int
* 3. string
*
* If we decided that only 'Boost.Python.enum' should be accepted in yade, and assignment from string and int should not work,
* then this template can be completely removed. And we could use only what is provided by boost::python::enum_<…>.
*
* However I don't think that's the case. There are plenty of enums in yade codebase. And currently they all work by assigning int values to them.
* So that code below is the one of possible ways to ensure backward compatibility, while at the same time we get the fancy dropdown menus.
*
*/
template <typename ArbitraryEnum> struct ArbitraryEnum_from_python {
static bool setArbitraryEnum(const ::boost::python::object& arg, ArbitraryEnum& col)
{
// All of them are validated if the value is possible within the possible enum values.
std::string arbName = boost::core::demangle(typeid(ArbitraryEnum).name());
::boost::python::object meCol(col);
if (::boost::python::extract<long>(arg).check()) {
long n = ::boost::python::extract<long>(arg)();
if (::boost::python::extract<::boost::python::dict>(meCol.attr("values"))().has_key(n)) {
col = ::boost::python::extract<ArbitraryEnum>(meCol.attr("values")[n])();
} else {
LOG_ERROR("enum class " + arbName + " does not have key number: " + boost::lexical_cast<std::string>(n));
return false;
}
} else if (::boost::python::extract<std::string>(arg).check()) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
std::string n = ::boost::python::extract<std::string>(arg)();
#pragma GCC diagnostic pop
if (::boost::python::extract<::boost::python::dict>(meCol.attr("names"))().has_key(n)) {
col = ::boost::python::extract<ArbitraryEnum>(meCol.attr("names")[n])();
} else {
LOG_ERROR("enum class " + arbName + " does not have key called: " + n);
return false;
}
} else if (::boost::python::extract<ArbitraryEnum>(arg).check()) {
col = ::boost::python::extract<ArbitraryEnum>(arg)();
} else {
LOG_ERROR("enum class " + arbName + " cannot read input argument.");
return false;
};
LOG_DEBUG("enum class " + arbName + " set successfully.");
return true;
};
ArbitraryEnum_from_python() { boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id<ArbitraryEnum>()); }
static void* convertible(PyObject* obj_ptr)
{
LOG_DEBUG("Ref count = " << obj_ptr->ob_refcnt);
// The invocations in setArbitraryEnum(…) which return false in line if( ::boost::python::extract<…> … ) will call this function again. We better prevent that.
// Make sure it's thread safe, even though assigning enums simultaneously from different threads is not gonna happen.
thread_local static int preventRecurrence = 0;
if (preventRecurrence != 0) return nullptr;
++preventRecurrence;
ArbitraryEnum test;
::boost::python::handle<> handle(::boost::python::borrowed(obj_ptr));
::boost::python::object arg(handle);
bool success = setArbitraryEnum(arg, test);
--preventRecurrence;
return success ? obj_ptr : nullptr;
}
static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
{
::boost::python::handle<> handle(::boost::python::borrowed(obj_ptr));
::boost::python::object arg(handle);
void* storage = ((boost::python::converter::rvalue_from_python_storage<ArbitraryEnum>*)(data))->storage.bytes;
new (storage) ArbitraryEnum;
ArbitraryEnum* val = (ArbitraryEnum*)storage;
setArbitraryEnum(arg, *val);
data->convertible = storage;
}
DECLARE_LOGGER;
};
#define YADE_ENUM_PARSE_ONE(r, FULL_SCOPE__ENUM_TYPE, enumNAME) .value(BOOST_PP_STRINGIZE(enumNAME), FULL_SCOPE__ENUM_TYPE::enumNAME)
#define YADE_ENUM(FULL_SCOPE, ENUM_TYPE, ENUM_SEQUENCE) \
TEMPLATE_CREATE_LOGGER(ArbitraryEnum_from_python<FULL_SCOPE::ENUM_TYPE>); \
} /* end namespace yade */ \
namespace { /* start anonymous namespace */ \
__attribute__((constructor)) void registerEnum_##ENUM_TYPE(void) /* we cannot have enums with same name in multiple scopes */ \
{ \
try { \
/* register in yade scope */ \
boost::python::object main = boost::python::import("yade"); \
boost::python::scope setScope(main); \
boost::python::type_info info = boost::python::type_id<FULL_SCOPE::ENUM_TYPE>(); \
const boost::python::converter::registration* reg = boost::python::converter::registry::query(info); \
if ((reg == NULL) or (reg->m_to_python == NULL)) { \
yade::ArbitraryEnum_from_python<FULL_SCOPE::ENUM_TYPE>(); \
boost::python::enum_<FULL_SCOPE::ENUM_TYPE>( \
"EnumClass_" #ENUM_TYPE) /* the loop over ENUM_SEQUENCE in next line registers all enum name strings */ \
BOOST_PP_SEQ_FOR_EACH(YADE_ENUM_PARSE_ONE, FULL_SCOPE::ENUM_TYPE, ENUM_SEQUENCE); \
} \
} catch (...) { \
std::cerr << "\nException: " __FILE__ ":" << __PRETTY_FUNCTION__ << ", enum (class or type) " #ENUM_TYPE ".\n\n"; \
PyErr_Print(); \
PyErr_SetString(PyExc_SystemError, __FILE__); /* raising here anything other than SystemError is not possible */ \
boost::python::handle_exception(); \
throw; \
} \
} \
} \
namespace yade {
}; // namespace yade
#endif
|