1717*******************************************************************************
1818* Contact information: contact@sofa-framework.org *
1919******************************************************************************/
20-
21-
2220// / Neede to have automatic conversion from pybind types to stl container.
2321#include < pybind11/stl.h>
24- #include < pybind11/eval .h>
22+ #include < pybind11/numpy .h>
2523
2624#include < sofa/simulation/Simulation.h>
2725#include < sofa/core/ComponentNameHelper.h>
@@ -35,6 +33,9 @@ namespace simpleapi = sofa::simpleapi;
3533#include < sofa/helper/logging/Messaging.h>
3634using sofa::helper::logging::Message;
3735
36+ #include < sofa/helper/DiffLib.h>
37+ using sofa::helper::getClosestMatch;
38+
3839#include < sofa/simulation/graph/DAGNode.h>
3940using sofa::core::ExecParams;
4041
@@ -59,41 +60,67 @@ using sofapython3::PythonEnvironment;
5960#include < SofaPython3/Sofa/Core/Binding_NodeIterator.h>
6061#include < SofaPython3/Sofa/Core/Binding_PythonScriptEvent.h>
6162
63+ #include < SofaPython3/SpellingSuggestionHelper.h>
64+
6265using sofa::core::objectmodel::BaseObjectDescription;
6366
6467#include < queue>
6568#include < sofa/core/objectmodel/Link.h>
6669
70+ // These two lines are there to handle deprecated version of pybind.
71+ SOFAPYTHON3_BIND_ATTRIBUTE_ERROR ()
72+ SOFAPYTHON3_ADD_PYBIND_TYPE_FOR_OLD_VERSION()
73+
6774// / Makes an alias for the pybind11 namespace to increase readability.
6875namespace py { using namespace pybind11 ; }
6976
7077using sofa::simulation::Node;
7178
72- namespace sofapython3 {
79+ namespace sofapython3
80+ {
7381
74- bool checkParamUsage (BaseObjectDescription& desc)
82+ namespace
7583{
76- bool hasFailure = false ;
77- std::stringstream tmp;
78- tmp << " Unknown Attribute(s): " << msgendl ;
84+ bool checkParamUsage (BaseObjectDescription& desc, const Base* base)
85+ {
86+ std::vector<std::tuple<std::string, std::string>> paramErrors ;
7987 for ( auto & it : desc.getAttributeMap () )
8088 {
8189 if (!it.second .isAccessed ())
8290 {
83- hasFailure = true ;
84- tmp << " - \" " <<it.first <<" \" with value: \" " <<std::string (it.second ) << msgendl;
91+ paramErrors.emplace_back (std::make_tuple (it.first , it.second ));
8592 }
8693 }
87- if (!desc.getErrors ().empty ())
88- {
89- hasFailure = true ;
90- tmp << desc.getErrors ()[0 ];
91- }
92- if (hasFailure)
94+
95+ if (!paramErrors.empty () || !desc.getErrors ().empty ())
9396 {
97+ std::stringstream tmp;
98+ tmp << " Unknown Attribute(s): " << msgendl;
99+
100+ std::vector<std::string> possibleNames;
101+ if (base)
102+ {
103+ fillVectorOfStringFrom (base->getDataFields (), std::back_inserter (possibleNames), [](const BaseData* d){return d->getName ();});
104+ fillVectorOfStringFrom (base->getLinks (), std::back_inserter (possibleNames), [](const BaseLink* l){return l->getName ();});
105+ }
106+
107+ for (auto & [name, value] : paramErrors)
108+ {
109+ tmp << " - Unable to set attribute '" << name <<" ' with value: " << value;
110+ const auto & v = getClosestMatch (name, possibleNames);
111+ if (!v.empty ())
112+ tmp << " . Possible misspelling of attribute '" << std::get<0 >(v[0 ]) << " ' ?" ;
113+ else
114+ tmp << " ." ;
115+ tmp << msgendl;
116+ }
117+
118+ if (!desc.getErrors ().empty ())
119+ tmp << desc.getErrors ()[0 ];
94120 throw py::type_error (tmp.str ());
95121 }
96- return hasFailure;
122+
123+ return false ;
97124}
98125
99126py::object getItem (Node& self, std::list<std::string>& path)
@@ -179,7 +206,7 @@ py::object getObject(Node &n, const std::string &name, const py::kwargs& kwargs)
179206 msg_deprecated (&n) << " Calling the method getObject() with extra arguments is not supported anymore."
180207 << " To remove this message please refer to the documentation of the getObject method"
181208 << msgendl
182- << PythonEnvironment::getPythonCallingPointString () ;
209+ << PythonEnvironment::getPythonCallingPointString () ;
183210 }
184211
185212 BaseObject *object = n.getObject (name);
@@ -247,7 +274,7 @@ py::object addObjectKwargs(Node* self, const std::string& type, const py::kwargs
247274
248275 setFieldsFromPythonValues (object.get (), kwargs);
249276
250- checkParamUsage (desc);
277+ checkParamUsage (desc, object. get () );
251278
252279 // Convert the logged messages in the object's internal logging into python exception.
253280 // this is not a very fast way to do that...but well...python is slow anyway. And serious
@@ -342,7 +369,7 @@ py::object addChildKwargs(Node* self, const std::string& name, const py::kwargs&
342369 node->setInstanciationSourceFileName (finfo->filename );
343370 node->setInstanciationSourceFilePos (finfo->line );
344371
345- checkParamUsage (desc);
372+ checkParamUsage (desc, node. get () );
346373
347374 for (auto a : kwargs)
348375 {
@@ -398,56 +425,79 @@ py::object removeChildByName(Node& n, const std::string name)
398425std::unique_ptr<NodeIterator> property_children (Node* node)
399426{
400427 return std::make_unique<NodeIterator>(node,
401- [](Node* n) -> size_t { return n->child .size (); },
402- [](Node* n, unsigned int index) -> Base::SPtr { return n->child [index]; },
403- [](const Node* n, const std::string& name) { return n->getChild (name); },
404- [](Node* n, unsigned int index) { n->removeChild (n->child [index]); }
405- );
428+ [](Node* n) -> size_t { return n->child .size (); },
429+ [](Node* n, unsigned int index) -> Base::SPtr { return n->child [index]; },
430+ [](const Node* n, const std::string& name) { return n->getChild (name); },
431+ [](Node* n, unsigned int index) { n->removeChild (n->child [index]); }
432+ );
406433}
407434
408435std::unique_ptr<NodeIterator> property_parents (Node* node)
409436{
410437 return std::make_unique<NodeIterator>(node,
411- [](Node* n) -> size_t { return n->getNbParents (); },
412- [](Node* n, unsigned int index) -> Node::SPtr {
413- auto p = n->getParents ();
414- return static_cast <Node*>(p[index]);
415- },
416- [](const Node* n, const std::string& name) -> sofa::core::Base* {
417- const auto & parents = n->getParents ();
418- return *std::find_if (parents.begin (),
419- parents.end (),
420- [name](BaseNode* child){ return child->getName () == name; });
421- },
422- [](Node*, unsigned int ) {
423- throw std::runtime_error (" Removing a parent is not a supported operation. Please detach the node from the corresponding graph node." );
424- });
438+ [](Node* n) -> size_t { return n->getNbParents (); },
439+ [](Node* n, unsigned int index) -> Node::SPtr {
440+ auto p = n->getParents ();
441+ return static_cast <Node*>(p[index]);
442+ },
443+ [](const Node* n, const std::string& name) -> sofa::core::Base* {
444+ const auto & parents = n->getParents ();
445+ return *std::find_if (parents.begin (),
446+ parents.end (),
447+ [name](BaseNode* child){ return child->getName () == name; });
448+ },
449+ [](Node*, unsigned int ) {
450+ throw std::runtime_error (" Removing a parent is not a supported operation. Please detach the node from the corresponding graph node." );
451+ });
425452}
426453
427454std::unique_ptr<NodeIterator> property_objects (Node* node)
428455{
429456 return std::make_unique<NodeIterator>(node,
430- [](Node* n) -> size_t { return n->object .size (); },
431- [](Node* n, unsigned int index) -> Base::SPtr { return (n->object [index]);},
432- [](const Node* n, const std::string& name) { return n->getObject (name); },
433- [](Node* n, unsigned int index) { n->removeObject (n->object [index]);}
434- );
457+ [](Node* n) -> size_t { return n->object .size (); },
458+ [](Node* n, unsigned int index) -> Base::SPtr { return (n->object [index]);},
459+ [](const Node* n, const std::string& name) { return n->getObject (name); },
460+ [](Node* n, unsigned int index) { n->removeObject (n->object [index]);}
461+ );
435462}
436463
437- py::object __getattr__ (Node& self , const std::string& name)
464+ py::object __getattr__ (py::object pyself , const std::string& name)
438465{
466+ Node* selfnode = py::cast<Node*>(pyself);
439467 // / Search in the object lists
440- BaseObject *object = self. getObject (name);
468+ BaseObject *object = selfnode-> getObject (name);
441469 if (object)
442470 return PythonFactory::toPython (object);
443471
444472 // / Search in the child lists
445- Node *child = self. getChild (name);
473+ Node *child = selfnode-> getChild (name);
446474 if (child)
447475 return PythonFactory::toPython (child);
448476
449477 // / Search in the data & link lists
450- return BindingBase::GetAttr (&self, name, true );
478+ py::object result = BindingBase::GetAttr (selfnode, name, false );
479+ if (!result.is_none ())
480+ return result;
481+
482+ std::stringstream tmp;
483+ emitSpellingMessage (tmp, " - The data field named " , selfnode->getDataFields (), name, 2 , 0.8 );
484+ emitSpellingMessage (tmp, " - The link named " , selfnode->getDataFields (), name, 2 , 0.8 );
485+ emitSpellingMessage (tmp, " - The object named " , selfnode->getNodeObjects (), name, 2 , 0.8 );
486+ emitSpellingMessage (tmp, " - The child node named " , selfnode->getChildren (), name, 2 , 0.8 );
487+
488+ // Also provide spelling hints on python functions.
489+ emitSpellingMessage (tmp, " - The python attribute named " , py::cast<py::dict>(py::type::of (pyself).attr (" __dict__" )), name, 5 , 0.8 ,
490+ [](const std::pair<py::handle, py::handle>& kv) { return py::cast<std::string>(std::get<0 >(kv)); });
491+
492+ std::stringstream message;
493+ message << " Unable to find attribute: " +name;
494+ if (!tmp.str ().empty ())
495+ {
496+ message << msgendl;
497+ message << " You possibly wanted to access: " << msgendl;
498+ message << tmp.rdbuf ();
499+ }
500+ throw pybind11::attribute_error (message.str ());
451501}
452502
453503// / gets an item using its path (path is dot-separated, relative to the object
@@ -549,6 +599,8 @@ void sendEvent(Node* self, py::object pyUserData, char* eventName)
549599 self->propagateEvent (sofa::core::execparams::defaultInstance (), &event);
550600}
551601
602+ }
603+
552604void moduleAddNode (py::module &m) {
553605 // / Register the complete parent-child relationship between Base and Node to the pybind11
554606 // / typing system.
0 commit comments