22 #ifndef PYTHON_FUNCTIONAL_NODE_H
23 #define PYTHON_FUNCTIONAL_NODE_H
25 #include <boost/python.hpp>
26 #include <boost/python/stl_iterator.hpp>
35 namespace bpy = boost::python;
38 template <
typename T,
size_t N>
45 typedef decltype(std::tuple<>())
type;
60 const static size_t input_s = 10;
61 const static size_t output_s = 10;
71 bpy::stl_input_iterator<std::string> begin(o_ports), end;
72 _oPortIds.insert(_oPortIds.begin(), begin, end);
84 boost::python::object
pySetup(boost::python::object obj)
87 std::shared_ptr<PythonFunctionalNode>
self = this->moveToSharedPtr();
89 self->_function = std::bind(&PythonFunctionalNode::pythonCallback,
self, std::placeholders::_1 );
90 self->setInputPtrs(std::make_index_sequence<input_s>());
91 self->setOutputPtrs(std::make_index_sequence<output_s>());
94 auto inspect = bpy::import(
"inspect");
95 bpy::stl_input_iterator<std::string> begin(inspect.attr(
"getfullargspec")(obj).attr(
"args")), end;
96 self->_iPortIds.insert(self->_iPortIds.begin(), begin, end);
99 if(self->_oPortIds.size() > output_s || self->_iPortIds.size() > output_s)
101 "requested python object");
103 for(
const auto& p : self->_oPortIds)
104 self->registerOutput(p);
107 std::shared_ptr<ComputationalNode> self_base = std::dynamic_pointer_cast<ComputationalNode>(
self);
110 return boost::python::object(
self);
116 template<
typename T_IN,
size_t N = 0>
120 for(
size_t n = 0; n < _iPortIds.size(); ++n)
121 if(_iPortIds[n] ==
id) {
127 throw NRPException::logCreate(
"\"" +
id +
"\" does not match any of the declared input arguments in node \"" + this->
id() +
"\"");
129 if constexpr (N < input_s) {
132 if(
port->id() ==
id) {
136 throw NRPException::logCreate(
"Input \"" +
id +
"\" has been registered with a different type in node \"" + this->
id() +
"\"");
139 return getOrRegisterInput<T_IN, N+1>(
id);
142 _iPortIdsMap.emplace(
id, N);
143 return registerInput<N, T_IN, bpy::object>(
id);
147 throw NRPException::logCreate(
"There is no input port with name" +
id +
"registered to this node and no additional ports can be registered");
153 template<
size_t N = 0>
156 if constexpr (N < output_s) {
157 if(!getOutputByIndex<N>())
158 return FunctionalNode::registerOutput<N, bpy::object>(
id);
160 return registerOutput<N+1>(
id);
169 template<
size_t N = 0>
183 std::stringstream error_msg;
184 error_msg <<
"In Functional node '" << this->id() <<
"'. Error While creating edge to port '" << port_id <<
"'. ";
185 error_msg <<
"Attempt to connect to port '" << out_port->
id() <<
"', but they are of different types.";
204 for(
size_t i=0; i < _iPortIds.size(); ++i)
205 if(!getInputByIndex(i)) {
207 s <<
"In python functional node \"" << this->id() <<
"\". Input argument \"" << _iPortIds[i] <<
208 "\" is not connected" << std::endl;
212 for(
const auto& pId : _oPortIds) {
213 if(!getOutput(pId)->subscriptionsSize()) {
215 s <<
"In python functional node \"" << this->id() <<
"\". Output \"" << pId <<
216 "\" is not connected" << std::endl;
222 friend class ComputationalGraphPythonNodes_PYTHON_FUNCTIONAL_NODE_Test;
229 bool pythonCallback(params_t&)
231 boost::python::tuple
args;
232 boost::python::dict kwargs;
233 boost::python::object o_output;
235 for(
size_t i=0; i < _iPortIds.size(); ++i) {
236 const bpy::object* in =
nullptr;
237 if(_iPortIdsMap.find(_iPortIds[i]) != _iPortIdsMap.end())
238 in = *_inputs[_iPortIdsMap.at(_iPortIds[i])];
239 kwargs[_iPortIds[i]] = in !=
nullptr ? *in : bpy::object();
243 o_output = _pythonF(*
args, **kwargs);
246 if (o_output.is_none())
249 catch (
const boost::python::error_already_set&) {
250 std::string error_msg =
"An error occurred while executing Functional Node with id \"" + this->id() +
"\"";
258 boost::python::list l_output = boost::python::extract<boost::python::list>(o_output);
259 if(_oPortIds.size() != (
size_t)boost::python::len(l_output)) {
260 std::stringstream error_msg;
261 error_msg <<
"Functional Node with id \"" << this->id() <<
"\" has " << _oPortIds.size() <<
262 " declared outputs, but returns " << boost::python::len(l_output) <<
" elements.";
267 for(
int i=0; i < boost::python::len(l_output); ++i)
268 *_outputs[i] = l_output[i];
270 catch (
const boost::python::error_already_set&) {
271 std::string error_msg =
"An error occurred while executing Functional Node with id \"" + this->id() +
"\". It is expected to return an object of type list or None";
282 template<std::size_t... Ints>
283 void setInputPtrs(std::index_sequence<Ints...>)
284 { ((_inputs[Ints] = &std::get<Ints>(_params)),...); }
289 template<std::size_t... Ints>
290 void setOutputPtrs(std::index_sequence<Ints...>)
291 { ((_outputs[Ints] = &std::get<input_s+Ints>(_params)),...); }
294 std::shared_ptr<PythonFunctionalNode> moveToSharedPtr()
298 std::vector<std::string> _iPortIds;
300 std::map<std::string, size_t> _iPortIdsMap;
302 std::vector<std::string> _oPortIds;
304 boost::python::object _pythonF;
306 std::array< const bpy::object**, input_s > _inputs;
308 std::array< bpy::object*, output_s > _outputs;
313 #endif //PYTHON_FUNCTIONAL_NODE_H