From ad57821cf9dba211f9ccfe6f48e438d0c6ba8cff Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 11 Jul 2019 13:57:53 +0800 Subject: [PATCH] Python feature/observer related changes * Add new API and signal handler in document observer * Pre initialize python handler function to improve performance. In case Python code use dynamic patching, i.e. add class method at runtime (which is rare and should be discouraged), the python feature can be re-initialized by simply assign proeprty Proxy again. * Add property tracking in DocumentObjectT * WidgetFactory adds support for accepting python QIcon, which is used by ViewProviderPythonFeature --- src/App/DocumentObserver.cpp | 39 +- src/App/DocumentObserver.h | 18 +- src/App/DocumentObserverPython.cpp | 437 ++++----- src/App/DocumentObserverPython.h | 70 +- src/App/FeaturePython.cpp | 487 ++++++++-- src/App/FeaturePython.h | 189 +++- src/Gui/DocumentObserverPython.cpp | 148 ++-- src/Gui/DocumentObserverPython.h | 29 +- src/Gui/ViewProviderPythonFeature.cpp | 1176 +++++++++++++++---------- src/Gui/ViewProviderPythonFeature.h | 214 ++++- src/Gui/WidgetFactory.cpp | 17 + src/Gui/WidgetFactory.h | 1 + 12 files changed, 1875 insertions(+), 950 deletions(-) diff --git a/src/App/DocumentObserver.cpp b/src/App/DocumentObserver.cpp index 53421d386d..18122c157a 100644 --- a/src/App/DocumentObserver.cpp +++ b/src/App/DocumentObserver.cpp @@ -103,13 +103,18 @@ DocumentObjectT::DocumentObjectT() { } -DocumentObjectT::DocumentObjectT(DocumentObject* obj) +DocumentObjectT::DocumentObjectT(const DocumentObject* obj) { object = obj->getNameInDocument(); label = obj->Label.getValue(); document = obj->getDocument()->getName(); } +DocumentObjectT::DocumentObjectT(const Property* prop) +{ + *this = prop; +} + DocumentObjectT::~DocumentObjectT() { } @@ -121,6 +126,7 @@ void DocumentObjectT::operator=(const DocumentObjectT& obj) object = obj.object; label = obj.label; document = obj.document; + property = obj.property; } void DocumentObjectT::operator=(const DocumentObject* obj) @@ -128,6 +134,16 @@ void DocumentObjectT::operator=(const DocumentObject* obj) object = obj->getNameInDocument(); label = obj->Label.getValue(); document = obj->getDocument()->getName(); + property.clear(); +} + +void DocumentObjectT::operator=(const Property *prop) { + auto obj = dynamic_cast(prop->getContainer()); + assert(obj); + object = obj->getNameInDocument(); + label = obj->Label.getValue(); + document = obj->getDocument()->getName(); + property = prop->getName(); } Document* DocumentObjectT::getDocument() const @@ -192,6 +208,27 @@ std::string DocumentObjectT::getObjectPython() const return str.str(); } +std::string DocumentObjectT::getPropertyName() const { + return property; +} + +std::string DocumentObjectT::getPropertyPython() const +{ + std::stringstream str; + str << "FreeCAD.getDocument('" << document + << "').getObject('" << object + << "')"; + if(property.size()) + str << '.' << property; + return str.str(); +} + +Property *DocumentObjectT::getProperty() const { + auto obj = getObject(); + if(obj) + return obj->getPropertyByName(property.c_str()); + return 0; +} // ----------------------------------------------------------------------------- DocumentObserver::DocumentObserver() : _document(0) diff --git a/src/App/DocumentObserver.h b/src/App/DocumentObserver.h index 41627e3bab..abb38423bc 100644 --- a/src/App/DocumentObserver.h +++ b/src/App/DocumentObserver.h @@ -83,13 +83,17 @@ public: /*! Constructor */ DocumentObjectT(); /*! Constructor */ - DocumentObjectT(DocumentObject*); + DocumentObjectT(const DocumentObject*); + /*! Constructor */ + DocumentObjectT(const Property*); /*! Destructor */ ~DocumentObjectT(); /*! Assignment operator */ void operator=(const DocumentObjectT&); /*! Assignment operator */ void operator=(const DocumentObject*); + /*! Assignment operator */ + void operator=(const Property*); /*! Get a pointer to the document or 0 if it doesn't exist any more. */ Document* getDocument() const; @@ -99,23 +103,35 @@ public: std::string getDocumentPython() const; /*! Get a pointer to the document object or 0 if it doesn't exist any more. */ DocumentObject* getObject() const; + /*! Get a pointer to the property or 0 if it doesn't exist any more. */ + Property* getProperty() const; /*! Get the name of the document object. */ std::string getObjectName() const; /*! Get the label of the document object. */ std::string getObjectLabel() const; + /*! Get the name of the property. */ + std::string getPropertyName() const; /*! Get the document object as Python command. */ std::string getObjectPython() const; + /*! Get the property as Python command. */ + std::string getPropertyPython() const; /*! Get a pointer to the document or 0 if it doesn't exist any more or the type doesn't match. */ template inline T* getObjectAs() const { return Base::freecad_dynamic_cast(getObject()); } + template + inline T* getPropertyAs() const + { + return Base::freecad_dynamic_cast(getProperty()); + } private: std::string document; std::string object; std::string label; + std::string property; }; /** diff --git a/src/App/DocumentObserverPython.cpp b/src/App/DocumentObserverPython.cpp index 7d1fa71256..ccc0b2055a 100644 --- a/src/App/DocumentObserverPython.cpp +++ b/src/App/DocumentObserverPython.cpp @@ -59,95 +59,38 @@ void DocumentObserverPython::removeObserver(const Py::Object& obj) DocumentObserverPython::DocumentObserverPython(const Py::Object& obj) : inst(obj) { - this->connectApplicationCreatedDocument = App::GetApplication().signalNewDocument.connect(boost::bind - (&DocumentObserverPython::slotCreatedDocument, this, _1)); - this->connectApplicationDeletedDocument = App::GetApplication().signalDeleteDocument.connect(boost::bind - (&DocumentObserverPython::slotDeletedDocument, this, _1)); - this->connectApplicationRelabelDocument = App::GetApplication().signalRelabelDocument.connect(boost::bind - (&DocumentObserverPython::slotRelabelDocument, this, _1)); - this->connectApplicationActivateDocument = App::GetApplication().signalActiveDocument.connect(boost::bind - (&DocumentObserverPython::slotActivateDocument, this, _1)); - this->connectApplicationUndoDocument = App::GetApplication().signalUndoDocument.connect(boost::bind - (&DocumentObserverPython::slotUndoDocument, this, _1)); - this->connectApplicationRedoDocument = App::GetApplication().signalRedoDocument.connect(boost::bind - (&DocumentObserverPython::slotRedoDocument, this, _1)); +#define signalCreatedDocument signalNewDocument +#define signalCreatedObject signalNewObject +#define signalRecomputedObject signalObjectRecomputed +#define signalRecomputedDocument signalRecomputed +#define signalActivateDocument signalActiveDocument +#define signalDeletedDocument signalDeleteDocument - this->connectDocumentBeforeChange = App::GetApplication().signalBeforeChangeDocument.connect(boost::bind - (&DocumentObserverPython::slotBeforeChangeDocument, this, _1, _2)); - this->connectDocumentChanged = App::GetApplication().signalChangedDocument.connect(boost::bind - (&DocumentObserverPython::slotChangedDocument, this, _1, _2)); - this->connectDocumentCreatedObject = App::GetApplication().signalNewObject.connect(boost::bind - (&DocumentObserverPython::slotCreatedObject, this, _1)); - this->connectDocumentDeletedObject = App::GetApplication().signalDeletedObject.connect(boost::bind - (&DocumentObserverPython::slotDeletedObject, this, _1)); - this->connectDocumentBeforeChangeObject = App::GetApplication().signalBeforeChangeObject.connect(boost::bind - (&DocumentObserverPython::slotBeforeChangeObject, this, _1, _2)); - this->connectDocumentChangedObject = App::GetApplication().signalChangedObject.connect(boost::bind - (&DocumentObserverPython::slotChangedObject, this, _1, _2)); +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name,...) do{\ + FC_PY_GetCallable(obj.ptr(),"slot" #_name, py##_name);\ + if(!py##_name.isNone())\ + connect##_name = App::GetApplication().signal##_name.connect(\ + boost::bind(&DocumentObserverPython::slot##_name, this, ##__VA_ARGS__));\ + }while(0); - this->connectDocumentObjectRecomputed = App::GetApplication().signalObjectRecomputed.connect(boost::bind - (&DocumentObserverPython::slotRecomputedObject, this, _1)); - this->connectDocumentRecomputed = App::GetApplication().signalRecomputed.connect(boost::bind - (&DocumentObserverPython::slotRecomputedDocument, this, _1)); - - this->connectDocumentOpenTransaction = App::GetApplication().signalOpenTransaction.connect(boost::bind - (&DocumentObserverPython::slotOpenTransaction, this, _1, _2)); - this->connectDocumentCommitTransaction = App::GetApplication().signalCommitTransaction.connect(boost::bind - (&DocumentObserverPython::slotCommitTransaction, this, _1)); - this->connectDocumentAbortTransaction = App::GetApplication().signalAbortTransaction.connect(boost::bind - (&DocumentObserverPython::slotAbortTransaction, this, _1)); - - this->connectDocumentStartSave = App::GetApplication().signalStartSaveDocument.connect(boost::bind - (&DocumentObserverPython::slotStartSaveDocument, this, _1, _2)); - this->connectDocumentFinishSave = App::GetApplication().signalFinishSaveDocument.connect(boost::bind - (&DocumentObserverPython::slotFinishSaveDocument, this, _1, _2)); - - this->connectObjectAppendDynamicProperty = App::GetApplication().signalAppendDynamicProperty.connect(boost::bind - (&DocumentObserverPython::slotAppendDynamicProperty, this, _1)); - this->connectObjectRemoveDynamicProperty = App::GetApplication().signalRemoveDynamicProperty.connect(boost::bind - (&DocumentObserverPython::slotRemoveDynamicProperty, this, _1)); - this->connectObjectChangePropertyEditor = App::GetApplication().signalChangePropertyEditor.connect(boost::bind - (&DocumentObserverPython::slotChangePropertyEditor, this, _1)); + FC_PY_DOC_OBSERVER } DocumentObserverPython::~DocumentObserverPython() { - this->connectApplicationCreatedDocument.disconnect(); - this->connectApplicationDeletedDocument.disconnect(); - this->connectApplicationRelabelDocument.disconnect(); - this->connectApplicationActivateDocument.disconnect(); - this->connectApplicationUndoDocument.disconnect(); - this->connectApplicationRedoDocument.disconnect(); - - this->connectDocumentBeforeChange.disconnect(); - this->connectDocumentChanged.disconnect(); - this->connectDocumentCreatedObject.disconnect(); - this->connectDocumentDeletedObject.disconnect(); - this->connectDocumentBeforeChangeObject.disconnect(); - this->connectDocumentChangedObject.disconnect(); - this->connectDocumentObjectRecomputed.disconnect(); - this->connectDocumentRecomputed.disconnect(); - this->connectDocumentOpenTransaction.disconnect(); - this->connectDocumentCommitTransaction.disconnect(); - this->connectDocumentAbortTransaction.disconnect(); - this->connectDocumentStartSave.disconnect(); - this->connectDocumentFinishSave.disconnect(); - - this->connectObjectAppendDynamicProperty.disconnect(); - this->connectObjectRemoveDynamicProperty.disconnect(); - this->connectObjectChangePropertyEditor.disconnect(); +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name,...) connect##_name.disconnect(); + FC_PY_DOC_OBSERVER } void DocumentObserverPython::slotCreatedDocument(const App::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotCreatedDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotCreatedDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyCreatedDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -159,12 +102,9 @@ void DocumentObserverPython::slotDeletedDocument(const App::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotDeletedDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotDeletedDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyDeletedDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -176,12 +116,9 @@ void DocumentObserverPython::slotRelabelDocument(const App::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotRelabelDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotRelabelDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyRelabelDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -193,12 +130,9 @@ void DocumentObserverPython::slotActivateDocument(const App::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotActivateDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotActivateDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyActivateDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -210,12 +144,9 @@ void DocumentObserverPython::slotUndoDocument(const App::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotUndoDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotUndoDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyUndoDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -223,16 +154,66 @@ void DocumentObserverPython::slotUndoDocument(const App::Document& Doc) } } + void DocumentObserverPython::slotRedoDocument(const App::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotRedoDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotRedoDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyRedoDocument.ptr(),args.ptr()); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } +} + +void DocumentObserverPython::slotUndo() +{ + Base::PyGILStateLocker lock; + try { + Base::pyCall(pyUndo.ptr()); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } +} + +void DocumentObserverPython::slotRedo() +{ + Base::PyGILStateLocker lock; + try { + Base::pyCall(pyRedo.ptr()); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } +} + +void DocumentObserverPython::slotBeforeCloseTransaction(bool abort) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0, Py::Boolean(abort)); + Base::pyCall(pyBeforeCloseTransaction.ptr(),args.ptr()); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } +} + +void DocumentObserverPython::slotCloseTransaction(bool abort) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0, Py::Boolean(abort)); + Base::pyCall(pyCloseTransaction.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -244,17 +225,14 @@ void DocumentObserverPython::slotBeforeChangeDocument(const App::Document& Doc, { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotBeforeChangeDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotBeforeChangeDocument"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = Doc.getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = Doc.getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyBeforeChangeDocument.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -267,17 +245,14 @@ void DocumentObserverPython::slotChangedDocument(const App::Document& Doc, const { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotChangedDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotChangedDocument"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = Doc.getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = Doc.getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyChangedDocument.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -290,12 +265,9 @@ void DocumentObserverPython::slotCreatedObject(const App::DocumentObject& Obj) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotCreatedObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotCreatedObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + Base::pyCall(pyCreatedObject.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -307,12 +279,9 @@ void DocumentObserverPython::slotDeletedObject(const App::DocumentObject& Obj) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotDeletedObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotDeletedObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + Base::pyCall(pyDeletedObject.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -325,17 +294,14 @@ void DocumentObserverPython::slotBeforeChangeObject(const App::DocumentObject& O { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotBeforeChangeObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotBeforeChangeObject"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = Obj.getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = Obj.getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyBeforeChangeObject.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -349,17 +315,14 @@ void DocumentObserverPython::slotChangedObject(const App::DocumentObject& Obj, { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotChangedObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotChangedObject"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = Obj.getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = Obj.getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyChangedObject.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -372,12 +335,9 @@ void DocumentObserverPython::slotRecomputedObject(const App::DocumentObject& Obj { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotRecomputedObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotRecomputedObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + Base::pyCall(pyRecomputedObject.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -389,12 +349,23 @@ void DocumentObserverPython::slotRecomputedDocument(const App::Document& doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotRecomputedDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotRecomputedDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); + Base::pyCall(pyRecomputedDocument.ptr(),args.ptr()); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } +} + +void DocumentObserverPython::slotBeforeRecomputeDocument(const App::Document& doc) +{ + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); + Base::pyCall(pyBeforeRecomputeDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -406,13 +377,10 @@ void DocumentObserverPython::slotOpenTransaction(const App::Document& doc, std:: { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotOpenTransaction"))) { - Py::Callable method(this->inst.getAttr(std::string("slotOpenTransaction"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); - args.setItem(1, Py::String(str)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); + args.setItem(1, Py::String(str)); + Base::pyCall(pyOpenTransaction.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -424,12 +392,9 @@ void DocumentObserverPython::slotCommitTransaction(const App::Document& doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotCommitTransaction"))) { - Py::Callable method(this->inst.getAttr(std::string("slotCommitTransaction"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); + Base::pyCall(pyCommitTransaction.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -441,12 +406,9 @@ void DocumentObserverPython::slotAbortTransaction(const App::Document& doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotAbortTransaction"))) { - Py::Callable method(this->inst.getAttr(std::string("slotAbortTransaction"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); + Base::pyCall(pyAbortTransaction.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -458,18 +420,15 @@ void DocumentObserverPython::slotAppendDynamicProperty(const App::Property& Prop { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotAppendDynamicProperty"))) { - auto container = Prop.getContainer(); - Py::Callable method(this->inst.getAttr(std::string("slotAppendDynamicProperty"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(container->getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = container->getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + auto container = Prop.getContainer(); + Py::Tuple args(2); + args.setItem(0, Py::Object(container->getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = container->getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyAppendDynamicProperty.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -482,18 +441,15 @@ void DocumentObserverPython::slotRemoveDynamicProperty(const App::Property& Prop { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotRemoveDynamicProperty"))) { - auto container = Prop.getContainer(); - Py::Callable method(this->inst.getAttr(std::string("slotRemoveDynamicProperty"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(container->getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = container->getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + auto container = Prop.getContainer(); + Py::Tuple args(2); + args.setItem(0, Py::Object(container->getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = container->getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyRemoveDynamicProperty.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -502,22 +458,19 @@ void DocumentObserverPython::slotRemoveDynamicProperty(const App::Property& Prop } } -void DocumentObserverPython::slotChangePropertyEditor(const App::Property& Prop) +void DocumentObserverPython::slotChangePropertyEditor(const App::Document &, const App::Property& Prop) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotChangePropertyEditor"))) { - auto container = Prop.getContainer(); - Py::Callable method(this->inst.getAttr(std::string("slotChangePropertyEditor"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(container->getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = container->getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + auto container = Prop.getContainer(); + Py::Tuple args(2); + args.setItem(0, Py::Object(container->getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = container->getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyChangePropertyEditor.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -530,13 +483,10 @@ void DocumentObserverPython::slotStartSaveDocument(const App::Document& doc, con { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotStartSaveDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotStartSaveDocument"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); - args.setItem(1, Py::String(file)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); + args.setItem(1, Py::String(file)); + Base::pyCall(pyStartSaveDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -548,13 +498,10 @@ void DocumentObserverPython::slotFinishSaveDocument(const App::Document& doc, co { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotFinishSaveDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotFinishSaveDocument"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); - args.setItem(1, Py::String(file)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(doc).getPyObject(), true)); + args.setItem(1, Py::String(file)); + Base::pyCall(pyFinishSaveDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text diff --git a/src/App/DocumentObserverPython.h b/src/App/DocumentObserverPython.h index afff82b45b..754f9c79fd 100644 --- a/src/App/DocumentObserverPython.h +++ b/src/App/DocumentObserverPython.h @@ -77,6 +77,8 @@ private: void slotRedoDocument(const App::Document& Doc); /** Called when a given object is recomputed */ void slotRecomputedObject(const App::DocumentObject& Obj); + /** Called before an observed document is recomputed */ + void slotBeforeRecomputeDocument(const App::Document& Doc); /** Called when an observed document is recomputed */ void slotRecomputedDocument(const App::Document& Doc); /** Called when an observed document opens a transaction */ @@ -85,12 +87,20 @@ private: void slotCommitTransaction(const App::Document& Doc); /** Called when an observed document aborts a transaction */ void slotAbortTransaction(const App::Document& Doc); + /** Called after application wide undo */ + void slotUndo(); + /** Called after application wide redo */ + void slotRedo(); + /** Called before closing/aborting application active transaction */ + void slotBeforeCloseTransaction(bool abort); + /** Called after closing/aborting application active transaction */ + void slotCloseTransaction(bool abort); /** Called when an object gets a new dynamic property added*/ void slotAppendDynamicProperty(const App::Property& Prop); /** Called when an object gets a dynamic property removed*/ void slotRemoveDynamicProperty(const App::Property& Prop); /** Called when an object property gets a new editor relevant status like hidden or read only*/ - void slotChangePropertyEditor(const App::Property& Prop); + void slotChangePropertyEditor(const App::Document &Doc, const App::Property& Prop); /** Called when a document is about to be saved*/ void slotStartSaveDocument(const App::Document&, const std::string&); /** Called when an document has been saved*/ @@ -101,28 +111,42 @@ private: static std::vector _instances; typedef boost::signals2::connection Connection; - Connection connectApplicationCreatedDocument; - Connection connectApplicationDeletedDocument; - Connection connectApplicationRelabelDocument; - Connection connectApplicationActivateDocument; - Connection connectApplicationUndoDocument; - Connection connectApplicationRedoDocument; - Connection connectDocumentBeforeChange; - Connection connectDocumentChanged; - Connection connectDocumentCreatedObject; - Connection connectDocumentDeletedObject; - Connection connectDocumentBeforeChangeObject; - Connection connectDocumentChangedObject; - Connection connectDocumentObjectRecomputed; - Connection connectDocumentRecomputed; - Connection connectDocumentOpenTransaction; - Connection connectDocumentCommitTransaction; - Connection connectDocumentAbortTransaction; - Connection connectDocumentStartSave; - Connection connectDocumentFinishSave; - Connection connectObjectAppendDynamicProperty; - Connection connectObjectRemoveDynamicProperty; - Connection connectObjectChangePropertyEditor; + +#define FC_PY_DOC_OBSERVER \ + FC_PY_ELEMENT(CreatedDocument,_1) \ + FC_PY_ELEMENT(DeletedDocument,_1) \ + FC_PY_ELEMENT(RelabelDocument,_1) \ + FC_PY_ELEMENT(ActivateDocument,_1) \ + FC_PY_ELEMENT(UndoDocument,_1) \ + FC_PY_ELEMENT(RedoDocument,_1) \ + FC_PY_ELEMENT(BeforeChangeDocument,_1,_2) \ + FC_PY_ELEMENT(ChangedDocument,_1,_2) \ + FC_PY_ELEMENT(CreatedObject,_1) \ + FC_PY_ELEMENT(DeletedObject,_1) \ + FC_PY_ELEMENT(BeforeChangeObject,_1,_2) \ + FC_PY_ELEMENT(ChangedObject,_1,_2) \ + FC_PY_ELEMENT(RecomputedObject,_1) \ + FC_PY_ELEMENT(BeforeRecomputeDocument,_1) \ + FC_PY_ELEMENT(RecomputedDocument,_1) \ + FC_PY_ELEMENT(OpenTransaction,_1,_2) \ + FC_PY_ELEMENT(CommitTransaction,_1) \ + FC_PY_ELEMENT(AbortTransaction,_1) \ + FC_PY_ELEMENT(Undo) \ + FC_PY_ELEMENT(Redo) \ + FC_PY_ELEMENT(BeforeCloseTransaction,_1) \ + FC_PY_ELEMENT(CloseTransaction,_1) \ + FC_PY_ELEMENT(StartSaveDocument,_1,_2) \ + FC_PY_ELEMENT(FinishSaveDocument,_1,_2) \ + FC_PY_ELEMENT(AppendDynamicProperty,_1) \ + FC_PY_ELEMENT(RemoveDynamicProperty,_1) \ + FC_PY_ELEMENT(ChangePropertyEditor,_1,_2) + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name,...) \ + Connection connect##_name;\ + Py::Object py##_name; + + FC_PY_DOC_OBSERVER }; } //namespace App diff --git a/src/App/FeaturePython.cpp b/src/App/FeaturePython.cpp index 9d04fecad3..9c61f3db01 100644 --- a/src/App/FeaturePython.cpp +++ b/src/App/FeaturePython.cpp @@ -30,15 +30,16 @@ #include #include #include +#include #include - #include #include "FeaturePython.h" #include "FeaturePythonPyImp.h" using namespace App; -FeaturePythonImp::FeaturePythonImp(App::DocumentObject* o) : object(o) +FeaturePythonImp::FeaturePythonImp(App::DocumentObject* o) + : object(o), has__object__(false) { } @@ -46,43 +47,40 @@ FeaturePythonImp::~FeaturePythonImp() { } +void FeaturePythonImp::init(PyObject *pyobj) { + Base::PyGILStateLocker lock; + has__object__ = !!PyObject_HasAttrString(pyobj, "__object__"); + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_INIT(_name) + + FC_PY_FEATURE_PYTHON +} + +#define FC_PY_CALL_CHECK(_name) _FC_PY_CALL_CHECK(_name,return(false)) + /*! Calls the execute() method of the Python feature class. If the Python feature class doesn't have an execute() method or if it returns False this method also return false and true otherwise. */ bool FeaturePythonImp::execute() { - // avoid recursive calls of execute() - if (object->testStatus(App::PythonCall)) - return false; - - // Run the execute method of the proxy object. + FC_PY_CALL_CHECK(execute) Base::PyGILStateLocker lock; try { - Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == PropertyPythonObject::getClassTypeId()) { - Py::Object feature = static_cast(proxy)->getValue(); - if (feature.hasAttr(std::string("execute"))) { - if (feature.hasAttr("__object__")) { - Base::ObjectStatusLocker exe(App::PythonCall, object); - Py::Callable method(feature.getAttr(std::string("execute"))); - Py::Tuple args; - Py::Object res = method.apply(args); - if (res.isBoolean() && !res.isTrue()) - return false; - return true; - } - else { - Base::ObjectStatusLocker exe(App::PythonCall, object); - Py::Callable method(feature.getAttr(std::string("execute"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(object->getPyObject(), true)); - Py::Object res = method.apply(args); - if (res.isBoolean() && !res.isTrue()) - return false; - return true; - } - } + if (has__object__) { + Py::Object res = Base::pyCall(py_execute.ptr()); + if (res.isBoolean() && !res.isTrue()) + return false; + return true; + } + else { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Object res = Base::pyCall(py_execute.ptr(),args.ptr()); + if (res.isBoolean() && !res.isTrue()) + return false; + return true; } } catch (Py::Exception&) { @@ -96,35 +94,51 @@ bool FeaturePythonImp::execute() return false; } +bool FeaturePythonImp::mustExecute() const +{ + FC_PY_CALL_CHECK(mustExecute) + Base::PyGILStateLocker lock; + try { + if (has__object__) { + Py::Object res(Base::pyCall(py_mustExecute.ptr())); + return res.isTrue(); + } + else { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Object res(Base::pyCall(py_mustExecute.ptr(),args.ptr())); + return res.isTrue(); + } + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return false; +} + + void FeaturePythonImp::onBeforeChange(const Property* prop) { + if(py_onBeforeChange.isNone()) + return; + // Run the execute method of the proxy object. Base::PyGILStateLocker lock; try { - Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == PropertyPythonObject::getClassTypeId()) { - Py::Object feature = static_cast(proxy)->getValue(); - if (feature.hasAttr(std::string("onBeforeChange"))) { - if (feature.hasAttr("__object__")) { - Py::Callable method(feature.getAttr(std::string("onBeforeChange"))); - Py::Tuple args(1); - const char* prop_name = object->getPropertyName(prop); - if (prop_name) { - args.setItem(0, Py::String(prop_name)); - method.apply(args); - } - } - else { - Py::Callable method(feature.getAttr(std::string("onBeforeChange"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - const char* prop_name = object->getPropertyName(prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } - } - } + const char *prop_name = object->getPropertyName(prop); + if(prop_name == 0) + return; + if (has__object__) { + Py::Tuple args(1); + args.setItem(0, Py::String(prop_name)); + Base::pyCall(py_onBeforeChange.ptr(),args.ptr()); + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, Py::String(prop_name)); + Base::pyCall(py_onBeforeChange.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -133,35 +147,52 @@ void FeaturePythonImp::onBeforeChange(const Property* prop) } } -void FeaturePythonImp::onChanged(const Property* prop) +bool FeaturePythonImp::onBeforeChangeLabel(std::string &newLabel) { + if(py_onBeforeChangeLabel.isNone()) + return false; + // Run the execute method of the proxy object. Base::PyGILStateLocker lock; try { - Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == PropertyPythonObject::getClassTypeId()) { - Py::Object feature = static_cast(proxy)->getValue(); - if (feature.hasAttr(std::string("onChanged"))) { - if (feature.hasAttr("__object__")) { - Py::Callable method(feature.getAttr(std::string("onChanged"))); - Py::Tuple args(1); - const char* prop_name = object->getPropertyName(prop); - if (prop_name) { - args.setItem(0, Py::String(prop_name)); - method.apply(args); - } - } - else { - Py::Callable method(feature.getAttr(std::string("onChanged"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - const char* prop_name = object->getPropertyName(prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } - } - } + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1,Py::String(newLabel)); + Py::Object ret(Base::pyCall(py_onBeforeChangeLabel.ptr(),args.ptr())); + if(!ret.isNone()) { + if(!ret.isString()) + throw Base::TypeError("onBeforeChangeLabel expects to return a string"); + newLabel = ret.as_string(); + return true; + } + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return false; +} + +void FeaturePythonImp::onChanged(const Property* prop) +{ + if(py_onChanged.isNone()) + return; + // Run the execute method of the proxy object. + Base::PyGILStateLocker lock; + try { + const char *prop_name = object->getPropertyName(prop); + if(prop_name == 0) + return; + if (has__object__) { + Py::Tuple args(1); + args.setItem(0, Py::String(prop_name)); + Base::pyCall(py_onChanged.ptr(),args.ptr()); + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, Py::String(prop_name)); + Base::pyCall(py_onChanged.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -172,25 +203,18 @@ void FeaturePythonImp::onChanged(const Property* prop) void FeaturePythonImp::onDocumentRestored() { + _FC_PY_CALL_CHECK(onDocumentRestored,return); + // Run the execute method of the proxy object. Base::PyGILStateLocker lock; try { - Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == PropertyPythonObject::getClassTypeId()) { - Py::Object feature = static_cast(proxy)->getValue(); - if (feature.hasAttr(std::string("onDocumentRestored"))) { - if (feature.hasAttr("__object__")) { - Py::Callable method(feature.getAttr(std::string("onDocumentRestored"))); - Py::Tuple args; - method.apply(args); - } - else { - Py::Callable method(feature.getAttr(std::string("onDocumentRestored"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(object->getPyObject(), true)); - method.apply(args); - } - } + if (has__object__) { + Base::pyCall(py_onDocumentRestored.ptr()); + } + else { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Base::pyCall(py_onDocumentRestored.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -199,12 +223,283 @@ void FeaturePythonImp::onDocumentRestored() } } +bool FeaturePythonImp::getSubObject(DocumentObject *&ret, const char *subname, + PyObject **pyObj, Base::Matrix4D *_mat, bool transform, int depth) const +{ + FC_PY_CALL_CHECK(getSubObject); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(6); + args.setItem(0, Py::Object(object->getPyObject(), true)); + if(!subname) subname = ""; + args.setItem(1,Py::String(subname)); + args.setItem(2,Py::Int(pyObj?2:1)); + Base::MatrixPy *pyMat = new Base::MatrixPy(new Base::Matrix4D); + if(_mat) *pyMat->getMatrixPtr() = *_mat; + args.setItem(3,Py::Object(pyMat)); + args.setItem(4,Py::Boolean(transform)); + args.setItem(5,Py::Int(depth)); + + Py::Object res(Base::pyCall(py_getSubObject.ptr(),args.ptr())); + if(res.isNone()) { + ret = 0; + return true; + } + if(!res.isTrue()) + return false; + if(!res.isSequence()) + throw Base::TypeError("getSubObject expects return type of tuple"); + Py::Sequence seq(res); + if(seq.length() < 2 || + (!seq.getItem(0).isNone() && + !PyObject_TypeCheck(seq.getItem(0).ptr(),&DocumentObjectPy::Type)) || + !PyObject_TypeCheck(seq.getItem(1).ptr(),&Base::MatrixPy::Type)) + { + throw Base::TypeError("getSubObject expects return type of (obj,matrix,pyobj)"); + } + if(_mat) + *_mat = *static_cast(seq.getItem(1).ptr())->getMatrixPtr(); + if(pyObj) { + if(seq.length()>2) + *pyObj = Py::new_reference_to(seq.getItem(2)); + else + *pyObj = Py::new_reference_to(Py::None()); + } + if(seq.getItem(0).isNone()) + ret = 0; + else + ret = static_cast(seq.getItem(0).ptr())->getDocumentObjectPtr(); + return true; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + ret = 0; + return true; + } +} + +bool FeaturePythonImp::getSubObjects(std::vector &ret, int reason) const { + FC_PY_CALL_CHECK(getSubObjects); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, Py::Int(reason)); + Py::Object res(Base::pyCall(py_getSubObjects.ptr(),args.ptr())); + if(!res.isTrue()) + return true; + if(!res.isSequence()) + throw Base::TypeError("getSubObjects expects return type of tuple"); + Py::Sequence seq(res); + for(size_t i=0;igetPyObject(), true)); + args.setItem(1,Py::Boolean(recurse)); + Base::MatrixPy *pyMat = new Base::MatrixPy(new Base::Matrix4D); + if(_mat) *pyMat->getMatrixPtr() = *_mat; + args.setItem(2,Py::Object(pyMat)); + args.setItem(3,Py::Boolean(transform)); + args.setItem(4,Py::Int(depth)); + + Py::Object res(Base::pyCall(py_getLinkedObject.ptr(),args.ptr())); + if(!res.isTrue()) { + ret = object; + return true; + } + if(!res.isSequence()) + throw Base::TypeError("getLinkedObject expects return type of (object,matrix)"); + Py::Sequence seq(res); + if(seq.length() != 2 || + (!seq.getItem(0).isNone() && + !PyObject_TypeCheck(seq.getItem(0).ptr(),&DocumentObjectPy::Type)) || + !PyObject_TypeCheck(seq.getItem(1).ptr(),&Base::MatrixPy::Type)) + { + throw Base::TypeError("getLinkedObject expects return type of (object,matrix)"); + } + if(_mat) + *_mat = *static_cast(seq.getItem(1).ptr())->getMatrixPtr(); + if(seq.getItem(0).isNone()) + ret = object; + else + ret = static_cast(seq.getItem(0).ptr())->getDocumentObjectPtr(); + return true; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + ret = 0; + return true; + } +} + PyObject *FeaturePythonImp::getPyObject(void) { // ref counter is set to 1 return new FeaturePythonPyT(object); } +int FeaturePythonImp::hasChildElement() const { + _FC_PY_CALL_CHECK(hasChildElement,return(-1)); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Boolean ok(Base::pyCall(py_hasChildElement.ptr(),args.ptr())); + return static_cast(ok) ? 1 : 0; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + + return -1; +} + +int FeaturePythonImp::isElementVisible(const char *element) const { + _FC_PY_CALL_CHECK(isElementVisible,return(-2)); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1,Py::String(element?element:"")); + return Py::Int(Base::pyCall(py_isElementVisible.ptr(),args.ptr())); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return -2; +} + +int FeaturePythonImp::setElementVisible(const char *element, bool visible) { + _FC_PY_CALL_CHECK(setElementVisible,return(-2)); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(3); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1,Py::String(element?element:"")); + args.setItem(2,Py::Boolean(visible)); + return Py::Int(Base::pyCall(py_setElementVisible.ptr(),args.ptr())); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + + return -2; +} + +std::string FeaturePythonImp::getViewProviderName() +{ + _FC_PY_CALL_CHECK(getViewProviderName,return(std::string())); + Base::PyGILStateLocker lock; + try { + Py::TupleN args(Py::Object(object->getPyObject(), true)); + Py::String ret(Base::pyCall(py_getViewProviderName.ptr(),args.ptr())); + return ret.as_string(); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + + return std::string(); +} + +int FeaturePythonImp::canLinkProperties() const { + _FC_PY_CALL_CHECK(canLinkProperties,return(-1)); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Boolean ok(Base::pyCall(py_canLinkProperties.ptr(),args.ptr())); + return ok?1:0; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + return 0; + } +} + +int FeaturePythonImp::allowDuplicateLabel() const { + _FC_PY_CALL_CHECK(allowDuplicateLabel,return(-1)); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Boolean ok(Base::pyCall(py_allowDuplicateLabel.ptr(),args.ptr())); + return ok?1:0; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + return 0; + } +} + +int FeaturePythonImp::canLoadPartial() const { + _FC_PY_CALL_CHECK(canLoadPartial,return(-1)); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Int ret(Base::pyCall(py_canLoadPartial.ptr(),args.ptr())); + return ret; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return -1; +} + +bool FeaturePythonImp::redirectSubName(std::ostringstream &ss, + App::DocumentObject *topParent, App::DocumentObject *child) const +{ + FC_PY_CALL_CHECK(redirectSubName); + Base::PyGILStateLocker lock; + try { + Py::Tuple args(4); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1,Py::String(ss.str())); + args.setItem(2,topParent?Py::Object(topParent->getPyObject(),true):Py::Object()); + args.setItem(3,child?Py::Object(child->getPyObject(),true):Py::Object()); + Py::Object ret(Base::pyCall(py_redirectSubName.ptr(),args.ptr())); + if(ret.isNone()) + return false; + ss.str(""); + ss << ret.as_string(); + return true; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return false; +} + // --------------------------------------------------------- namespace App { diff --git a/src/App/FeaturePython.h b/src/App/FeaturePython.h index 57f1482fee..54e53588bd 100644 --- a/src/App/FeaturePython.h +++ b/src/App/FeaturePython.h @@ -46,13 +46,109 @@ public: ~FeaturePythonImp(); bool execute(); + bool mustExecute() const; void onBeforeChange(const Property* prop); + bool onBeforeChangeLabel(std::string &newLabel); void onChanged(const Property* prop); void onDocumentRestored(); + std::string getViewProviderName(); PyObject *getPyObject(void); + bool getSubObject(App::DocumentObject *&ret, const char *subname, PyObject **pyObj, + Base::Matrix4D *mat, bool transform, int depth) const; + + bool getSubObjects(std::vector &ret, int reason) const; + + bool getLinkedObject(App::DocumentObject *&ret, bool recurse, + Base::Matrix4D *mat, bool transform, int depth) const; + + int canLinkProperties() const; + + int allowDuplicateLabel() const; + + bool redirectSubName(std::ostringstream &ss, + App::DocumentObject *topParent, App::DocumentObject *child) const; + + int canLoadPartial() const; + + /// return true to activate tree view group object handling + int hasChildElement() const; + /// Get sub-element visibility + int isElementVisible(const char *) const; + /// Set sub-element visibility + int setElementVisible(const char *, bool); + private: App::DocumentObject* object; + bool has__object__; + +#define FC_PY_FEATURE_PYTHON \ + FC_PY_ELEMENT(execute)\ + FC_PY_ELEMENT(mustExecute)\ + FC_PY_ELEMENT(onBeforeChange)\ + FC_PY_ELEMENT(onBeforeChangeLabel)\ + FC_PY_ELEMENT(onChanged)\ + FC_PY_ELEMENT(onDocumentRestored)\ + FC_PY_ELEMENT(getViewProviderName)\ + FC_PY_ELEMENT(getSubObject)\ + FC_PY_ELEMENT(getSubObjects)\ + FC_PY_ELEMENT(getLinkedObject)\ + FC_PY_ELEMENT(canLinkProperties)\ + FC_PY_ELEMENT(allowDuplicateLabel)\ + FC_PY_ELEMENT(redirectSubName)\ + FC_PY_ELEMENT(canLoadPartial)\ + FC_PY_ELEMENT(hasChildElement)\ + FC_PY_ELEMENT(isElementVisible)\ + FC_PY_ELEMENT(setElementVisible) + +#define FC_PY_ELEMENT_DEFINE(_name) \ + Py::Object py_##_name; + +#define FC_PY_ELEMENT_INIT(_name) \ + FC_PY_GetCallable(pyobj,#_name,py_##_name);\ + if(!py_##_name.isNone()) {\ + PyObject *pyRecursive = PyObject_GetAttrString(pyobj, \ + "__allow_recursive_" #_name);\ + if(!pyRecursive) {\ + PyErr_Clear();\ + _Flags.set(FlagAllowRecursive_##_name, false);\ + }else{\ + _Flags.set(FlagAllowRecursive_##_name, PyObject_IsTrue(pyRecursive));\ + Py_DECREF(pyRecursive);\ + }\ + } + +#define FC_PY_ELEMENT_FLAG(_name) \ + FlagCalling_##_name,\ + FlagAllowRecursive_##_name, + +#define _FC_PY_CALL_CHECK(_name,_ret) \ + if((!_Flags.test(FlagAllowRecursive_##_name) \ + && _Flags.test(FlagCalling_##_name)) \ + || py_##_name.isNone()) \ + {\ + _ret;\ + }\ + Base::BitsetLocker guard(_Flags, FlagCalling_##_name); + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_DEFINE(_name) + + FC_PY_FEATURE_PYTHON + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_FLAG(_name) + + enum Flag { + FC_PY_FEATURE_PYTHON + FlagMax, + }; + + typedef std::bitset Flags; + mutable Flags _Flags; + +public: + void init(PyObject *pyobj); }; /** @@ -80,7 +176,9 @@ public: short mustExecute() const { if (this->isTouched()) return 1; - return FeatureT::mustExecute(); + auto ret = FeatureT::mustExecute(); + if(ret) return ret; + return imp->mustExecute()?1:0; } /// recalculate the Feature virtual DocumentObjectExecReturn *execute(void) { @@ -94,12 +192,93 @@ public: } return DocumentObject::StdReturn; } + virtual const char* getViewProviderNameOverride(void) const override{ + viewProviderName = imp->getViewProviderName(); + if(viewProviderName.size()) + return viewProviderName.c_str(); + return FeatureT::getViewProviderNameOverride(); + } /// returns the type name of the ViewProvider virtual const char* getViewProviderName(void) const { return FeatureT::getViewProviderName(); //return "Gui::ViewProviderPythonFeature"; } + virtual App::DocumentObject *getSubObject(const char *subname, PyObject **pyObj, + Base::Matrix4D *mat, bool transform, int depth) const override + { + App::DocumentObject *ret = 0; + if(imp->getSubObject(ret,subname,pyObj,mat,transform,depth)) + return ret; + return FeatureT::getSubObject(subname,pyObj,mat,transform,depth); + } + + virtual std::vector getSubObjects(int reason=0) const override { + std::vector ret; + if(imp->getSubObjects(ret,reason)) + return ret; + return FeatureT::getSubObjects(reason); + } + + virtual App::DocumentObject *getLinkedObject(bool recurse, + Base::Matrix4D *mat, bool transform, int depth) const override + { + App::DocumentObject *ret = 0; + if(imp->getLinkedObject(ret,recurse,mat,transform,depth)) + return ret; + return FeatureT::getLinkedObject(recurse,mat,transform,depth); + } + + /// return true to activate tree view group object handling + virtual bool hasChildElement() const override { + int ret = imp->hasChildElement(); + if(ret<0) + return FeatureT::hasChildElement(); + return ret?true:false; + } + /// Get sub-element visibility + virtual int isElementVisible(const char *element) const override { + int ret = imp->isElementVisible(element); + if(ret == -2) + return FeatureT::isElementVisible(element); + return ret; + } + /// Set sub-element visibility + virtual int setElementVisible(const char *element, bool visible) override { + int ret = imp->setElementVisible(element,visible); + if(ret == -2) + return FeatureT::setElementVisible(element,visible); + return ret; + } + + virtual bool canLinkProperties() const override { + int ret = imp->canLinkProperties(); + if(ret < 0) + return FeatureT::canLinkProperties(); + return ret?true:false; + } + + virtual bool allowDuplicateLabel() const override { + int ret = imp->allowDuplicateLabel(); + if(ret < 0) + return FeatureT::allowDuplicateLabel(); + return ret?true:false; + } + + virtual bool redirectSubName(std::ostringstream &ss, + App::DocumentObject *topParent, App::DocumentObject *child) const override + { + return imp->redirectSubName(ss,topParent,child) || + FeatureT::redirectSubName(ss,topParent,child); + } + + virtual int canLoadPartial() const override { + int ret = imp->canLoadPartial(); + if(ret>=0) + return ret; + return FeatureT::canLoadPartial(); + } + PyObject *getPyObject(void) { if (FeatureT::PythonObject.is(Py::_None())) { // ref counter is set to 1 @@ -119,7 +298,13 @@ protected: FeatureT::onBeforeChange(prop); imp->onBeforeChange(prop); } + virtual void onBeforeChangeLabel(std::string &newLabel) override{ + if(!imp->onBeforeChangeLabel(newLabel)) + FeatureT::onBeforeChangeLabel(newLabel); + } virtual void onChanged(const Property* prop) { + if(prop == &Proxy) + imp->init(Proxy.getValue().ptr()); imp->onChanged(prop); FeatureT::onChanged(prop); } @@ -130,8 +315,8 @@ protected: private: FeaturePythonImp* imp; - DynamicProperty* props; PropertyPythonObject Proxy; + mutable std::string viewProviderName; }; // Special Feature-Python classes diff --git a/src/Gui/DocumentObserverPython.cpp b/src/Gui/DocumentObserverPython.cpp index ddcb8ae0b7..255331a1a2 100644 --- a/src/Gui/DocumentObserverPython.cpp +++ b/src/Gui/DocumentObserverPython.cpp @@ -60,57 +60,36 @@ void DocumentObserverPython::removeObserver(const Py::Object& obj) DocumentObserverPython::DocumentObserverPython(const Py::Object& obj) : inst(obj) { - this->connectApplicationCreatedDocument = Gui::Application::Instance->signalNewDocument.connect(boost::bind - (&DocumentObserverPython::slotCreatedDocument, this, _1)); - this->connectApplicationDeletedDocument = Gui::Application::Instance->signalDeleteDocument.connect(boost::bind - (&DocumentObserverPython::slotDeletedDocument, this, _1)); - this->connectApplicationRelabelDocument = Gui::Application::Instance->signalRelabelDocument.connect(boost::bind - (&DocumentObserverPython::slotRelabelDocument, this, _1)); - this->connectApplicationRenameDocument = Gui::Application::Instance->signalRenameDocument.connect(boost::bind - (&DocumentObserverPython::slotRelabelDocument, this, _1)); - this->connectApplicationActivateDocument = Gui::Application::Instance->signalActiveDocument.connect(boost::bind - (&DocumentObserverPython::slotActivateDocument, this, _1)); +#define signalCreatedDocument signalNewDocument +#define signalCreatedObject signalNewObject +#define signalDeletedDocument signalDeleteDocument +#define signalActivateDocument signalActiveDocument - this->connectDocumentCreatedObject = Gui::Application::Instance->signalNewObject.connect(boost::bind - (&DocumentObserverPython::slotCreatedObject, this, _1)); - this->connectDocumentDeletedObject = Gui::Application::Instance->signalDeletedObject.connect(boost::bind - (&DocumentObserverPython::slotDeletedObject, this, _1)); - this->connectDocumentChangedObject = Gui::Application::Instance->signalChangedObject.connect(boost::bind - (&DocumentObserverPython::slotChangedObject, this, _1, _2)); - - this->connectDocumentObjectInEdit = Gui::Application::Instance->signalInEdit.connect(boost::bind - (&DocumentObserverPython::slotInEdit, this, _1)); - this->connectDocumentObjectResetEdit = Gui::Application::Instance->signalResetEdit.connect(boost::bind - (&DocumentObserverPython::slotResetEdit, this, _1)); - +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name,...) \ + FC_PY_GetCallable(obj.ptr(),"slot" #_name, py##_name);\ + if(!py##_name.isNone())\ + connect##_name = Application::Instance->signal##_name.connect(\ + boost::bind(&DocumentObserverPython::slot##_name, this, ## __VA_ARGS__)); + + FC_PY_GDOC_OBSERVER } DocumentObserverPython::~DocumentObserverPython() { - this->connectApplicationCreatedDocument.disconnect(); - this->connectApplicationDeletedDocument.disconnect(); - this->connectApplicationRelabelDocument.disconnect(); - this->connectApplicationRenameDocument.disconnect(); - this->connectApplicationActivateDocument.disconnect(); +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name,...) connect##_name.disconnect(); - this->connectDocumentCreatedObject.disconnect(); - this->connectDocumentDeletedObject.disconnect(); - this->connectDocumentChangedObject.disconnect(); - - this->connectDocumentObjectInEdit.disconnect(); - this->connectDocumentObjectResetEdit.disconnect(); + FC_PY_GDOC_OBSERVER } void DocumentObserverPython::slotCreatedDocument(const Gui::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotCreatedDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotCreatedDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyCreatedDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -122,12 +101,9 @@ void DocumentObserverPython::slotDeletedDocument(const Gui::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotDeletedDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotDeletedDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyDeletedDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -139,12 +115,9 @@ void DocumentObserverPython::slotRelabelDocument(const Gui::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotRelabelDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotRelabelDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyRelabelDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -156,12 +129,9 @@ void DocumentObserverPython::slotRenameDocument(const Gui::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotRenameDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotRenameDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyRenameDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -173,12 +143,9 @@ void DocumentObserverPython::slotActivateDocument(const Gui::Document& Doc) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotActivateDocument"))) { - Py::Callable method(this->inst.getAttr(std::string("slotActivateDocument"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Doc).getPyObject(), true)); + Base::pyCall(pyActivateDocument.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -190,12 +157,9 @@ void DocumentObserverPython::slotCreatedObject(const Gui::ViewProvider& Obj) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotCreatedObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotCreatedObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + Base::pyCall(pyCreatedObject.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -207,12 +171,9 @@ void DocumentObserverPython::slotDeletedObject(const Gui::ViewProvider& Obj) { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotDeletedObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotDeletedObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + Base::pyCall(pyDeletedObject.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -225,17 +186,14 @@ void DocumentObserverPython::slotChangedObject(const Gui::ViewProvider& Obj, { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotChangedObject"))) { - Py::Callable method(this->inst.getAttr(std::string("slotChangedObject"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - // If a property is touched but not part of a document object then its name is null. - // In this case the slot function must not be called. - const char* prop_name = Obj.getPropertyName(&Prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } + Py::Tuple args(2); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + // If a property is touched but not part of a document object then its name is null. + // In this case the slot function must not be called. + const char* prop_name = Obj.getPropertyName(&Prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(pyChangedObject.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -248,12 +206,9 @@ void DocumentObserverPython::slotInEdit(const Gui::ViewProviderDocumentObject& O { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotInEdit"))) { - Py::Callable method(this->inst.getAttr(std::string("slotInEdit"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + Base::pyCall(pyInEdit.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -265,12 +220,9 @@ void DocumentObserverPython::slotResetEdit(const Gui::ViewProviderDocumentObject { Base::PyGILStateLocker lock; try { - if (this->inst.hasAttr(std::string("slotResetEdit"))) { - Py::Callable method(this->inst.getAttr(std::string("slotResetEdit"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); - method.apply(args); - } + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(Obj).getPyObject(), true)); + Base::pyCall(pyResetEdit.ptr(),args.ptr()); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text diff --git a/src/Gui/DocumentObserverPython.h b/src/Gui/DocumentObserverPython.h index ca3318a79f..bfe553704a 100644 --- a/src/Gui/DocumentObserverPython.h +++ b/src/Gui/DocumentObserverPython.h @@ -76,16 +76,25 @@ private: static std::vector _instances; typedef boost::signals2::connection Connection; - Connection connectApplicationCreatedDocument; - Connection connectApplicationDeletedDocument; - Connection connectApplicationRelabelDocument; - Connection connectApplicationRenameDocument; - Connection connectApplicationActivateDocument; - Connection connectDocumentCreatedObject; - Connection connectDocumentDeletedObject; - Connection connectDocumentChangedObject; - Connection connectDocumentObjectInEdit; - Connection connectDocumentObjectResetEdit; + +#define FC_PY_GDOC_OBSERVER \ + FC_PY_ELEMENT(CreatedDocument,_1) \ + FC_PY_ELEMENT(DeletedDocument,_1) \ + FC_PY_ELEMENT(RelabelDocument,_1) \ + FC_PY_ELEMENT(RenameDocument,_1) \ + FC_PY_ELEMENT(ActivateDocument,_1) \ + FC_PY_ELEMENT(CreatedObject,_1) \ + FC_PY_ELEMENT(DeletedObject,_1) \ + FC_PY_ELEMENT(ChangedObject,_1,_2) \ + FC_PY_ELEMENT(InEdit,_1) \ + FC_PY_ELEMENT(ResetEdit,_1) + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name,...) \ + Connection connect##_name;\ + Py::Object py##_name; + + FC_PY_GDOC_OBSERVER }; } //namespace Gui diff --git a/src/Gui/ViewProviderPythonFeature.cpp b/src/Gui/ViewProviderPythonFeature.cpp index 58891dd32a..adc63fcea9 100644 --- a/src/Gui/ViewProviderPythonFeature.cpp +++ b/src/Gui/ViewProviderPythonFeature.cpp @@ -50,6 +50,7 @@ # include #endif +#include "ViewProviderDocumentObjectPy.h" #include "ViewProviderPythonFeature.h" #include "Tree.h" #include "Window.h" @@ -57,12 +58,16 @@ #include "BitmapFactory.h" #include "Document.h" #include "WidgetFactory.h" +#include "View3DInventorViewer.h" #include #include #include #include #include #include +#include + +FC_LOG_LEVEL_INIT("ViewProviderPythonFeature",true,true); using namespace Gui; @@ -72,16 +77,27 @@ using namespace Gui; #if 0 namespace Gui { +struct ProxyInfo { + Py::Object viewObject; + Py::Object proxy; + + ~ProxyInfo() { + Base::PyGILStateLocker lock; + viewObject = Py::Object(); + proxy = Py::Object(); + } +}; + class PropertyEvent : public QEvent { public: - PropertyEvent(const Gui::ViewProvider* vp, App::Property* p) - : QEvent(QEvent::Type(QEvent::User)), view(vp), prop(p) + PropertyEvent(const Gui::ViewProviderDocumentObject* vp, const ProxyInfo &info) + : QEvent(QEvent::Type(QEvent::User)), view(vp), info(info) { } - const Gui::ViewProvider* view; - App::Property* prop; + const Gui::ViewProviderDocumentObject* view; + ProxyInfo info; }; class ViewProviderPythonFeatureObserver : public QObject @@ -103,12 +119,56 @@ private: // Make sure that the object hasn't been deleted in the meantime (#0001522) if (it != viewMap.end()) { viewMap.erase(it); - App::Property* prop = pe->view->getPropertyByName("Proxy"); - if (prop && prop->isDerivedFrom(App::PropertyPythonObject::getClassTypeId())) { - prop->Paste(*pe->prop); + + // We check below the python object of the view provider to make + // sure that the view provider is actually the owner of the proxy + // object we cached before. This step is necessary to prevent a + // very obscure bug described here. + // + // The proxy caching is only effective when an object is deleted in + // an event of undo, and we restore the proxy in the event of redo. + // The object is not really freed while inside undo/redo stack. It + // gets really deleted from memory when either the user clears the + // undo/redo stack manually, or the redo stack gets automatically + // cleared when new transaction is created. FC has no explicit + // signaling of when the object is really deleted from the memory. + // This ViewProviderPythonFeatureObserver uses a heuristic to + // decide when to flush the cache in slotAppendObject(), that is, + // it sees any cache miss event as the signaling of an redo clear. + // The bug happens in the very rare event, when the redo stack is + // cleared when new transaction is added, and the freed object's + // memory gets immediately reused by c++ allocator for the new + // object created in the new transaction. This creates a cache + // false hit event, where the old deleted view provider's proxy + // gets mistakenly assigned to the newly created object, which + // happens to have the exact same memory location. This situation + // is very rare and really depends on the system's allocator + // implementation. However, tests show that it happens regularly + // in Linux debug build. To prevent this, we use the trick of + // checking the python object pointer of the view provider to make + // sure the view provider are in fact the same. We hold the python + // object reference count, so it never gets freed and reused like + // its owner view provider. + // + // Side note: the original implementation uses property copy and + // paste to store the proxy object, which is fine, but has the + // trouble of having to manually freed the copied property. And the + // original implementation didn't do that in every case, causing + // memory leak. We now simply stores the python object with + // reference counting, so no need to worry about deleting + + Py::Object viewObject(const_cast(pe->view)->getPyObject(),true); + if(viewObject.ptr() != pe->info.viewObject.ptr()) { + if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("invalid proxy cache " << viewObject.ptr() << ", " << + pe->info.viewObject.ptr() << ", " << pe->info.proxy.ptr()); + }else{ + App::Property* prop = pe->view->getPropertyByName("Proxy"); + if (prop && prop->isDerivedFrom(App::PropertyPythonObject::getClassTypeId())) { + prop->setPyObject(pe->info.proxy.ptr()); + } } } - delete pe->prop; } static ViewProviderPythonFeatureObserver* _singleton; @@ -116,7 +176,7 @@ private: ~ViewProviderPythonFeatureObserver(); typedef std::map< const App::DocumentObject*, - App::Property* + ProxyInfo > ObjectProxy; std::map proxyMap; @@ -145,6 +205,7 @@ void ViewProviderPythonFeatureObserver::slotDeleteDocument(const Gui::Document& App::Document* doc = d.getDocument(); std::map::iterator it = proxyMap.find(doc); if (it != proxyMap.end()) { + Base::PyGILStateLocker lock; proxyMap.erase(it); } } @@ -199,7 +260,10 @@ void ViewProviderPythonFeatureObserver::slotDeleteObject(const Gui::ViewProvider try { App::Property* prop = vp.getPropertyByName("Proxy"); if (prop && prop->isDerivedFrom(App::PropertyPythonObject::getClassTypeId())) { - proxyMap[doc][docobj] = prop->Copy(); + auto &info = proxyMap[doc][docobj]; + info.viewObject = Py::asObject(const_cast(vp).getPyObject()); + info.proxy = Py::asObject(prop->getPyObject()); + FC_LOG("proxy cache " << info.viewObject.ptr() << ", " << info.proxy.ptr()); } } catch (Py::Exception& e) { @@ -224,8 +288,9 @@ ViewProviderPythonFeatureObserver::~ViewProviderPythonFeatureObserver() // ---------------------------------------------------------------------------- -ViewProviderPythonFeatureImp::ViewProviderPythonFeatureImp(ViewProviderDocumentObject* vp) - : object(vp) +ViewProviderPythonFeatureImp::ViewProviderPythonFeatureImp( + ViewProviderDocumentObject* vp, App::PropertyPythonObject &proxy) + : object(vp), Proxy(proxy), has__object__(false) { #if 0 (void)ViewProviderPythonFeatureObserver::instance(); @@ -236,50 +301,66 @@ ViewProviderPythonFeatureImp::~ViewProviderPythonFeatureImp() { } +void ViewProviderPythonFeatureImp::init(PyObject *pyobj) { + Base::PyGILStateLocker lock; + has__object__ = !!PyObject_HasAttrString(pyobj, "__object__"); + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_INIT(_name) + + FC_PY_VIEW_OBJECT +} + +#define FC_PY_CALL_CHECK(_name) _FC_PY_CALL_CHECK(_name,return(NotImplemented)) + QIcon ViewProviderPythonFeatureImp::getIcon() const { + _FC_PY_CALL_CHECK(getIcon,return(QIcon())); + // default icon //static QPixmap px = BitmapFactory().pixmap("Tree_Python"); // Run the getIcon method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("getIcon"))) { - Py::Callable method(vp.getAttr(std::string("getIcon"))); - Py::Tuple args; - Py::String str(method.apply(args)); - std::string content = str.as_std_string("utf-8"); - QPixmap icon; - // Check if the passed string is a filename, otherwise treat as xpm data - QFileInfo fi(QString::fromUtf8(content.c_str())); - if (fi.isFile() && fi.exists()) { - icon.load(fi.absoluteFilePath()); - } else { - QByteArray ary; - int strlen = (int)content.size(); - ary.resize(strlen); - for (int j=0; j lines = ary.split('\n'); - QByteArray buffer; - buffer.reserve(ary.size()+lines.size()); - for (QList::iterator it = lines.begin(); it != lines.end(); ++it) { - QByteArray trim = it->trimmed(); - if (!trim.isEmpty()) { - buffer.append(trim); - buffer.append('\n'); - } - } - icon.loadFromData(buffer, "XPM"); - } - if (!icon.isNull()) { - return icon; + Py::Object ret(Base::pyCall(py_getIcon.ptr())); + if(ret.isNone()) + return QIcon(); + + PythonWrapper wrap; + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + QIcon *picon = wrap.toQIcon(ret.ptr()); + if(picon) + return *picon; + + std::string content = Py::String(ret).as_std_string("utf-8"); + QPixmap icon; + // Check if the passed string is a filename, otherwise treat as xpm data + QFileInfo fi(QString::fromUtf8(content.c_str())); + if (fi.isFile() && fi.exists()) { + icon.load(fi.absoluteFilePath()); + } else { + QByteArray ary; + int strlen = (int)content.size(); + ary.resize(strlen); + for (int j=0; j lines = ary.split('\n'); + QByteArray buffer; + buffer.reserve(ary.size()+lines.size()); + for (QList::iterator it = lines.begin(); it != lines.end(); ++it) { + QByteArray trim = it->trimmed(); + if (!trim.isEmpty()) { + buffer.append(trim); + buffer.append('\n'); } } + icon.loadFromData(buffer, "XPM"); + } + if (!icon.isNull()) { + return icon; } } catch (Py::Exception&) { @@ -290,28 +371,20 @@ QIcon ViewProviderPythonFeatureImp::getIcon() const return QIcon(); } -std::vector ViewProviderPythonFeatureImp::claimChildren(const std::vector& base) const +std::vector +ViewProviderPythonFeatureImp::claimChildren(std::vector&& base) const { + _FC_PY_CALL_CHECK(claimChildren,return(base)); + std::vector children; Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("claimChildren"))) { - Py::Callable method(vp.getAttr(std::string("claimChildren"))); - Py::Tuple args; - Py::Sequence list(method.apply(args)); - for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { - PyObject* item = (*it).ptr(); - if (PyObject_TypeCheck(item, &(App::DocumentObjectPy::Type))) { - App::DocumentObject* obj = static_cast(item)->getDocumentObjectPtr(); - children.push_back(obj); - } - } - } - else { - children = base; + Py::Sequence list(Base::pyCall(py_claimChildren.ptr())); + for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(App::DocumentObjectPy::Type))) { + App::DocumentObject* obj = static_cast(item)->getDocumentObjectPtr(); + children.push_back(obj); } } } @@ -325,19 +398,13 @@ std::vector ViewProviderPythonFeatureImp::claimChildren(co bool ViewProviderPythonFeatureImp::useNewSelectionModel() const { + _FC_PY_CALL_CHECK(useNewSelectionModel,return(true)); + // Run the useNewSelectionModel method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("useNewSelectionModel"))) { - Py::Callable method(vp.getAttr(std::string("useNewSelectionModel"))); - Py::Tuple args; - Py::Boolean ok(method.apply(args)); - return (bool)ok; - } - } + Py::Boolean ok(Py::Callable(py_useNewSelectionModel).apply(Py::Tuple())); + return (bool)ok; } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -349,25 +416,20 @@ bool ViewProviderPythonFeatureImp::useNewSelectionModel() const std::string ViewProviderPythonFeatureImp::getElement(const SoDetail *det) const { + _FC_PY_CALL_CHECK(getElement,return(std::string())); + // Run the onChanged method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("getElement"))) { - PyObject* pivy = 0; - // Note: As there is no ref'counting mechanism for the SoDetail class we must - // pass '0' as the last parameter so that the Python object does not 'own' - // the detail object. - pivy = Base::Interpreter().createSWIGPointerObj("pivy.coin", "SoDetail *", (void*)det, 0); - Py::Callable method(vp.getAttr(std::string("getElement"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(pivy, true)); - Py::String name(method.apply(args)); - return (std::string)name; - } - } + PyObject* pivy = 0; + // Note: As there is no ref'counting mechanism for the SoDetail class we must + // pass '0' as the last parameter so that the Python object does not 'own' + // the detail object. + pivy = Base::Interpreter().createSWIGPointerObj("pivy.coin", "SoDetail *", (void*)det, 0); + Py::Tuple args(1); + args.setItem(0, Py::Object(pivy, true)); + Py::String name(Base::pyCall(py_getElement.ptr(),args.ptr())); + return (std::string)name; } catch (const Base::Exception& e) { e.ReportException(); @@ -377,28 +439,50 @@ std::string ViewProviderPythonFeatureImp::getElement(const SoDetail *det) const e.ReportException(); } - return ""; + return std::string(); +} + +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::getElementPicked(const SoPickedPoint *pp, std::string &subname) const +{ + FC_PY_CALL_CHECK(getElementPicked); + + Base::PyGILStateLocker lock; + try { + PyObject* pivy = 0; + pivy = Base::Interpreter().createSWIGPointerObj("pivy.coin", "SoPickedPoint *", (void*)pp, 0); + Py::Tuple args(1); + args.setItem(0, Py::Object(pivy, true)); + Py::Object ret(Base::pyCall(py_getElementPicked.ptr(),args.ptr())); + if(!ret.isString()) return Rejected; + subname = ret.as_string(); + return Accepted; + } + catch (const Base::Exception& e) { + e.ReportException(); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + + return NotImplemented; } SoDetail* ViewProviderPythonFeatureImp::getDetail(const char* name) const { + _FC_PY_CALL_CHECK(getDetail,return(0)); + // Run the onChanged method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("getDetail"))) { - Py::Callable method(vp.getAttr(std::string("getDetail"))); - Py::Tuple args(1); - args.setItem(0, Py::String(name)); - Py::Object det(method.apply(args)); - void* ptr = 0; - Base::Interpreter().convertSWIGPointerObj("pivy.coin", "SoDetail *", det.ptr(), &ptr, 0); - SoDetail* detail = reinterpret_cast(ptr); - return detail ? detail->copy() : 0; - } - } + Py::Tuple args(1); + args.setItem(0, Py::String(name)); + Py::Object det(Base::pyCall(py_getDetail.ptr(),args.ptr())); + void* ptr = 0; + Base::Interpreter().convertSWIGPointerObj("pivy.coin", "SoDetail *", det.ptr(), &ptr, 0); + SoDetail* detail = reinterpret_cast(ptr); + return detail ? detail->copy() : 0; } catch (const Base::Exception& e) { e.ReportException(); @@ -411,6 +495,46 @@ SoDetail* ViewProviderPythonFeatureImp::getDetail(const char* name) const return 0; } +ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::getDetailPath( + const char* name, SoFullPath *path, bool append, SoDetail *&det) const +{ + FC_PY_CALL_CHECK(getDetailPath); + + Base::PyGILStateLocker lock; + auto length = path->getLength(); + try { + PyObject* pivy = 0; + pivy = Base::Interpreter().createSWIGPointerObj("pivy.coin", "SoFullPath *", (void*)path, 1); + path->ref(); + Py::Tuple args(3); + args.setItem(0, Py::String(name)); + args.setItem(1, Py::Object(pivy, true)); + args.setItem(2, Py::Boolean(append)); + Py::Object pyDet(Base::pyCall(py_getDetailPath.ptr(),args.ptr())); + if(!pyDet.isTrue()) + return Rejected; + if(pyDet.isBoolean()) + return Accepted; + void* ptr = 0; + Base::Interpreter().convertSWIGPointerObj("pivy.coin", "SoDetail *", pyDet.ptr(), &ptr, 0); + SoDetail* detail = reinterpret_cast(ptr); + det = detail ? detail->copy() : 0; + if(det) + return Accepted; + delete det; + } + catch (const Base::Exception& e) { + e.ReportException(); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + path->truncate(length); + return Rejected; +} + + std::vector ViewProviderPythonFeatureImp::getSelectionShape(const char* /*Element*/) const { return std::vector(); @@ -419,37 +543,30 @@ std::vector ViewProviderPythonFeatureImp::getSelectionShape(cons ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::setEdit(int ModNum) { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(setEdit) + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("setEdit"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("setEdit"))); - Py::Tuple args(1); - args.setItem(0, Py::Int(ModNum)); - Py::Object ret(method.apply(args)); - if (ret.isNone()) - return NotImplemented; - Py::Boolean ok(ret); - bool value = static_cast(ok); - return value ? Accepted : Rejected; - } - else { - Py::Callable method(vp.getAttr(std::string("setEdit"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - args.setItem(1, Py::Int(ModNum)); - Py::Object ret(method.apply(args)); - if (ret.isNone()) - return NotImplemented; - Py::Boolean ok(ret); - bool value = static_cast(ok); - return value ? Accepted : Rejected; - } - } + if (has__object__) { + Py::Tuple args(1); + args.setItem(0, Py::Int(ModNum)); + Py::Object ret(Base::pyCall(py_setEdit.ptr(),args.ptr())); + if (ret.isNone()) + return NotImplemented; + Py::Boolean ok(ret); + bool value = static_cast(ok); + return value ? Accepted : Rejected; + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, Py::Int(ModNum)); + Py::Object ret(Base::pyCall(py_setEdit.ptr(),args.ptr())); + if (ret.isNone()) + return NotImplemented; + Py::Boolean ok(ret); + bool value = static_cast(ok); + return value ? Accepted : Rejected; } } catch (Py::Exception&) { @@ -475,37 +592,30 @@ ViewProviderPythonFeatureImp::setEdit(int ModNum) ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::unsetEdit(int ModNum) { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(unsetEdit) + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("unsetEdit"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("unsetEdit"))); - Py::Tuple args(1); - args.setItem(0, Py::Int(ModNum)); - Py::Object ret(method.apply(args)); - if (ret.isNone()) - return NotImplemented; - Py::Boolean ok(ret); - bool value = static_cast(ok); - return value ? Accepted : Rejected; - } - else { - Py::Callable method(vp.getAttr(std::string("unsetEdit"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - args.setItem(1, Py::Int(ModNum)); - Py::Object ret(method.apply(args)); - if (ret.isNone()) - return NotImplemented; - Py::Boolean ok(ret); - bool value = static_cast(ok); - return value ? Accepted : Rejected; - } - } + if (has__object__) { + Py::Tuple args(1); + args.setItem(0, Py::Int(ModNum)); + Py::Object ret(Base::pyCall(py_unsetEdit.ptr(),args.ptr())); + if (ret.isNone()) + return NotImplemented; + Py::Boolean ok(ret); + bool value = static_cast(ok); + return value ? Accepted : Rejected; + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, Py::Int(ModNum)); + Py::Object ret(Base::pyCall(py_unsetEdit.ptr(),args.ptr())); + if (ret.isNone()) + return NotImplemented; + Py::Boolean ok(ret); + bool value = static_cast(ok); + return value ? Accepted : Rejected; } } catch (Py::Exception&) { @@ -528,33 +638,65 @@ ViewProviderPythonFeatureImp::unsetEdit(int ModNum) return NotImplemented; } -ViewProviderPythonFeatureImp::ValueT -ViewProviderPythonFeatureImp::doubleClicked(void) +bool ViewProviderPythonFeatureImp::setEditViewer(View3DInventorViewer *viewer, int ModNum) { + _FC_PY_CALL_CHECK(setEditViewer,return(false)) + + Base::PyGILStateLocker lock; + try { + Py::Tuple args(3); + args.setItem(0, Py::Object(object->getPyObject(),true)); + args.setItem(1, Py::Object(viewer->getPyObject(),true)); + args.setItem(2, Py::Int(ModNum)); + Py::Object ret(Base::pyCall(py_setEditViewer.ptr(),args.ptr())); + return ret.isTrue(); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return false; +} + +bool ViewProviderPythonFeatureImp::unsetEditViewer(View3DInventorViewer *viewer) +{ + _FC_PY_CALL_CHECK(unsetEditViewer,return(false)) + // Run the onChanged method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("doubleClicked"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("doubleClicked"))); - Py::Tuple args; - //args.setItem(0, Py::Int(ModNum)); - Py::Boolean ok(method.apply(args)); - bool value = (bool)ok; - return value ? Accepted : Rejected; - } - else { - Py::Callable method(vp.getAttr(std::string("doubleClicked"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(object->getPyObject(), true)); - Py::Boolean ok(method.apply(args)); - bool value = (bool)ok; - return value ? Accepted : Rejected; - } - } + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(),true)); + args.setItem(1, Py::Object(viewer->getPyObject(),true)); + Py::Object ret(Base::pyCall(py_unsetEditViewer.ptr(),args.ptr())); + return ret.isTrue(); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return false; +} + +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::doubleClicked(void) +{ + FC_PY_CALL_CHECK(doubleClicked) + + // Run the onChanged method of the proxy object. + Base::PyGILStateLocker lock; + try { + if (has__object__) { + Py::Boolean ok(Base::pyCall(py_doubleClicked.ptr())); + bool value = (bool)ok; + return value ? Accepted : Rejected; + } + else { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Boolean ok(Base::pyCall(py_doubleClicked.ptr(),args.ptr())); + bool value = (bool)ok; + return value ? Accepted : Rejected; } } catch (Py::Exception&) { @@ -567,33 +709,27 @@ ViewProviderPythonFeatureImp::doubleClicked(void) void ViewProviderPythonFeatureImp::setupContextMenu(QMenu* menu) { + _FC_PY_CALL_CHECK(setupContextMenu,return); + // Run the attach method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("setupContextMenu"))) { - if (vp.hasAttr("__object__")) { - PythonWrapper wrap; - wrap.loadGuiModule(); - wrap.loadWidgetsModule(); - Py::Callable method(vp.getAttr(std::string("setupContextMenu"))); - Py::Tuple args(1); - args.setItem(0, wrap.fromQWidget(menu, "QMenu")); - method.apply(args); - } - else { - PythonWrapper wrap; - wrap.loadGuiModule(); - wrap.loadWidgetsModule(); - Py::Callable method(vp.getAttr(std::string("setupContextMenu"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - args.setItem(1, wrap.fromQWidget(menu, "QMenu")); - method.apply(args); - } - } + if (has__object__) { + PythonWrapper wrap; + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + Py::Tuple args(1); + args.setItem(0, wrap.fromQWidget(menu, "QMenu")); + Base::pyCall(py_setupContextMenu.ptr(),args.ptr()); + } + else { + PythonWrapper wrap; + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, wrap.fromQWidget(menu, "QMenu")); + Base::pyCall(py_setupContextMenu.ptr(),args.ptr()); } } catch (Py::Exception&) { @@ -604,30 +740,23 @@ void ViewProviderPythonFeatureImp::setupContextMenu(QMenu* menu) void ViewProviderPythonFeatureImp::attach(App::DocumentObject *pcObject) { + _FC_PY_CALL_CHECK(attach,return); + // Run the attach method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("attach"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("attach"))); - Py::Tuple args; - method.apply(args); - } - else { - Py::Callable method(vp.getAttr(std::string("attach"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(object->getPyObject(), true)); - method.apply(args); - } - - // #0000415: Now simulate a property change event to call - // claimChildren if implemented. - pcObject->Label.touch(); - } + if (has__object__) { + Base::pyCall(py_attach.ptr()); } + else { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Base::pyCall(py_attach.ptr(),args.ptr()); + } + + // #0000415: Now simulate a property change event to call + // claimChildren if implemented. + pcObject->Label.touch(); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -637,32 +766,27 @@ void ViewProviderPythonFeatureImp::attach(App::DocumentObject *pcObject) void ViewProviderPythonFeatureImp::updateData(const App::Property* prop) { + if(py_updateData.isNone()) + return; + // Run the updateData method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("updateData"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("updateData"))); - Py::Tuple args(1); - const char* prop_name = object->getObject()->getPropertyName(prop); - if (prop_name) { - args.setItem(0, Py::String(prop_name)); - method.apply(args); - } - } - else { - Py::Callable method(vp.getAttr(std::string("updateData"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getObject()->getPyObject(), true)); - const char* prop_name = object->getObject()->getPropertyName(prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } - } + if (has__object__) { + Py::Tuple args(1); + const char* prop_name = object->getObject()->getPropertyName(prop); + if (prop_name) { + args.setItem(0, Py::String(prop_name)); + Base::pyCall(py_updateData.ptr(),args.ptr()); + } + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getObject()->getPyObject(), true)); + const char* prop_name = object->getObject()->getPropertyName(prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(py_updateData.ptr(),args.ptr()); } } } @@ -674,32 +798,27 @@ void ViewProviderPythonFeatureImp::updateData(const App::Property* prop) void ViewProviderPythonFeatureImp::onChanged(const App::Property* prop) { + if(py_onChanged.isNone()) + return; + // Run the onChanged method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("onChanged"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("onChanged"))); - Py::Tuple args(1); - const char* prop_name = object->getPropertyName(prop); - if (prop_name) { - args.setItem(0, Py::String(prop_name)); - method.apply(args); - } - } - else { - Py::Callable method(vp.getAttr(std::string("onChanged"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - const char* prop_name = object->getPropertyName(prop); - if (prop_name) { - args.setItem(1, Py::String(prop_name)); - method.apply(args); - } - } + if (has__object__) { + Py::Tuple args(1); + const char* prop_name = object->getPropertyName(prop); + if (prop_name) { + args.setItem(0, Py::String(prop_name)); + Base::pyCall(py_onChanged.ptr(),args.ptr()); + } + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + const char* prop_name = object->getPropertyName(prop); + if (prop_name) { + args.setItem(1, Py::String(prop_name)); + Base::pyCall(py_onChanged.ptr(),args.ptr()); } } } @@ -715,46 +834,46 @@ void ViewProviderPythonFeatureImp::startRestoring() void ViewProviderPythonFeatureImp::finishRestoring() { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.ptr() == Py::_None()) { + Base::PyGILStateLocker lock; + try { + Py::Object vp = Proxy.getValue(); + if (vp.isNone()) { object->show(); - static_cast(proxy)->setValue(Py::Int(1)); + Proxy.setValue(Py::Int(1)); + } else { + _FC_PY_CALL_CHECK(finishRestoring,return); + Base::pyCall(py_finishRestoring.ptr()); } + }catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); } } bool ViewProviderPythonFeatureImp::onDelete(const std::vector & sub) { + _FC_PY_CALL_CHECK(onDelete,return(true)); + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("onDelete"))) { - Py::Tuple seq(sub.size()); - int index=0; - for (std::vector::const_iterator it = sub.begin(); it != sub.end(); ++it) { - seq.setItem(index++, Py::String(*it)); - } + Py::Tuple seq(sub.size()); + int index=0; + for (std::vector::const_iterator it = sub.begin(); it != sub.end(); ++it) { + seq.setItem(index++, Py::String(*it)); + } - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("onDelete"))); - Py::Tuple args(1); - args.setItem(0, seq); - Py::Boolean ok(method.apply(args)); - return (bool)ok; - } - else { - Py::Callable method(vp.getAttr(std::string("onDelete"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - args.setItem(1, seq); - Py::Boolean ok(method.apply(args)); - return (bool)ok; - } - } + if (has__object__) { + Py::Tuple args(1); + args.setItem(0, seq); + Py::Boolean ok(Base::pyCall(py_onDelete.ptr(),args.ptr())); + return (bool)ok; + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, seq); + Py::Boolean ok(Base::pyCall(py_onDelete.ptr(),args.ptr())); + return (bool)ok; } } catch (Py::Exception&) { @@ -765,25 +884,52 @@ bool ViewProviderPythonFeatureImp::onDelete(const std::vector & sub return true; } +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::canDelete(App::DocumentObject *obj) const +{ + FC_PY_CALL_CHECK(canDelete); + + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0,obj?Py::Object(obj->getPyObject(),true):Py::Object()); + return Py::Boolean(Base::pyCall(py_canDelete.ptr(),args.ptr()))?Accepted:Rejected; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + return Rejected; + } +} + +bool ViewProviderPythonFeatureImp::canAddToSceneGraph() const +{ + _FC_PY_CALL_CHECK(canAddToSceneGraph,return(true)); + + Base::PyGILStateLocker lock; + try { + return Py::Boolean(Py::Callable(py_canAddToSceneGraph).apply(Py::Tuple())); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return true; +} + const char* ViewProviderPythonFeatureImp::getDefaultDisplayMode() const { + _FC_PY_CALL_CHECK(getDefaultDisplayMode,return(0)); + // Run the getDefaultDisplayMode method of the proxy object. Base::PyGILStateLocker lock; static std::string mode; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("getDefaultDisplayMode"))) { - Py::Callable method(vp.getAttr(std::string("getDefaultDisplayMode"))); - Py::Tuple args; - Py::String str(method.apply(args)); - //if (str.isUnicode()) - // str = str.encode("ascii"); // json converts strings into unicode - mode = str.as_std_string("ascii"); - return mode.c_str(); - } - } + Py::String str(Base::pyCall(py_getDefaultDisplayMode.ptr())); + //if (str.isUnicode()) + // str = str.encode("ascii"); // json converts strings into unicode + mode = str.as_std_string("ascii"); + return mode.c_str(); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -795,33 +941,26 @@ const char* ViewProviderPythonFeatureImp::getDefaultDisplayMode() const std::vector ViewProviderPythonFeatureImp::getDisplayModes(void) const { + std::vector modes; + _FC_PY_CALL_CHECK(getDisplayModes,return(modes)); + // Run the getDisplayModes method of the proxy object. Base::PyGILStateLocker lock; - std::vector modes; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("getDisplayModes"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("getDisplayModes"))); - Py::Tuple args; - Py::Sequence list(method.apply(args)); - for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { - Py::String str(*it); - modes.push_back(str.as_std_string("ascii")); - } - } - else { - Py::Callable method(vp.getAttr(std::string("getDisplayModes"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(object->getPyObject(), true)); - Py::Sequence list(method.apply(args)); - for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { - Py::String str(*it); - modes.push_back(str.as_std_string("ascii")); - } - } + if (has__object__) { + Py::Sequence list(Base::pyCall(py_getDisplayModes.ptr())); + for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { + Py::String str(*it); + modes.push_back(str.as_std_string("ascii")); + } + } + else { + Py::Tuple args(1); + args.setItem(0, Py::Object(object->getPyObject(), true)); + Py::Sequence list(Base::pyCall(py_getDisplayModes.ptr(),args.ptr())); + for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { + Py::String str(*it); + modes.push_back(str.as_std_string("ascii")); } } } @@ -835,20 +974,15 @@ std::vector ViewProviderPythonFeatureImp::getDisplayModes(void) con std::string ViewProviderPythonFeatureImp::setDisplayMode(const char* ModeName) { + _FC_PY_CALL_CHECK(setDisplayMode,return(ModeName)); + // Run the setDisplayMode method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("setDisplayMode"))) { - Py::Callable method(vp.getAttr(std::string("setDisplayMode"))); - Py::Tuple args(1); - args.setItem(0, Py::String(ModeName)); - Py::String str(method.apply(args)); - return str.as_std_string("ascii"); - } - } + Py::Tuple args(1); + args.setItem(0, Py::String(ModeName)); + Py::String str(Base::pyCall(py_setDisplayMode.ptr(),args.ptr())); + return str.as_std_string("ascii"); } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -861,22 +995,12 @@ std::string ViewProviderPythonFeatureImp::setDisplayMode(const char* ModeName) ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::canDragObjects() const { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(canDragObjects); + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("canDragObjects"))) { - Py::Callable method(vp.getAttr(std::string("canDragObjects"))); - Py::Tuple args; - Py::Boolean ok(method.apply(args)); - return static_cast(ok) ? Accepted : Rejected; - } - else { - return NotImplemented; - } - } + Py::Boolean ok(Base::pyCall(py_canDragObjects.ptr())); + return static_cast(ok) ? Accepted : Rejected; } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -889,23 +1013,14 @@ ViewProviderPythonFeatureImp::canDragObjects() const ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::canDragObject(App::DocumentObject* obj) const { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(canDragObject); + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("canDragObject"))) { - Py::Callable method(vp.getAttr(std::string("canDragObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(obj->getPyObject(), true)); - Py::Boolean ok(method.apply(args)); - return static_cast(ok) ? Accepted : Rejected; - } - else { - return NotImplemented; - } - } + Py::Tuple args(1); + args.setItem(0, Py::Object(obj->getPyObject(), true)); + Py::Boolean ok(Base::pyCall(py_canDragObject.ptr(),args.ptr())); + return static_cast(ok) ? Accepted : Rejected; } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -918,32 +1033,23 @@ ViewProviderPythonFeatureImp::canDragObject(App::DocumentObject* obj) const ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::dragObject(App::DocumentObject* obj) { + FC_PY_CALL_CHECK(dragObject); + // Run the onChanged method of the proxy object. Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("dragObject"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("dragObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(obj->getPyObject(), true)); - method.apply(args); - return Accepted; - } - else { - Py::Callable method(vp.getAttr(std::string("dragObject"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - args.setItem(1, Py::Object(obj->getPyObject(), true)); - method.apply(args); - return Accepted; - } - } - else { - return NotImplemented; - } + if (has__object__) { + Py::Tuple args(1); + args.setItem(0, Py::Object(obj->getPyObject(), true)); + Base::pyCall(py_dragObject.ptr(),args.ptr()); + return Accepted; + } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, Py::Object(obj->getPyObject(), true)); + Base::pyCall(py_dragObject.ptr(),args.ptr()); + return Accepted; } } catch (Py::Exception&) { @@ -957,22 +1063,12 @@ ViewProviderPythonFeatureImp::dragObject(App::DocumentObject* obj) ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::canDropObjects() const { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(canDropObjects); + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("canDropObjects"))) { - Py::Callable method(vp.getAttr(std::string("canDropObjects"))); - Py::Tuple args; - Py::Boolean ok(method.apply(args)); - return static_cast(ok) ? Accepted : Rejected; - } - else { - return NotImplemented; - } - } + Py::Boolean ok(Base::pyCall(py_canDropObjects.ptr())); + return static_cast(ok) ? Accepted : Rejected; } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -985,23 +1081,14 @@ ViewProviderPythonFeatureImp::canDropObjects() const ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::canDropObject(App::DocumentObject* obj) const { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(canDropObject); + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("canDropObject"))) { - Py::Callable method(vp.getAttr(std::string("canDropObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(obj->getPyObject(), true)); - Py::Boolean ok(method.apply(args)); - return static_cast(ok) ? Accepted : Rejected; - } - else { - return NotImplemented; - } - } + Py::Tuple args(1); + args.setItem(0, Py::Object(obj->getPyObject(), true)); + Py::Boolean ok(Base::pyCall(py_canDropObject.ptr(),args.ptr())); + return static_cast(ok) ? Accepted : Rejected; } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -1014,33 +1101,43 @@ ViewProviderPythonFeatureImp::canDropObject(App::DocumentObject* obj) const ViewProviderPythonFeatureImp::ValueT ViewProviderPythonFeatureImp::dropObject(App::DocumentObject* obj) { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(dropObject); + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("dropObject"))) { - if (vp.hasAttr("__object__")) { - Py::Callable method(vp.getAttr(std::string("dropObject"))); - Py::Tuple args(1); - args.setItem(0, Py::Object(obj->getPyObject(), true)); - method.apply(args); - return Accepted; - } - else { - Py::Callable method(vp.getAttr(std::string("dropObject"))); - Py::Tuple args(2); - args.setItem(0, Py::Object(object->getPyObject(), true)); - args.setItem(1, Py::Object(obj->getPyObject(), true)); - method.apply(args); - return Accepted; - } - } - else { - return NotImplemented; - } + if (has__object__) { + Py::Tuple args(1); + args.setItem(0, Py::Object(obj->getPyObject(), true)); + Base::pyCall(py_dropObject.ptr(),args.ptr()); + return Accepted; } + else { + Py::Tuple args(2); + args.setItem(0, Py::Object(object->getPyObject(), true)); + args.setItem(1, Py::Object(obj->getPyObject(), true)); + Base::pyCall(py_dropObject.ptr(),args.ptr()); + return Accepted; + } + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + throw e; + } + + return Rejected; +} + +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::canDragAndDropObject(App::DocumentObject *obj) const +{ + FC_PY_CALL_CHECK(canDragAndDropObject); + + Base::PyGILStateLocker lock; + try { + Py::TupleN args(Py::Object(obj->getPyObject(),true)); + Py::Boolean ok(Base::pyCall(py_canDragAndDropObject.ptr(),args.ptr())); + return static_cast(ok) ? Accepted : Rejected; } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -1050,21 +1147,71 @@ ViewProviderPythonFeatureImp::dropObject(App::DocumentObject* obj) return Rejected; } -bool ViewProviderPythonFeatureImp::isShow() const +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::canDropObjectEx(App::DocumentObject* obj, + App::DocumentObject *owner, const char *subname, const std::vector &elements) const { - // Run the onChanged method of the proxy object. + FC_PY_CALL_CHECK(canDropObjectEx); + Base::PyGILStateLocker lock; try { - App::Property* proxy = object->getPropertyByName("Proxy"); - if (proxy && proxy->getTypeId() == App::PropertyPythonObject::getClassTypeId()) { - Py::Object vp = static_cast(proxy)->getValue(); - if (vp.hasAttr(std::string("isShow"))) { - Py::Callable method(vp.getAttr(std::string("isShow"))); - Py::Tuple args; - Py::Boolean ok(method.apply(args)); - return static_cast(ok) ? true : false; - } - } + Py::Tuple args(4); + args.setItem(0, Py::Object(obj->getPyObject(), true)); + args.setItem(1, owner?Py::Object(owner->getPyObject(), true):Py::None()); + args.setItem(2, Py::String(subname?subname:"")); + Py::Tuple tuple(elements.size()); + int i=0; + for(auto &element : elements) + tuple.setItem(i++,Py::String(element)); + args.setItem(3, tuple); + Py::Boolean ok(Base::pyCall(py_canDropObjectEx.ptr(),args.ptr())); + return static_cast(ok) ? Accepted : Rejected; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + + return Rejected; +} + +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::dropObjectEx(App::DocumentObject* obj, App::DocumentObject *owner, + const char *subname, const std::vector &elements,std::string &ret) +{ + FC_PY_CALL_CHECK(dropObjectEx); + + Base::PyGILStateLocker lock; + try { + Py::Tuple tuple(elements.size()); + int i=0; + for(auto &element : elements) + tuple.setItem(i++,Py::String(element)); + Py::Object res; + Py::TupleN args( + Py::Object(object->getPyObject(),true), + Py::Object(obj->getPyObject(),true), + owner?Py::Object(owner->getPyObject(),true):Py::Object(), + Py::String(subname?subname:""),tuple); + res = Base::pyCall(py_dropObjectEx.ptr(),args.ptr()); + if(!res.isNone()) + ret = res.as_string(); + return Accepted; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + throw e; + } +} + +bool ViewProviderPythonFeatureImp::isShow() const +{ + _FC_PY_CALL_CHECK(isShow,return(false)); + + Base::PyGILStateLocker lock; + try { + Py::Boolean ok(Base::pyCall(py_isShow.ptr())); + return static_cast(ok) ? true : false; } catch (Py::Exception&) { Base::PyException e; // extract the Python error text @@ -1075,6 +1222,93 @@ bool ViewProviderPythonFeatureImp::isShow() const } +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::canRemoveChildrenFromRoot() const { + + FC_PY_CALL_CHECK(canRemoveChildrenFromRoot); + + Base::PyGILStateLocker lock; + try { + Py::Boolean ok(Base::pyCall(py_canRemoveChildrenFromRoot.ptr())); + return static_cast(ok) ? Accepted : Rejected; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return Rejected; +} + +bool ViewProviderPythonFeatureImp::getDropPrefix(std::string &prefix) const { + + _FC_PY_CALL_CHECK(getDropPrefix,return(false)); + + Base::PyGILStateLocker lock; + try { + Py::Object ret(Base::pyCall(py_getDropPrefix.ptr())); + if(ret.isNone()) + return false; + prefix = ret.as_string(); + return true; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return false; +} + +ViewProviderPythonFeatureImp::ValueT +ViewProviderPythonFeatureImp::replaceObject( + App::DocumentObject *oldObj, App::DocumentObject *newObj) +{ + if(!oldObj || !oldObj->getNameInDocument() + || !newObj || !newObj->getNameInDocument()) + return NotImplemented; + + FC_PY_CALL_CHECK(replaceObject); + + Base::PyGILStateLocker lock; + try { + Py::TupleN args(Py::asObject(oldObj->getPyObject()), + Py::asObject(newObj->getPyObject())); + Py::Boolean ok(Base::pyCall(py_replaceObject.ptr(),args.ptr())); + return ok ? Accepted : Rejected; + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return Rejected; +} + +ViewProviderDocumentObject * +ViewProviderPythonFeatureImp::getLinkedViewProvider(bool recursive) const +{ + _FC_PY_CALL_CHECK(getLinkedViewProvider,return(0)); + + Base::PyGILStateLocker lock; + try { + Py::Tuple args(1); + args.setItem(0,Py::Boolean(recursive)); + Py::Object res(Base::pyCall(py_getLinkedViewProvider.ptr(),args.ptr())); + if(res.isNone()) + return 0; + if(PyObject_TypeCheck(res.ptr(),&ViewProviderDocumentObjectPy::Type)) + return static_cast( + res.ptr())->getViewProviderDocumentObjectPtr(); + + FC_ERR("getLinkedViewProvider(): invalid return object type '" + << res.ptr()->ob_type->tp_name << "'"); + } + catch (Py::Exception&) { + Base::PyException e; // extract the Python error text + e.ReportException(); + } + return 0; +} + + // --------------------------------------------------------- namespace Gui { diff --git a/src/Gui/ViewProviderPythonFeature.h b/src/Gui/ViewProviderPythonFeature.h index 59d9cbade0..ebde3980a1 100644 --- a/src/Gui/ViewProviderPythonFeature.h +++ b/src/Gui/ViewProviderPythonFeature.h @@ -24,9 +24,12 @@ #ifndef GUI_VIEWPROVIDERPYTHONFEATURE_H #define GUI_VIEWPROVIDERPYTHONFEATURE_H +#include #include +#include #include #include +#include class SoSensor; class SoDragger; @@ -44,19 +47,23 @@ public: }; /// constructor. - ViewProviderPythonFeatureImp(ViewProviderDocumentObject*); + ViewProviderPythonFeatureImp(ViewProviderDocumentObject*, App::PropertyPythonObject &); /// destructor. ~ViewProviderPythonFeatureImp(); // Returns the icon QIcon getIcon() const; - std::vector claimChildren(const std::vector&) const; + std::vector claimChildren(std::vector&&) const; bool useNewSelectionModel() const; + ValueT getElementPicked(const SoPickedPoint *pp, std::string &subname) const; std::string getElement(const SoDetail *det) const; SoDetail* getDetail(const char*) const; + ValueT getDetailPath(const char *name, SoFullPath *path, bool append, SoDetail *&det) const; std::vector getSelectionShape(const char* Element) const; ValueT setEdit(int ModNum); ValueT unsetEdit(int ModNum); + bool setEditViewer(View3DInventorViewer*, int ModNum); + bool unsetEditViewer(View3DInventorViewer*); ValueT doubleClicked(void); void setupContextMenu(QMenu* menu); @@ -68,6 +75,7 @@ public: void startRestoring(); void finishRestoring(); bool onDelete(const std::vector & sub); + ValueT canDelete(App::DocumentObject *obj) const; //@} /** @name Display methods */ @@ -82,6 +90,8 @@ public: std::string setDisplayMode(const char* ModeName); //@} + ValueT canRemoveChildrenFromRoot() const; + /** @name Drag and drop */ //@{ /// Returns true if the view provider generally supports dragging objects @@ -96,10 +106,86 @@ public: ValueT canDropObject(App::DocumentObject*) const; /// If the dropped object type is accepted the object will be added as child ValueT dropObject(App::DocumentObject*); + /** Return false to force drop only operation for a give object*/ + ValueT canDragAndDropObject(App::DocumentObject*) const; + /** Query object dropping with full quanlified name */ + ValueT canDropObjectEx(App::DocumentObject *obj, App::DocumentObject *, + const char *,const std::vector &elements) const; + /** Add an object with full quanlified name to the view provider by drag and drop */ + ValueT dropObjectEx(App::DocumentObject *obj, App::DocumentObject *, + const char *, const std::vector &elements, std::string &ret); + ValueT replaceObject(App::DocumentObject *, App::DocumentObject *); //@} + ViewProviderDocumentObject *getLinkedViewProvider(bool recursive) const; + + bool canAddToSceneGraph() const; + + bool getDropPrefix(std::string &prefix) const; + private: ViewProviderDocumentObject* object; + App::PropertyPythonObject &Proxy; + bool has__object__; + +#define FC_PY_VIEW_OBJECT \ + FC_PY_ELEMENT(getIcon) \ + FC_PY_ELEMENT(claimChildren) \ + FC_PY_ELEMENT(useNewSelectionModel) \ + FC_PY_ELEMENT(getElementPicked) \ + FC_PY_ELEMENT(getElement) \ + FC_PY_ELEMENT(getDetail) \ + FC_PY_ELEMENT(getDetailPath) \ + FC_PY_ELEMENT(getSelectionShape) \ + FC_PY_ELEMENT(setEdit) \ + FC_PY_ELEMENT(unsetEdit) \ + FC_PY_ELEMENT(setEditViewer) \ + FC_PY_ELEMENT(unsetEditViewer) \ + FC_PY_ELEMENT(doubleClicked) \ + FC_PY_ELEMENT(setupContextMenu) \ + FC_PY_ELEMENT(attach) \ + FC_PY_ELEMENT(updateData) \ + FC_PY_ELEMENT(onChanged) \ + FC_PY_ELEMENT(startRestoring) \ + FC_PY_ELEMENT(finishRestoring) \ + FC_PY_ELEMENT(onDelete) \ + FC_PY_ELEMENT(canDelete) \ + FC_PY_ELEMENT(isShow) \ + FC_PY_ELEMENT(getDefaultDisplayMode) \ + FC_PY_ELEMENT(getDisplayModes) \ + FC_PY_ELEMENT(setDisplayMode) \ + FC_PY_ELEMENT(canRemoveChildrenFromRoot) \ + FC_PY_ELEMENT(canDragObjects) \ + FC_PY_ELEMENT(canDragObject) \ + FC_PY_ELEMENT(dragObject) \ + FC_PY_ELEMENT(canDropObjects) \ + FC_PY_ELEMENT(canDropObject) \ + FC_PY_ELEMENT(dropObject) \ + FC_PY_ELEMENT(canDragAndDropObject) \ + FC_PY_ELEMENT(canDropObjectEx) \ + FC_PY_ELEMENT(dropObjectEx) \ + FC_PY_ELEMENT(canAddToSceneGraph) \ + FC_PY_ELEMENT(getDropPrefix) \ + FC_PY_ELEMENT(replaceObject) \ + FC_PY_ELEMENT(getLinkedViewProvider) \ + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_DEFINE(_name) + + FC_PY_VIEW_OBJECT + +#undef FC_PY_ELEMENT +#define FC_PY_ELEMENT(_name) FC_PY_ELEMENT_FLAG(_name) + + enum Flag { + FC_PY_VIEW_OBJECT + FlagMax, + }; + typedef std::bitset Flags; + mutable Flags _Flags; + +public: + void init(PyObject *pyobj); }; template @@ -111,7 +197,7 @@ public: /// constructor. ViewProviderPythonFeatureT() : _attached(false) { ADD_PROPERTY(Proxy,(Py::Object())); - imp = new ViewProviderPythonFeatureImp(this); + imp = new ViewProviderPythonFeatureImp(this,Proxy); } /// destructor. virtual ~ViewProviderPythonFeatureT() { @@ -149,6 +235,14 @@ public: virtual bool useNewSelectionModel() const { return imp->useNewSelectionModel(); } + virtual bool getElementPicked(const SoPickedPoint *pp, std::string &subname) const { + auto ret = imp->getElementPicked(pp,subname); + if(ret == ViewProviderPythonFeatureImp::NotImplemented) + return ViewProviderT::getElementPicked(pp,subname); + else if(ret == ViewProviderPythonFeatureImp::Accepted) + return true; + return false; + } virtual std::string getElement(const SoDetail *det) const { std::string name = imp->getElement(det); if (!name.empty()) return name; @@ -159,6 +253,12 @@ public: if (det) return det; return ViewProviderT::getDetail(name); } + virtual bool getDetailPath(const char *name, SoFullPath *path, bool append,SoDetail *&det) const { + auto ret = imp->getDetailPath(name,path,append,det); + if(ret == ViewProviderPythonFeatureImp::NotImplemented) + return ViewProviderT::getDetailPath(name,path,append,det); + return ret == ViewProviderPythonFeatureImp::Accepted; + } virtual std::vector getSelectionShape(const char* Element) const { return ViewProviderT::getSelectionShape(Element); }; @@ -183,15 +283,27 @@ public: if (!ok) return ok; return ViewProviderT::onDelete(sub); } + virtual bool canDelete(App::DocumentObject *obj) const override { + switch(imp->canDelete(obj)) { + case ViewProviderPythonFeatureImp::Accepted: + return true; + case ViewProviderPythonFeatureImp::Rejected: + return false; + default: + return ViewProviderT::canDelete(obj); + } + } //@} /** @name Restoring view provider from document load */ //@{ virtual void startRestoring() { + ViewProviderT::startRestoring(); imp->startRestoring(); } virtual void finishRestoring() { imp->finishRestoring(); + ViewProviderT::finishRestoring(); } //@} @@ -221,6 +333,7 @@ public: } /// Starts to drag the object virtual void dragObject(App::DocumentObject* obj) { + App::AutoTransaction committer; switch (imp->dragObject(obj)) { case ViewProviderPythonFeatureImp::Accepted: case ViewProviderPythonFeatureImp::Rejected: @@ -253,6 +366,7 @@ public: } /// If the dropped object type is accepted the object will be added as child virtual void dropObject(App::DocumentObject* obj) { + App::AutoTransaction committer; switch (imp->dropObject(obj)) { case ViewProviderPythonFeatureImp::Accepted: case ViewProviderPythonFeatureImp::Rejected: @@ -261,6 +375,44 @@ public: return ViewProviderT::dropObject(obj); } } + /** Return false to force drop only operation for a give object*/ + virtual bool canDragAndDropObject(App::DocumentObject *obj) const override { + switch (imp->canDragAndDropObject(obj)) { + case ViewProviderPythonFeatureImp::Accepted: + return true; + case ViewProviderPythonFeatureImp::Rejected: + return false; + default: + return ViewProviderT::canDragAndDropObject(obj); + } + } + virtual bool canDropObjectEx(App::DocumentObject *obj, App::DocumentObject *owner, + const char *subname, const std::vector &elements) const override + { + switch (imp->canDropObjectEx(obj,owner,subname,elements)) { + case ViewProviderPythonFeatureImp::Accepted: + return true; + case ViewProviderPythonFeatureImp::Rejected: + return false; + default: + return ViewProviderT::canDropObjectEx(obj,owner,subname,elements); + } + } + /** Add an object with full quanlified name to the view provider by drag and drop */ + virtual std::string dropObjectEx(App::DocumentObject *obj, App::DocumentObject *owner, + const char *subname, const std::vector &elements) + { + App::AutoTransaction committer; + std::string ret; + switch (imp->dropObjectEx(obj,owner,subname,elements,ret)) { + case ViewProviderPythonFeatureImp::NotImplemented: + ret = ViewProviderT::dropObjectEx(obj,owner,subname,elements); + break; + default: + break; + } + return ret; + } //@} /** @name Display methods */ @@ -290,14 +442,29 @@ public: } //@} + virtual bool canRemoveChildrenFromRoot() const override { + switch(imp->canRemoveChildrenFromRoot()) { + case ViewProviderPythonFeatureImp::NotImplemented: + return ViewProviderT::canRemoveChildrenFromRoot(); + case ViewProviderPythonFeatureImp::Accepted: + return true; + default: + return false; + } + } PyObject* getPyObject() { return ViewProviderT::getPyObject(); } + virtual bool canAddToSceneGraph() const override { + return ViewProviderT::canAddToSceneGraph() && imp->canAddToSceneGraph(); + } + protected: virtual void onChanged(const App::Property* prop) { if (prop == &Proxy) { + imp->init(Proxy.getValue().ptr()); if (ViewProviderT::pcObject && !Proxy.getValue().is(Py::_None())) { if (!_attached) { _attached = true; @@ -307,6 +474,12 @@ protected: ViewProviderT::DisplayMode.touch(); ViewProviderT::setOverrideMode(viewerMode); } + if(!this->testStatus(Gui::isRestoring) && + ViewProviderT::canAddToSceneGraph() && + !imp->canAddToSceneGraph()) + { + this->getDocument()->toggleInSceneGraph(this); + } ViewProviderT::updateView(); } } @@ -338,6 +511,40 @@ protected: return ViewProviderT::unsetEdit(ModNum); } } + virtual void setEditViewer(View3DInventorViewer *viewer, int ModNum) { + if(!imp->setEditViewer(viewer,ModNum)) + ViewProviderT::setEditViewer(viewer,ModNum); + } + virtual void unsetEditViewer(View3DInventorViewer *viewer) { + if(!imp->unsetEditViewer(viewer)) + ViewProviderT::unsetEditViewer(viewer); + } + + virtual std::string getDropPrefix() const { + std::string prefix; + if(!imp->getDropPrefix(prefix)) + return ViewProviderT::getDropPrefix(); + return prefix; + } + + virtual int replaceObject(App::DocumentObject *oldObj, App::DocumentObject *newObj) { + App::AutoTransaction committer; + switch (imp->replaceObject(oldObj,newObj)) { + case ViewProviderPythonFeatureImp::Accepted: + return 1; + case ViewProviderPythonFeatureImp::Rejected: + return 0; + default: + return ViewProviderT::replaceObject(oldObj,newObj); + } + } + + virtual ViewProviderDocumentObject *getLinkedViewProvider(bool recursive=false) const { + auto res = imp->getLinkedViewProvider(recursive); + if(!res) + res = ViewProviderT::getLinkedViewProvider(); + return res; + } public: virtual void setupContextMenu(QMenu* menu, QObject* recipient, const char* member) @@ -349,6 +556,7 @@ public: protected: virtual bool doubleClicked(void) { + App::AutoTransaction committer; switch (imp->doubleClicked()) { case ViewProviderPythonFeatureImp::Accepted: return true; diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index 405604b953..4615a552b9 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -402,6 +402,23 @@ Py::Object PythonWrapper::fromQIcon(const QIcon* icon) throw Py::RuntimeError("Failed to wrap icon"); } +QIcon *PythonWrapper::toQIcon(PyObject *pyobj) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject * type = Shiboken::SbkType(); + if(type) { + if (Shiboken::Object::checkType(pyobj)) { + SbkObject* sbkobject = reinterpret_cast(pyobj); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + Q_UNUSED(pyobj); +#endif + return 0; +} + Py::Object PythonWrapper::fromQWidget(QWidget* widget, const char* className) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) diff --git a/src/Gui/WidgetFactory.h b/src/Gui/WidgetFactory.h index 811bd0e6f0..03f0dc095c 100644 --- a/src/Gui/WidgetFactory.h +++ b/src/Gui/WidgetFactory.h @@ -56,6 +56,7 @@ public: and the Python wrapper takes ownership of it. */ Py::Object fromQIcon(const QIcon*); + QIcon *toQIcon(PyObject *pyobj); static void createChildrenNameAttributes(PyObject* root, QObject* object); static void setParent(PyObject* pyWdg, QObject* parent); };