diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 9ac4874380..d41c47c18a 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -148,6 +148,7 @@ using namespace boost::program_options; # include #endif +FC_LOG_LEVEL_INIT("App",true,true); //using Base::GetConsole; using namespace Base; @@ -249,7 +250,7 @@ init_freecad_module(void) #endif Application::Application(std::map &mConfig) - : _mConfig(mConfig), _pActiveDoc(0) + : _mConfig(mConfig), _pActiveDoc(0), _objCount(-1) { //_hApp = new ApplicationOCC; mpcPramManager["System parameter"] = _pcSysParamMngr; @@ -481,6 +482,8 @@ bool Application::closeDocument(const char* name) std::unique_ptr delDoc (pos->second); DocMap.erase( pos ); + _objCount = -1; + // Trigger observers after removing the document from the internal map. signalDeletedDocument(); @@ -704,6 +707,51 @@ std::string Application::getHelpDir() #endif } +int Application::checkLinkDepth(int depth, bool no_throw) { + if(_objCount<0) { + _objCount = 0; + for(auto &v : DocMap) + _objCount += v.second->countObjects(); + } + if(depth > _objCount+2) { + const char *msg = "Link recursion limit reached. " + "Please check for cyclic reference."; + if(no_throw) { + FC_ERR(msg); + return 0; + }else + throw Base::RuntimeError(msg); + } + return _objCount+2; +} + +std::set Application::getLinksTo( + const DocumentObject *obj, int options, int maxCount) const +{ + std::set links; + if(!obj) { + for(auto &v : DocMap) { + v.second->getLinksTo(links,obj,options,maxCount); + if(maxCount && (int)links.size()>=maxCount) + break; + } + } else { + std::set docs; + for(auto o : obj->getInList()) { + if(o && o->getNameInDocument() && docs.insert(o->getDocument()).second) { + o->getDocument()->getLinksTo(links,obj,options,maxCount); + if(maxCount && (int)links.size()>=maxCount) + break; + } + } + } + return links; +} + +bool Application::hasLinksTo(const DocumentObject *obj) const { + return !getLinksTo(obj,0,1).empty(); +} + ParameterManager & Application::GetSystemParameter(void) { return *_pcSysParamMngr; @@ -1009,11 +1057,13 @@ void Application::slotChangedDocument(const App::Document& doc, const Property& void Application::slotNewObject(const App::DocumentObject&O) { this->signalNewObject(O); + _objCount = -1; } void Application::slotDeletedObject(const App::DocumentObject&O) { this->signalDeletedObject(O); + _objCount = -1; } void Application::slotBeforeChangeObject(const DocumentObject& O, const Property& Prop) diff --git a/src/App/Application.h b/src/App/Application.h index 68afef12a8..d7191dcbdc 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -48,6 +49,16 @@ class DocumentObject; class ApplicationObserver; class Property; +enum GetLinkOption { + /// Get all links (both directly and in directly) linked to the given object + GetLinkRecursive = 1, + /// Get link array element instead of the array + GetLinkArrayElement = 2, + /// Get linked object instead of the link, no effect if GetLinkRecursive + GetLinkedObject = 4, + /// Get only external links, no effect if GetLinkRecursive + GetLinkExternal = 8, +}; /** The Application @@ -124,6 +135,8 @@ public: boost::signals2::signal signalUndoDocument; /// signal on redo in document boost::signals2::signal signalRedoDocument; + /// signal on show hidden items + boost::signals2::signal signalShowHidden; //@} @@ -274,6 +287,34 @@ public: static std::string getHelpDir(); //@} + /** @name Link handling */ + //@{ + + /** Check for link recursion depth + * + * @param depth: current depth + * @param no_throw: whether to throw exception + * + * @return Return the maximum remaining depth. + * + * The function uses an internal count of all objects in all documents as + * the limit of recursion depth. + */ + int checkLinkDepth(int depth, bool no_throw=true); + + /** Return the links to a given object + * + * @param obj: the linked object. If NULL, then all links are returned. + * @param option: @sa App::GetLinkOptions + * @param maxCount: limit the number of links returned, 0 means no limit + */ + std::set getLinksTo( + const DocumentObject *, int options, int maxCount=0) const; + + /// Check if there is any link to the given object + bool hasLinksTo(const DocumentObject *obj) const; + //@} + friend class App::Document; protected: @@ -315,7 +356,6 @@ private: static ParameterManager *_pcUserParamMngr; //@} - //--------------------------------------------------------------------- // python exports goes here +++++++++++++++++++++++++++++++++++++++++++ //--------------------------------------------------------------------- @@ -353,7 +393,10 @@ private: static PyObject *sSetLogLevel (PyObject *self,PyObject *args); static PyObject *sGetLogLevel (PyObject *self,PyObject *args); - static PyMethodDef Methods[]; + static PyObject *sCheckLinkDepth (PyObject *self,PyObject *args); + static PyObject *sGetLinksTo (PyObject *self,PyObject *args); + + static PyMethodDef Methods[]; friend class ApplicationObserver; @@ -401,6 +444,9 @@ private: std::map &_mConfig; App::Document* _pActiveDoc; + // for estimate max link depth + int _objCount; + static Base::ConsoleObserverStd *_pConsoleObserverStd; static Base::ConsoleObserverFile *_pConsoleObserverFile; }; diff --git a/src/App/ApplicationPy.cpp b/src/App/ApplicationPy.cpp index 2ff5c20a6e..5fcaa6a2e1 100644 --- a/src/App/ApplicationPy.cpp +++ b/src/App/ApplicationPy.cpp @@ -35,6 +35,7 @@ #include "Document.h" #include "DocumentPy.h" #include "DocumentObserverPython.h" +#include "DocumentObjectPy.h" // FreeCAD Base header #include @@ -144,7 +145,12 @@ PyMethodDef Application::Methods[] = { "'level' can either be string 'Log', 'Msg', 'Wrn', 'Error', or an integer value"}, {"getLogLevel", (PyCFunction) Application::sGetLogLevel, METH_VARARGS, "getLogLevel(tag) -- Get the log level of a string tag"}, - + {"checkLinkDepth", (PyCFunction) Application::sCheckLinkDepth, METH_VARARGS, + "checkLinkDepth(depth) -- check link recursion depth"}, + {"getLinksTo", (PyCFunction) Application::sGetLinksTo, METH_VARARGS, + "getLinksTo(obj,options=0,maxCount=0) -- return the objects linked to 'obj'\n\n" + "options: 1: recursive, 2: check link array. Options can combine.\n" + "maxCount: to limit the number of links returned\n"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -763,4 +769,40 @@ PyObject *Application::sGetLogLevel(PyObject * /*self*/, PyObject *args) } PY_CATCH; } +PyObject *Application::sCheckLinkDepth(PyObject * /*self*/, PyObject *args) +{ + short depth = 0; + if (!PyArg_ParseTuple(args, "h", &depth)) + return NULL; + + PY_TRY { + return Py::new_reference_to(Py::Int(GetApplication().checkLinkDepth(depth,false))); + }PY_CATCH; +} + +PyObject *Application::sGetLinksTo(PyObject * /*self*/, PyObject *args) +{ + PyObject *pyobj = Py_None; + int options = 0; + short count = 0; + if (!PyArg_ParseTuple(args, "|Oih",&pyobj,&options, &count)) + return NULL; + + PY_TRY { + DocumentObject *obj = 0; + if(pyobj!=Py_None) { + if(!PyObject_TypeCheck(pyobj,&DocumentObjectPy::Type)) { + PyErr_SetString(PyExc_TypeError, "Expect the first argument of type document object"); + return 0; + } + obj = static_cast(pyobj)->getDocumentObjectPtr(); + } + auto links = GetApplication().getLinksTo(obj,options,count); + Py::Tuple ret(links.size()); + int i=0; + for(auto o : links) + ret.setItem(i++,Py::Object(o->getPyObject(),true)); + return Py::new_reference_to(ret); + }PY_CATCH; +} diff --git a/src/App/Document.cpp b/src/App/Document.cpp index b72e2bc34c..48c96ecf34 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -68,6 +68,8 @@ recompute path. Also, it enables more complicated dependencies beyond trees. # include #endif +#include + #include #include #include @@ -83,6 +85,7 @@ recompute path. Also, it enables more complicated dependencies beyond trees. #include #include #include +#include #include #include @@ -154,7 +157,9 @@ struct DocumentP { // Array to preserve the creation order of created objects std::vector objectArray; - std::map objectMap; + std::unordered_map objectMap; + std::unordered_map objectIdMap; + long lastObjectId; DocumentObject* activeObject; Transaction *activeUndoTransaction; int iTransactionMode; @@ -171,6 +176,13 @@ struct DocumentP #endif //USE_OLD_DAG DocumentP() { + static std::random_device _RD; + static std::mt19937 _RGEN(_RD()); + static std::uniform_int_distribution<> _RDIST(0,5000); + // Set some random offset to reduce likelyhood of ID collison when + // copying shape from other document. It is probably better to randomize + // on each object ID. + lastObjectId = _RDIST(_RGEN); activeObject = 0; activeUndoTransaction = 0; iTransactionMode = 0; @@ -218,7 +230,7 @@ void Document::writeDependencyGraphViz(std::ostream &out) out << "\tordering=out;" << endl; out << "\tnode [shape = box];" << endl; - for (std::map::const_iterator It = d->objectMap.begin(); It != d->objectMap.end();++It) { + for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) { out << "\t" << It->first << ";" < OutList = It->second->getOutList(); for (std::vector::const_iterator It2=OutList.begin();It2!=OutList.end();++It2) @@ -573,11 +585,11 @@ void Document::exportGraphviz(std::ostream& out) const } // Internal document objects - for (std::map::const_iterator It = d->objectMap.begin(); It != d->objectMap.end();++It) + for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) addExpressionSubgraphIfNeeded(It->second, CSSubgraphs); // Add external document objects - for (std::map::const_iterator It = d->objectMap.begin(); It != d->objectMap.end();++It) { + for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) { std::vector OutList = It->second->getOutList(); for (std::vector::const_iterator It2=OutList.begin();It2!=OutList.end();++It2) { if (*It2) { @@ -598,11 +610,11 @@ void Document::exportGraphviz(std::ostream& out) const bool CSSubgraphs = depGrp->GetBool("GeoFeatureSubgraphs", true); // Add internal document objects - for (std::map::const_iterator It = d->objectMap.begin(); It != d->objectMap.end();++It) + for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) add(It->second, It->second->getNameInDocument(), It->second->Label.getValue(), CSSubgraphs); // Add external document objects - for (std::map::const_iterator It = d->objectMap.begin(); It != d->objectMap.end();++It) { + for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) { std::vector OutList = It->second->getOutList(); for (std::vector::const_iterator It2=OutList.begin();It2!=OutList.end();++It2) { if (*It2) { @@ -664,7 +676,7 @@ void Document::exportGraphviz(std::ostream& out) const bool omitGeoFeatureGroups = depGrp->GetBool("GeoFeatureSubgraphs", true); // Add edges between document objects - for (std::map::const_iterator It = d->objectMap.begin(); It != d->objectMap.end();++It) { + for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) { if(omitGeoFeatureGroups) { //coordinate systems are represented by subgraphs @@ -1132,8 +1144,9 @@ void Document::onChanged(const Property* prop) // the Name property is a label for display purposes if (prop == &Label) { App::GetApplication().signalRelabelDocument(*this); - } - else if (prop == &Uid) { + } else if(prop == &ShowHidden) { + App::GetApplication().signalShowHidden(*this); + } else if (prop == &Uid) { std::string new_dir = getTransientDirectoryName(this->Uid.getValueStr(),this->FileName.getStrValue()); std::string old_dir = this->TransientDir.getStrValue(); Base::FileInfo TransDirNew(new_dir); @@ -1279,6 +1292,8 @@ Document::Document(void) ADD_PROPERTY_TYPE(License,(license.c_str()),0,Prop_None,"License string of the Item"); ADD_PROPERTY_TYPE(LicenseURL,(licenseUrl.c_str()),0,Prop_None,"URL to the license text/contract"); + ADD_PROPERTY_TYPE(ShowHidden,(false), 0,PropertyType(Prop_None), + "Whether to show hidden object items in the tree view"); // this creates and sets 'TransientDir' in onChanged() ADD_PROPERTY_TYPE(TransientDir,(""),0,PropertyType(Prop_Transient|Prop_ReadOnly), @@ -1302,14 +1317,12 @@ Document::~Document() catch (const boost::exception&) { } - std::map::iterator it; - #ifdef FC_LOGUPDATECHAIN Console().Log("-Delete Features of %s \n",getName()); #endif d->objectArray.clear(); - for (it = d->objectMap.begin(); it != d->objectMap.end(); ++it) { + for (auto it = d->objectMap.begin(); it != d->objectMap.end(); ++it) { it->second->setStatus(ObjectStatus::Destroy, true); delete(it->second); } @@ -1413,7 +1426,6 @@ void Document::Restore(Base::XMLReader &reader) reader.readElement("Feature"); string type = reader.getAttribute("type"); string name = reader.getAttribute("name"); - try { addObject(type.c_str(), name.c_str(), /*isNew=*/ false); } @@ -1477,6 +1489,7 @@ void Document::exportObjects(const std::vector& obj, writer.writeFiles(); } + void Document::writeObjects(const std::vector& obj, Base::Writer &writer) const { @@ -1490,12 +1503,18 @@ void Document::writeObjects(const std::vector& obj, writer.Stream() << writer.ind() << "getTypeId().getName() << "\" " << "name=\"" << (*it)->getNameInDocument() << "\" "; + << "id=\"" << (*it)->getID() << "\" " + << "ViewType=\"" << (*it)->getViewProviderNameStored() << "\" "; // See DocumentObjectPy::getState if ((*it)->testStatus(ObjectStatus::Touch)) writer.Stream() << "Touched=\"1\" "; - if ((*it)->testStatus(ObjectStatus::Error)) + if ((*it)->testStatus(ObjectStatus::Error)) { writer.Stream() << "Invalid=\"1\" "; + auto desc = getErrorDescription(*it); + if(desc) + writer.Stream() << "Error=\"" << Property::encodeAttribute(desc) << "\" "; + } writer.Stream() << "/>" << endl; } @@ -1528,21 +1547,33 @@ Document::readObjects(Base::XMLReader& reader) setStatus(Document::KeepTrailingDigits, !reader.doNameMapping()); std::vector objs; + // read the object types reader.readElement("Objects"); int Cnt = reader.getAttributeAsInteger("Count"); + + long lastId = 0; for (int i=0 ;ilastObjectId = reader.getAttributeAsInteger("id")-1; + } try { // Use name from XML as is and do NOT remove trailing digits because // otherwise we may cause a dependency to itself // Example: Object 'Cut001' references object 'Cut' and removing the // digits we make an object 'Cut' referencing itself. - App::DocumentObject* obj = addObject(type.c_str(), name.c_str(), /*isNew=*/ false); + App::DocumentObject* obj = addObject(type.c_str(), obj_name, /*isNew=*/ false, viewType, partial); if (obj) { + if(lastId < obj->_Id) + lastId = obj->_Id; objs.push_back(obj); // use this name for the later access because an object with // the given name may already exist @@ -1559,6 +1590,8 @@ Document::readObjects(Base::XMLReader& reader) Base::Console().Error("Cannot create object '%s': (%s)\n", name.c_str(), e.what()); } } + if(!testStatus(Status::Importing)) + d->lastObjectId = lastId; reader.readEndElement("Objects"); setStatus(Document::KeepTrailingDigits, keepDigits); @@ -1627,6 +1660,7 @@ Document::importObjects(Base::XMLReader& reader) std::vector objs = readObjects(reader); reader.readEndElement("Document"); + signalImportObjects(objs, reader); // reset all touched @@ -1846,7 +1880,8 @@ void Document::restore (void) } d->objectArray.clear(); d->objectMap.clear(); - d->activeObject = 0; + d->objectIdMap.clear(); + d->lastObjectId = 0; Base::FileInfo fi(FileName.getValue()); Base::ifstream file(fi, std::ios::in | std::ios::binary); @@ -1966,12 +2001,75 @@ int Document::countObjects(void) const return static_cast(d->objectArray.size()); } +void Document::getLinksTo(std::set &links, + const DocumentObject *obj, int options, int maxCount, + const std::vector &objs) const +{ + std::map linkMap; + + for(auto o : objs.size()?objs:d->objectArray) { + if(o == obj) continue; + auto linked = o; + if(options & GetLinkArrayElement) + linked = o->getLinkedObject(false); + else { + auto ext = o->getExtensionByType(true); + if(ext) + linked = ext->getTrueLinkedObject(false,0,0,true); + else + linked = o->getLinkedObject(false); + } + + if(linked && linked!=o) { + if(options & GetLinkRecursive) + linkMap[linked] = o; + else if(linked == obj || !obj) { + if((options & GetLinkExternal) + && linked->getDocument()==o->getDocument()) + continue; + else if(options & GetLinkedObject) + links.insert(linked); + else + links.insert(o); + if(maxCount && maxCount<=(int)links.size()) + return; + } + } + } + + if(!(options & GetLinkRecursive)) + return; + + std::vector current(1,obj); + for(int depth=0;current.size();++depth) { + if(!GetApplication().checkLinkDepth(depth,true)) + break; + std::vector next; + for(auto o : current) { + auto iter = linkMap.find(o); + if(iter!=linkMap.end() && links.insert(iter->second).second) { + if(maxCount && maxCount<=(int)links.size()) + return; + next.push_back(iter->second); + } + } + current.swap(next); + } + return; +} + +bool Document::hasLinksTo(const DocumentObject *obj) const { + std::set links; + getLinksTo(links,obj,0,1); + return !links.empty(); +} + std::vector Document::getInList(const DocumentObject* me) const { // result list std::vector result; // go through all objects - for (std::map::const_iterator It = d->objectMap.begin(); It != d->objectMap.end();++It) { + for (auto It = d->objectMap.begin(); It != d->objectMap.end();++It) { // get the outList and search if me is in that list std::vector OutList = It->second->getOutList(); for (std::vector::const_iterator It2=OutList.begin();It2!=OutList.end();++It2) @@ -2606,7 +2704,8 @@ void Document::recomputeFeature(DocumentObject* Feat) } } -DocumentObject * Document::addObject(const char* sType, const char* pObjectName, bool isNew) +DocumentObject * Document::addObject(const char* sType, const char* pObjectName, + bool isNew, const char *viewType, bool isPartial) { Base::BaseClass* base = static_cast(Base::Type::createInstanceByName(sType,true)); @@ -2641,6 +2740,9 @@ DocumentObject * Document::addObject(const char* sType, const char* pObjectName, // insert in the name map d->objectMap[ObjectName] = pcObject; + // generate object id and add to id map; + pcObject->_Id = ++d->lastObjectId; + d->objectIdMap[pcObject->_Id] = pcObject; // cache the pointer to the name string in the Object (for performance of DocumentObject::getNameInDocument()) pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first); // insert in the vector @@ -2660,6 +2762,12 @@ DocumentObject * Document::addObject(const char* sType, const char* pObjectName, // mark the object as new (i.e. set status bit 2) and send the signal pcObject->setStatus(ObjectStatus::New, true); + + if(!viewType) + viewType = pcObject->getViewProviderNameOverride(); + if(viewType) + pcObject->_pcViewProviderName = viewType; + signalNewObject(*pcObject); // do no transactions if we do a rollback! @@ -2730,6 +2838,9 @@ std::vector Document::addObjects(const char* sType, const std: // insert in the name map d->objectMap[ObjectName] = pcObject; + // generate object id and add to id map; + pcObject->_Id = ++d->lastObjectId; + d->objectIdMap[pcObject->_Id] = pcObject; // cache the pointer to the name string in the Object (for performance of DocumentObject::getNameInDocument()) pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first); // insert in the vector @@ -2744,6 +2855,10 @@ std::vector Document::addObjects(const char* sType, const std: // mark the object as new (i.e. set status bit 2) and send the signal pcObject->setStatus(ObjectStatus::New, true); + + const char *viewType = pcObject->getViewProviderNameOverride(); + pcObject->_pcViewProviderName = viewType?viewType:""; + signalNewObject(*pcObject); // do no transactions if we do a rollback! @@ -2786,6 +2901,9 @@ void Document::addObject(DocumentObject* pcObject, const char* pObjectName) // insert in the name map d->objectMap[ObjectName] = pcObject; + // generate object id and add to id map; + if(!pcObject->_Id) pcObject->_Id = ++d->lastObjectId; + d->objectIdMap[pcObject->_Id] = pcObject; // cache the pointer to the name string in the Object (for performance of DocumentObject::getNameInDocument()) pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first); // insert in the vector @@ -2795,6 +2913,10 @@ void Document::addObject(DocumentObject* pcObject, const char* pObjectName) // mark the object as new (i.e. set status bit 2) and send the signal pcObject->setStatus(ObjectStatus::New, true); + + const char *viewType = pcObject->getViewProviderNameOverride(); + pcObject->_pcViewProviderName = viewType?viewType:""; + signalNewObject(*pcObject); // do no transactions if we do a rollback! @@ -2809,6 +2931,9 @@ void Document::_addObject(DocumentObject* pcObject, const char* pObjectName) { std::string ObjectName = getUniqueObjectName(pObjectName); d->objectMap[ObjectName] = pcObject; + // generate object id and add to id map; + if(!pcObject->_Id) pcObject->_Id = ++d->lastObjectId; + d->objectIdMap[pcObject->_Id] = pcObject; d->objectArray.push_back(pcObject); // cache the pointer to the name string in the Object (for performance of DocumentObject::getNameInDocument()) pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first); @@ -2820,6 +2945,9 @@ void Document::_addObject(DocumentObject* pcObject, const char* pObjectName) d->activeUndoTransaction->addObjectDel(pcObject); } + const char *viewType = pcObject->getViewProviderNameOverride(); + pcObject->_pcViewProviderName = viewType?viewType:""; + // send the signal signalNewObject(*pcObject); @@ -2835,7 +2963,7 @@ void Document::_addObject(DocumentObject* pcObject, const char* pObjectName) /// Remove an object out of the document void Document::removeObject(const char* sName) { - std::map::iterator pos = d->objectMap.find(sName); + auto pos = d->objectMap.find(sName); // name not found? if (pos == d->objectMap.end()) @@ -2909,6 +3037,7 @@ void Document::removeObject(const char* sName) } pos->second->setStatus(ObjectStatus::Remove, false); // Unset the bit to be on the safe side + d->objectIdMap.erase(pos->second->_Id); d->objectMap.erase(pos); } @@ -2918,7 +3047,21 @@ void Document::_removeObject(DocumentObject* pcObject) // TODO Refactoring: share code with Document::removeObject() (2015-09-01, Fat-Zer) _checkTransaction(pcObject); - std::map::iterator pos = d->objectMap.find(pcObject->getNameInDocument()); + auto pos = d->objectMap.find(pcObject->getNameInDocument()); + + if(!d->rollback && d->activeUndoTransaction && pos->second->hasChildElement()) { + // Preserve link group children global visibility. See comments in + // removeObject() for more details. + for(auto &sub : pos->second->getSubObjects()) { + if(sub.empty()) + continue; + if(sub[sub.size()-1]!='.') + sub += '.'; + auto sobj = pos->second->getSubObject(sub.c_str()); + if(sobj && sobj->getDocument()==this && !sobj->Visibility.getValue()) + d->activeUndoTransaction->addObjectChange(sobj,&sobj->Visibility); + } + } if (d->activeObject == pcObject) d->activeObject = 0; @@ -2951,6 +3094,7 @@ void Document::_removeObject(DocumentObject* pcObject) // remove from map pcObject->setStatus(ObjectStatus::Remove, false); // Unset the bit to be on the safe side + d->objectIdMap.erase(pcObject->_Id); d->objectMap.erase(pos); for (std::vector::iterator it = d->objectArray.begin(); it != d->objectArray.end(); ++it) { @@ -3138,9 +3282,7 @@ DocumentObject * Document::getActiveObject(void) const DocumentObject * Document::getObject(const char *Name) const { - std::map::const_iterator pos; - - pos = d->objectMap.find(Name); + auto pos = d->objectMap.find(Name); if (pos != d->objectMap.end()) return pos->second; @@ -3148,10 +3290,19 @@ DocumentObject * Document::getObject(const char *Name) const return 0; } +DocumentObject * Document::getObjectByID(long id) const +{ + auto it = d->objectIdMap.find(id); + if(it!=d->objectIdMap.end()) + return it->second; + return 0; +} + + // Note: This method is only used in Tree.cpp slotChangeObject(), see explanation there bool Document::isIn(const DocumentObject *pFeat) const { - for (std::map::const_iterator o = d->objectMap.begin(); o != d->objectMap.end(); ++o) { + for (auto o = d->objectMap.begin(); o != d->objectMap.end(); ++o) { if (o->second == pFeat) return true; } @@ -3161,9 +3312,7 @@ bool Document::isIn(const DocumentObject *pFeat) const const char * Document::getObjectName(DocumentObject *pFeat) const { - std::map::const_iterator pos; - - for (pos = d->objectMap.begin();pos != d->objectMap.end();++pos) { + for (auto pos = d->objectMap.begin();pos != d->objectMap.end();++pos) { if (pos->second == pFeat) return pos->first.c_str(); } @@ -3178,8 +3327,7 @@ std::string Document::getUniqueObjectName(const char *Name) const std::string CleanName = Base::Tools::getIdentifier(Name); // name in use? - std::map::const_iterator pos; - pos = d->objectMap.find(CleanName); + auto pos = d->objectMap.find(CleanName); if (pos == d->objectMap.end()) { // if not, name is OK @@ -3222,6 +3370,7 @@ std::vector Document::getObjects() const return d->objectArray; } + std::vector Document::getObjectsOfType(const Base::Type& typeId) const { std::vector Objects; @@ -3260,7 +3409,7 @@ std::vector Document::findObjects(const Base::Type& typeId, con int Document::countObjectsOfType(const Base::Type& typeId) const { int ct=0; - for (std::map::const_iterator it = d->objectMap.begin(); it != d->objectMap.end(); ++it) { + for (auto it = d->objectMap.begin(); it != d->objectMap.end(); ++it) { if (it->second->getTypeId().isDerivedFrom(typeId)) ct++; } diff --git a/src/App/Document.h b/src/App/Document.h index 46fd2aa742..4bee832e9c 100644 --- a/src/App/Document.h +++ b/src/App/Document.h @@ -69,7 +69,7 @@ public: Closable = 2, Restoring = 3, Recomputing = 4, - PartialRestore = 5 + PartialRestore = 5, }; /** @name Properties */ @@ -109,6 +109,8 @@ public: PropertyLink Tip; /// Tip object of the document (if any) PropertyString TipName; + /// Whether to show hidden items in TreeView + PropertyBool ShowHidden; //@} /** @name Signals of the document */ @@ -126,6 +128,8 @@ public: boost::signals2::signal signalBeforeChangeObject; /// signal on changed Object boost::signals2::signal signalChangedObject; + /// signal on manually called DocumentObject::touch() + boost::signals2::signal signalTouchedObject; /// signal on relabeled Object boost::signals2::signal signalRelabelObject; /// signal on activated Object @@ -201,8 +205,11 @@ public: * @param sType the type of created object * @param pObjectName if nonNULL use that name otherwise generate a new unique name based on the \a sType * @param isNew if false don't call the \c DocumentObject::setupObject() callback (default is true) + * @param viewType override object's view provider name + * @param isPartial indicate if this object is meant to be partially loaded */ - DocumentObject *addObject(const char* sType, const char* pObjectName=0, bool isNew=true); + DocumentObject *addObject(const char* sType, const char* pObjectName=0, + bool isNew=true, const char *viewType=0, bool isPartial=false); /** Add an array of features of the given types and names. * Unicode names are set through the Label property. * @param sType The type of created object @@ -239,6 +246,8 @@ public: DocumentObject *getActiveObject(void) const; /// Returns a Object of this document DocumentObject *getObject(const char *Name) const; + /// Returns a Object of this document by its id + DocumentObject *getObjectByID(long id) const; /// Returns true if the DocumentObject is contained in this document bool isIn(const DocumentObject *pFeat) const; /// Returns a Name of an Object or 0 @@ -248,7 +257,7 @@ public: /// Returns a name of the form prefix_number. d specifies the number of digits. std::string getStandardObjectName(const char *Name, int d) const; /// Returns a list of all Objects - std::vector getObjects() const; + const std::vector &getObjects() const; std::vector getObjectsOfType(const Base::Type& typeId) const; /// Returns all object with given extensions. If derived=true also all objects with extensions derived from the given one std::vector getObjectsWithExtension(const Base::Type& typeId, bool derived = true) const; @@ -359,6 +368,22 @@ public: (const App::DocumentObject* from, const App::DocumentObject* to) const; //@} + /** Return the links to a given object + * + * @param links: holds the links found + * @param obj: the linked object. If NULL, then all links are returned. + * @param option: @sa App::GetLinkOptions + * @param maxCount: limit the number of links returned, 0 means no limit + * @param objs: optional objects to search for, if empty, then all objects + * of this document are searched. + */ + void getLinksTo(std::set &links, + const DocumentObject *obj, int options, int maxCount=0, + const std::vector &objs = {}) const; + + /// Check if there is any link to the given object + bool hasLinksTo(const DocumentObject *obj) const; + /// Function called to signal that an object identifier has been renamed void renameObjectIdentifiers(const std::map & paths, const std::function &selector = [](const App::DocumentObject *) { return true; }); diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index 902aab9611..fadbf1b5dd 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -29,17 +29,22 @@ #include #include #include +#include +#include "Application.h" #include "Document.h" #include "DocumentObject.h" #include "DocumentObjectGroup.h" #include "PropertyLinks.h" +#include "PropertyGeo.h" #include "PropertyExpressionEngine.h" #include "DocumentObjectExtension.h" #include "GeoFeatureGroupExtension.h" #include #include +FC_LOG_LEVEL_INIT("App",true,true) + using namespace App; /** \defgroup DocObject Document Object @@ -56,11 +61,22 @@ DocumentObjectExecReturn *DocumentObject::StdReturn = 0; //=========================================================================== DocumentObject::DocumentObject(void) - : ExpressionEngine(),_pDoc(0),pcNameInDocument(0) + : ExpressionEngine(),_pDoc(0),pcNameInDocument(0),_Id(0) { // define Label of type 'Output' to avoid being marked as touched after relabeling ADD_PROPERTY_TYPE(Label,("Unnamed"),"Base",Prop_Output,"User name of the object (UTF8)"); + ADD_PROPERTY_TYPE(Label2,(""),"Base",Prop_Hidden,"User description of the object (UTF8)"); + Label2.setStatus(App::Property::Output,true); ADD_PROPERTY_TYPE(ExpressionEngine,(),"Base",Prop_Hidden,"Property expressions"); + + ADD_PROPERTY(Visibility, (true)); + + // default set Visibility status to hidden and output (no touch) for + // compatibitily reason. We use setStatus instead of PropertyType to + // allow user to change its status later + Visibility.setStatus(Property::Output,true); + Visibility.setStatus(Property::Hidden,true); + Visibility.setStatus(Property::NoModify,true); } DocumentObject::~DocumentObject(void) @@ -122,9 +138,13 @@ bool DocumentObject::recomputeFeature() * If it should be forced to recompute a document object then use * \ref enforceRecompute() instead. */ -void DocumentObject::touch(void) +void DocumentObject::touch(bool noRecompute) { + if(!noRecompute) + StatusBits.set(ObjectStatus::Enforce); StatusBits.set(ObjectStatus::Touch); + if (_pDoc) + _pDoc->signalTouchedObject(*this); } /** @@ -143,8 +163,7 @@ bool DocumentObject::isTouched() const */ void DocumentObject::enforceRecompute(void) { - StatusBits.set(ObjectStatus::Enforce); - StatusBits.set(ObjectStatus::Touch); + touch(false); } /** @@ -188,7 +207,16 @@ const char* DocumentObject::getStatusString(void) const return "Valid"; } -const char *DocumentObject::getNameInDocument(void) const +std::string DocumentObject::getFullName() const { + if(!getDocument() || !pcNameInDocument) + return "?"; + std::string name(getDocument()->getName()); + name += '#'; + name += *pcNameInDocument; + return name; +} + +const char *DocumentObject::getNameInDocument() const { // Note: It can happen that we query the internal name of an object even if it is not // part of a document (anymore). This is the case e.g. if we have a reference in Python @@ -322,7 +350,8 @@ void _getInListRecursive(std::set& objSet, std::vector DocumentObject::getInListRecursive(void) const { // number of objects in document is a good estimate in result size - int maxDepth = getDocument()->countObjects() + 2; + // int maxDepth = getDocument()->countObjects() +2; + int maxDepth = GetApplication().checkLinkDepth(0); std::set result; // using a rcursie helper to collect all InLists @@ -353,7 +382,7 @@ void _getOutListRecursive(std::set& objSet, std::vector DocumentObject::getOutListRecursive(void) const { // number of objects in document is a good estimate in result size - int maxDepth = getDocument()->countObjects() + 2; + int maxDepth = GetApplication().checkLinkDepth(0); std::set result; // using a recursive helper to collect all OutLists @@ -532,13 +561,20 @@ void DocumentObject::onBeforeChange(const Property* prop) if (_pDoc) onBeforeChangeProperty(_pDoc, prop); + + signalBeforeChange(*this,*prop); } /// get called by the container when a Property was changed void DocumentObject::onChanged(const Property* prop) { - if (_pDoc) - _pDoc->onChangedProperty(this,prop); + if(GetApplication().isClosingAll()) + return; + + // Delay signaling view provider until the document object has handled the + // change + // if (_pDoc) + // _pDoc->onChangedProperty(this,prop); if (prop == &Label && _pDoc && oldLabel != Label.getStrValue()) _pDoc->signalRelabelObject(*this); @@ -553,6 +589,12 @@ void DocumentObject::onChanged(const Property* prop) //call the parent for appropriate handling TransactionalObject::onChanged(prop); + + // Now signal the view provider + if (_pDoc) + _pDoc->onChangedProperty(this,prop); + + signalChanged(*this,*prop); } PyObject *DocumentObject::getPyObject(void) @@ -564,10 +606,126 @@ PyObject *DocumentObject::getPyObject(void) return Py::new_reference_to(PythonObject); } -std::vector DocumentObject::getPySubObjects(const std::vector&) const +DocumentObject *DocumentObject::getSubObject(const char *subname, + PyObject **pyObj, Base::Matrix4D *mat, bool transform, int depth) const { - // default implementation returns nothing - return std::vector(); + DocumentObject *ret = 0; + auto exts = getExtensionsDerivedFromType(); + for(auto ext : exts) { + if(ext->extensionGetSubObject(ret,subname,pyObj,mat,transform, depth)) + return ret; + } + + std::string name; + const char *dot=0; + if(!subname || !(dot=strchr(subname,'.'))) { + ret = const_cast(this); + }else if(subname[0]=='$') { + name = std::string(subname+1,dot); + for(auto obj : getOutList()) { + if(name == obj->Label.getValue()) { + ret = obj; + break; + } + } + }else{ + name = std::string(subname,dot); + const auto &outList = getOutList(); + if(outList.size()!=_outListMap.size()) { + _outListMap.clear(); + for(auto obj : outList) + _outListMap[obj->getNameInDocument()] = obj; + } + auto it = _outListMap.find(name.c_str()); + if(it != _outListMap.end()) + ret = it->second; + } + + // TODO: By right, normal object's placement does not transform its sub + // objects (think of the claimed children of a Fusion). But I do think we + // should change that. + if(transform && mat) { + auto pla = dynamic_cast(getPropertyByName("Placement")); + if(pla) + *mat *= pla->getValue().toMatrix(); + } + + if(ret && dot) + return ret->getSubObject(dot+1,pyObj,mat,true,depth+1); + return ret; +} + +std::vector DocumentObject::getSubObjectList(const char *subname) const { + std::vector res; + res.push_back(const_cast(this)); + if(!subname || !subname[0]) + return res; + std::string sub(subname); + for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos+1)) { + char c = sub[pos+1]; + sub[pos+1] = 0; + auto sobj = getSubObject(sub.c_str()); + if(!sobj || !sobj->getNameInDocument()) + break; + res.push_back(sobj); + sub[pos+1] = c; + } + return res; +} + +std::vector DocumentObject::getSubObjects(int reason) const { + std::vector ret; + auto exts = getExtensionsDerivedFromType(); + for(auto ext : exts) { + if(ext->extensionGetSubObjects(ret,reason)) + return ret; + } + return ret; +} + +std::vector > DocumentObject::getParents(int depth) const { + std::vector > ret; + if(!getNameInDocument() || !GetApplication().checkLinkDepth(depth)) + return ret; + std::string name(getNameInDocument()); + name += "."; + for(auto parent : getInList()) { + if(!parent || !parent->getNameInDocument()) + continue; + if(!parent->hasChildElement() && + !parent->hasExtension(GeoFeatureGroupExtension::getExtensionClassTypeId())) + continue; + if(!parent->getSubObject(name.c_str())) + continue; + + auto links = GetApplication().getLinksTo(parent,App::GetLinkRecursive); + links.insert(parent); + for(auto parent : links) { + auto parents = parent->getParents(depth+1); + if(parents.empty()) + parents.emplace_back(parent,std::string()); + for(auto &v : parents) + ret.emplace_back(v.first,v.second+name); + } + } + return ret; +} + +DocumentObject *DocumentObject::getLinkedObject( + bool recursive, Base::Matrix4D *mat, bool transform, int depth) const +{ + DocumentObject *ret = 0; + auto exts = getExtensionsDerivedFromType(); + for(auto ext : exts) { + if(ext->extensionGetLinkedObject(ret,recursive,mat,transform,depth)) + return ret; + } + if(transform && mat) { + auto pla = dynamic_cast(getPropertyByName("Placement")); + if(pla) + *mat *= pla->getValue().toMatrix(); + } + return const_cast(this); } void DocumentObject::Save (Base::Writer &writer) const @@ -664,6 +822,8 @@ void DocumentObject::onDocumentRestored() auto vector = getExtensionsDerivedFromType(); for(auto ext : vector) ext->onExtendedDocumentRestored(); + if(Visibility.testStatus(Property::Output)) + Visibility.setStatus(Property::NoModify,true); } void DocumentObject::onSettingDocument() @@ -715,3 +875,136 @@ void App::DocumentObject::_addBackLink(DocumentObject* newObj) (void)newObj; #endif //USE_OLD_DAG } + +int DocumentObject::setElementVisible(const char *element, bool visible) { + for(auto ext : getExtensionsDerivedFromType()) { + int ret = ext->extensionSetElementVisible(element,visible); + if(ret>=0) return ret; + } + + return -1; +} + +int DocumentObject::isElementVisible(const char *element) const { + for(auto ext : getExtensionsDerivedFromType()) { + int ret = ext->extensionIsElementVisible(element); + if(ret>=0) return ret; + } + + return -1; +} + +bool DocumentObject::hasChildElement() const { + for(auto ext : getExtensionsDerivedFromType()) { + if(ext->extensionHasChildElement()) + return true; + } + return false; +} + +DocumentObject *DocumentObject::resolve(const char *subname, + App::DocumentObject **parent, std::string *childName, const char **subElement, + PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const +{ + auto self = const_cast(this); + if(parent) *parent = 0; + if(subElement) *subElement = 0; + + auto obj = getSubObject(subname,pyObj,pmat,transform,depth); + if(!obj || !subname || *subname==0) + return self; + + if(!parent && !subElement) + return obj; + + // NOTE, the convension of '.' separated SubName demands a mandatory ending + // '.' for each object name in SubName, even if there is no subelement + // following it. So finding the last dot will give us the end of the last + // object name. + const char *dot=0; + if(Data::ComplexGeoData::isMappedElement(subname) || + !(dot=strrchr(subname,'.')) || + dot == subname) + { + if(subElement) + *subElement = dot?dot+1:subname; + return obj; // this means no parent object reference in SubName + } + + if(parent) + *parent = self; + + bool elementMapChecked = false; + const char *lastDot = dot; + for(--dot;;--dot) { + // check for the second last dot, which is the end of the last parent object + if(*dot == '.' || dot == subname) { + // We can't get parent object by its name, because the object may be + // externally linked (i.e. in a different document). So go through + // getSubObject again. + if(!elementMapChecked) { + elementMapChecked = true; + const char *sub = dot==subname?dot:dot+1; + if(Data::ComplexGeoData::isMappedElement(sub)) { + lastDot = dot; + if(dot==subname) + break; + else + continue; + } + } + if(dot==subname) + break; + auto sobj = getSubObject(std::string(subname,dot-subname+1).c_str()); + if(sobj!=obj) { + if(parent) { + // Link/LinkGroup has special visiblility handling of plain + // group, so keep ascending + if(!sobj->hasExtension(GroupExtension::getExtensionClassTypeId(),false)) { + *parent = sobj; + break; + } + for(auto ddot=dot-1;ddot!=subname;--ddot) { + if(*ddot != '.') continue; + auto sobj = getSubObject(std::string(subname,ddot-subname+1).c_str()); + if(!sobj->hasExtension(GroupExtension::getExtensionClassTypeId(),false)) { + *parent = sobj; + break; + } + } + } + break; + } + } + } + if(childName && lastDot!=dot) { + if(*dot == '.') + ++dot; + const char *nextDot = strchr(dot,'.'); + assert(nextDot); + *childName = std::string(dot,nextDot-dot); + } + if(subElement) + *subElement = *lastDot=='.'?lastDot+1:lastDot; + return obj; +} + +const std::string &DocumentObject::hiddenMarker() { + static std::string marker("!hide"); + return marker; +} + +const char *DocumentObject::hasHiddenMarker(const char *subname) { + if(!subname) return 0; + const char *marker = strrchr(subname,'.'); + if(!marker) + marker = subname; + else + ++marker; + return hiddenMarker()==marker?marker:0; +} + +bool DocumentObject::redirectSubName(std::ostringstream &, DocumentObject *, DocumentObject *) const { + return false; +} + diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index 4a445eee06..3a1351202f 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -46,7 +47,7 @@ enum ObjectStatus { Touch = 0, Error = 1, New = 2, - Recompute = 3, + Recompute = 3, // set when the object is currently being recomputed Restore = 4, Remove = 5, PythonCall = 6, @@ -86,18 +87,35 @@ class AppExport DocumentObject: public App::TransactionalObject public: PropertyString Label; + PropertyString Label2; PropertyExpressionEngine ExpressionEngine; + /// Allow control visibility status in App name space + PropertyBool Visibility; + + /// signal before changing a property of this object + boost::signals2::signal signalBeforeChange; + /// signal on changed property of this object + boost::signals2::signal signalChanged; + /// returns the type name of the ViewProvider virtual const char* getViewProviderName(void) const { return ""; } + /// This function is introduced to allow Python feature override its view provider + virtual const char *getViewProviderNameOverride() const { + return getViewProviderName(); + } /// Constructor DocumentObject(void); virtual ~DocumentObject(); /// returns the name which is set in the document for this object (not the name property!) const char *getNameInDocument(void) const; + /// Return the object ID that is unique within its owner document + long getID() const {return _Id;} + /// Return the object full name of the form DocName#ObjName + std::string getFullName() const; virtual bool isAttachedToDocument() const; virtual const char* detachFromDocument(); /// gets the document in which this Object is handled @@ -107,7 +125,7 @@ public: */ //@{ /// set this document object touched (cause recomputation on dependent features) - void touch(void); + void touch(bool noRecompute=false); /// test if this document object is touched bool isTouched(void) const; /// Enforce this document object to be recomputed @@ -137,6 +155,31 @@ public: void setStatus(ObjectStatus pos, bool on) {StatusBits.set((size_t)pos, on);} //@} + /** Child element handling + */ + //@{ + /** Set sub-element visibility + * + * For performance reason, \c element must not contain any further + * sub-elements, i.e. there should be no '.' inside \c element. + * + * @return -1 if element visiblity is not supported, 0 if element is not + * found, 1 if success + */ + virtual int setElementVisible(const char *element, bool visible); + + /** Get sub-element visibility + * + * @return -1 if element visiblity is not supported or element not found, 0 + * if element is invisible, or else 1 + */ + virtual int isElementVisible(const char *element) const; + + /// return true to activate tree view group object handling and element visibility + virtual bool hasChildElement() const; + //@} + + /** DAG handling This part of the interface deals with viewing the document as a DAG (directed acyclic graph). @@ -214,8 +257,89 @@ public: */ virtual void onLostLinkToObject(DocumentObject*); virtual PyObject *getPyObject(void); - /// its used to get the python sub objects by name (e.g. by the selection) - virtual std::vector getPySubObjects(const std::vector&) const; + + /** Get the sub element/object by name + * + * @param subname: a string which is dot separated name to refer to a sub + * element or object. An empty string can be used to refer to the object + * itself + * + * @param pyObj: if non zero, returns the python object corresponding to + * this sub object. The actual type of this python object is implementation + * dependent. For example, The current implementation of Part::Feature will + * return the TopoShapePy, event if there is no sub-element reference, in + * which case it returns the whole shape. + * + * @param mat: If non zero, it is used as the current transformation matrix + * on input. And output as the accumulated transformation up until and + * include the transformation applied by the final object reference in \c + * subname. For Part::Feature, the transformation is applied to the + * TopoShape inside \c pyObj before returning. + * + * @param transform: if false, then it will not apply the object's own + * transformation to \c mat, which lets you override the object's placement + * (and possibly scale). + * + * @param depth: depth limitation as hint for cyclic link detection + * + * @return The last document object refered in subname. If subname is empty, + * then it shall return itself. If subname is invalid, then it shall return + * zero. + */ + virtual DocumentObject *getSubObject(const char *subname, PyObject **pyObj=0, + Base::Matrix4D *mat=0, bool transform=true, int depth=0) const; + + /// Return a list of objects referenced by a given subname including this object + std::vector getSubObjectList(const char *subname) const; + + /// reason of calling getSubObjects() + enum GSReason { + /// default, mostly for exporting shape objects + GS_DEFAULT, + /// for element selection + GS_SELECT, + }; + + /** Return name reference of all sub-objects + * + * @param reason: indicate reason of obtaining the sub objects + * + * The default implementation returns all object references in + * PropertyLink, and PropertyLinkList, if any + * + * @return Return a vector of subname references for all sub-objects. In + * most cases, the name returned will be the object name plus an ending + * '.', which can be passed directly to getSubObject() to retrieve the + * name. The reason to return the name reference instead of the sub object + * itself is because there may be no real sub object, or the sub object + * need special transformation. For example, sub objects of an array type + * of object. + */ + virtual std::vector getSubObjects(int reason=0) const; + + ///Obtain top parents and subnames of this object using its InList + std::vector > getParents(int depth=0) const; + + /** Return the linked object with optional transformation + * + * @param recurse: If false, return the immediate linked object, or else + * recursively call this function to return the final linked object. + * + * @param mat: If non zero, it is used as the current transformation matrix + * on input. And output as the accumulated transformation till the final + * linked object. + * + * @param transform: if false, then it will not accumulate the object's own + * placement into \c mat, which lets you override the object's placement. + * + * @return Return the linked object. This function must return itself if the + * it is not a link or the link is invalid. + */ + virtual DocumentObject *getLinkedObject(bool recurse=true, + Base::Matrix4D *mat=0, bool transform=false, int depth=0) const; + + /* Return true to cause PropertyView to show linked object's property */ + virtual bool canLinkProperties() const {return true;} friend class Document; friend class Transaction; @@ -237,6 +361,51 @@ public: const std::string & getOldLabel() const { return oldLabel; } + const char *getViewProviderNameStored() const { + return _pcViewProviderName.c_str(); + } + + /** Resolve the last document object referenced in the subname + * + * @param subname: dot separated subname + * @param parent: return the direct parent of the object + * @param childName: return child name to be passed to is/setElementVisible() + * @param subElement: return non-object sub-element name if found. The + * pointer is guaranteed to be within the buffer pointed to by 'subname' + * + * @sa getSubObject() + * @return Returns the last referenced document object in the subname. If no + * such object in subname, return pObject. + */ + App::DocumentObject *resolve(const char *subname, App::DocumentObject **parent=0, + std::string *childName=0, const char **subElement=0, + PyObject **pyObj=0, Base::Matrix4D *mat=0, bool transform=true, int depth=0) const; + + /** Allow object to redirect a subname path + * + * @param ss: input as the current subname path from \a topParent leading + * just before this object, i.e. ends at the parent of this object. This + * function should append its own name to this path, or redirect the + * subname to other place. + * @param topParent: top parent of this subname path + * @param child: the immediate child object in the path + * + * This function is called by tree view to generate a subname path when an + * item is selected in the tree. Document object can use this function to + * redirect the selection to some other objects. + */ + virtual bool redirectSubName(std::ostringstream &ss, + DocumentObject *topParent, DocumentObject *child) const; + + /** Sepecial marker to mark the object has hidden + * + * It is used by Gui::ViewProvider::getElementColors(), but exposed here + * for convenience + */ + static const std::string &hiddenMarker(); + /// Check if the subname reference ends with hidden marker. + static const char *hasHiddenMarker(const char *subname); + protected: /// recompute only this object virtual App::DocumentObjectExecReturn *recompute(void); @@ -301,6 +470,13 @@ protected: // attributes // pointer to the document name string (for performance) const std::string *pcNameInDocument; +private: + // accessed by App::Document to record and restore the correct view provider type + std::string _pcViewProviderName; + + // unique identifier (ammong a document) of this object. + long _Id; + private: // Back pointer to all the fathers in a DAG of the document // this is used by the document (via friend) to have a effective DAG handling diff --git a/src/App/DocumentObjectExtension.cpp b/src/App/DocumentObjectExtension.cpp index 204054176c..c563b81e3e 100644 --- a/src/App/DocumentObjectExtension.cpp +++ b/src/App/DocumentObjectExtension.cpp @@ -91,3 +91,20 @@ DocumentObject* DocumentObjectExtension::getExtendedObject() { assert(getExtendedContainer()->isDerivedFrom(DocumentObject::getClassTypeId())); return static_cast(getExtendedContainer()); } + +bool DocumentObjectExtension::extensionGetSubObject(DocumentObject *&, + const char *, PyObject **, Base::Matrix4D *, bool, int) const +{ + return false; +} + +bool DocumentObjectExtension::extensionGetSubObjects(std::vector&, int) const +{ + return false; +} + +bool DocumentObjectExtension::extensionGetLinkedObject( + DocumentObject *&, bool, Base::Matrix4D *, bool, int) const +{ + return false; +} diff --git a/src/App/DocumentObjectExtension.h b/src/App/DocumentObjectExtension.h index 248ee119cd..3f1c8ea6d2 100644 --- a/src/App/DocumentObjectExtension.h +++ b/src/App/DocumentObjectExtension.h @@ -61,12 +61,39 @@ public: virtual void onExtendedSetupObject(); /// get called when object is going to be removed from the document virtual void onExtendedUnsetupObject(); - + virtual PyObject* getExtensionPyObject(void) override; /// returns the type name of the ViewProviderExtension which is automatically attached /// to the viewprovider object when it is initiated virtual const char* getViewProviderExtensionName(void) const {return "";} + + /** Get the sub object by name + * @sa DocumentObject::getSubObject() + * + * @return Return turn if handled, the sub object is returned in \c ret + */ + virtual bool extensionGetSubObject( DocumentObject *&ret, const char *subname, + PyObject **pyObj, Base::Matrix4D *mat, bool transform, int depth) const; + + /** Get name references of all sub objects + * @sa DocumentObject::getSubObjects() + * + * @return Return turn if handled, the sub object is returned in \c ret + */ + virtual bool extensionGetSubObjects(std::vector &ret, int reason) const; + + /** Get the linked object + * @sa DocumentObject::getLinkedObject() + * + * @return Return turn if handled, the linked object is returned in \c ret + */ + virtual bool extensionGetLinkedObject(DocumentObject *&ret, bool recursive, + Base::Matrix4D *mat, bool transform, int depth) const; + + virtual int extensionSetElementVisible(const char *, bool) {return -1;} + virtual int extensionIsElementVisible(const char *) {return -1;} + virtual bool extensionHasChildElement() const {return false;} }; } //App diff --git a/src/App/DocumentObjectPy.xml b/src/App/DocumentObjectPy.xml index 4622b71666..8204ffa02e 100644 --- a/src/App/DocumentObjectPy.xml +++ b/src/App/DocumentObjectPy.xml @@ -60,6 +60,90 @@ Recomputes this object + + + +getSubObject(subname, retType=0, matrix=None, transform=True, depth=0) + +* subname(string|list|tuple): dot separated string or sequence of strings +referencing subobject. + +* retType: return type, 0=PyObject, 1=DocObject, 2=DocAndPyObject, 3=Placement + + PyObject: return a python binding object for the (sub)object referenced in + each 'subname' The actual type of 'PyObject' is impelementation dependent. + For Part::Feature compatible objects, this will be of type TopoShapePy and + pre-transformed by accumulated transformation matrix along the object path. + + DocObject: return the document object referenced in subname, if 'matrix' is + None. Or, return a tuple (object, matrix) for each 'subname' and 'matrix' is + the accumulated transformation matrix for the sub object. + + DocAndPyObject: return a tuple (object, matrix, pyobj) for each subname + + Placement: return a trasformed placement of the sub-object + +* matrix: the initial transformation to be applied to the sub object. + +* transform: whether to transform the sub object using this object's placement + +* depth: current recursive depth + + + + + + +getSubObjectList(subname) + +Return a list of objects referenced by a given subname including this object + + + + + + getSubObjects(reason=0): Return subname reference of all sub-objects + + + + + +getLinkedObject(recursive=True, matrix=None, transform=True, depth=0) +Returns the linked object if there is one, or else return itself + +* recusive: whether to recursively resolve the links + +* transform: whether to transform the sub object using this object's placement + +* matrix: If not none, this sepcifies the initial transformation to be applied +to the sub object. And cause the method to return a tuple (object, matrix) +containing the accumulated transformation matrix + +* depth: current recursive depth + + + + + + +setElementVisible(element,visible): Set the visibility of a child element +Return -1 if element visibility is not supported, 0 if element not found, 1 if success + + + + + + +isElementVisible(element): Check if a child element is visible +Return -1 if element visibility is not supported or element not found, 0 if invisible, or else 1 + + + + + + Return true to indicate the object having child elements + + Returns the group the object is in or None if it is not part of a group. @@ -79,6 +163,19 @@ Get all paths from this object to another object following the OutList. + + + +resolve(subname) -- resolve the sub object + +Returns a tuple (subobj,parent,elementName,subElement), where 'subobj' is the +last object referenced in 'subname', and 'parent' is the direct parent of +'subobj', and 'elementName' is the name of the subobj, which can be used +to call parent.isElementVisible/setElementVisible(). 'subElement' is the +non-object sub-element name if any. + + + A list of all objects this object links to. @@ -103,6 +200,12 @@ + + + Return the document name and internal name of this object + + + Return the internal name of this object @@ -129,5 +232,17 @@ or None if the GUI is not up + + + The unique identifier (among its document) of this object + + + + + + A List of tuple(parent,subname) holding all parents to this object + + + diff --git a/src/App/DocumentObjectPyImp.cpp b/src/App/DocumentObjectPyImp.cpp index 8d6addac70..a74742a4c7 100644 --- a/src/App/DocumentObjectPyImp.cpp +++ b/src/App/DocumentObjectPyImp.cpp @@ -22,6 +22,8 @@ #include "PreCompiled.h" +#include +#include #include "DocumentObject.h" #include "Document.h" #include "Expression.h" @@ -53,6 +55,11 @@ Py::String DocumentObjectPy::getName(void) const return Py::String(std::string(internal)); } +Py::String DocumentObjectPy::getFullName(void) const +{ + return Py::String(getDocumentObjectPtr()->getFullName()); +} + Py::Object DocumentObjectPy::getDocument(void) const { DocumentObject* object = this->getDocumentObjectPtr(); @@ -132,8 +139,21 @@ PyObject* DocumentObjectPy::supportedProperties(PyObject *args) PyObject* DocumentObjectPy::touch(PyObject * args) { - if (!PyArg_ParseTuple(args, "")) // convert args: Python->C + char *propName = 0; + if (!PyArg_ParseTuple(args, "|s",&propName)) // convert args: Python->C return NULL; // NULL triggers exception + if(propName) { + if(!propName[0]) { + getDocumentObjectPtr()->touch(true); + Py_Return; + } + auto prop = getDocumentObjectPtr()->getPropertyByName(propName); + if(!prop) + throw Py::RuntimeError("Property not found"); + prop->touch(); + Py_Return; + } + getDocumentObjectPtr()->touch(); Py_Return; } @@ -204,17 +224,19 @@ Py::Object DocumentObjectPy::getViewObject(void) const // in v0.14+, the GUI module can be loaded in console mode (but doesn't have all its document methods) return Py::None(); } + if(!getDocumentObjectPtr()->getDocument()) { + throw Py::RuntimeError("Object has no document"); + } + const char* internalName = getDocumentObjectPtr()->getNameInDocument(); + if (!internalName) { + throw Py::RuntimeError("Object has been removed from document"); + } Py::Callable method(module.getAttr("getDocument")); Py::Tuple arg(1); arg.setItem(0, Py::String(getDocumentObjectPtr()->getDocument()->getName())); Py::Object doc = method.apply(arg); method = doc.getAttr("getObject"); - const char* internalName = getDocumentObjectPtr()->getNameInDocument(); - if (!internalName) { - throw Py::RuntimeError("Object has been removed from document"); - } - arg.setItem(0, Py::String(internalName)); Py::Object obj = method.apply(arg); return obj; @@ -346,6 +368,247 @@ PyObject* DocumentObjectPy::recompute(PyObject *args) } } +PyObject* DocumentObjectPy::getSubObject(PyObject *args, PyObject *keywds) +{ + PyObject *obj; + short retType = 0; + PyObject *pyMat = Py_None; + PyObject *doTransform = Py_True; + short depth = 0; + static char *kwlist[] = {"subname","retType","matrix","transform","depth", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|hOOh", kwlist, + &obj,&retType,&pyMat,&doTransform,&depth)) + return 0; + + if(retType<0 || retType>6) { + PyErr_SetString(PyExc_TypeError, "invalid retType, can only be integer 0~6"); + return 0; + } + + std::vector subs; + bool single=true; + if (PyUnicode_Check(obj)) { +#if PY_MAJOR_VERSION >= 3 + subs.push_back(PyUnicode_AsUTF8(obj)); +#else + PyObject* unicode = PyUnicode_AsUTF8String(obj); + subs.push_back(PyString_AsString(unicode)); + Py_DECREF(unicode); + } + else if (PyString_Check(obj)) { + subs.push_back(PyString_AsString(obj)); +#endif + } else if (PySequence_Check(obj)) { + single=false; + Py::Sequence shapeSeq(obj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyUnicode_Check(item)) { +#if PY_MAJOR_VERSION >= 3 + subs.push_back(PyUnicode_AsUTF8(item)); +#else + PyObject* unicode = PyUnicode_AsUTF8String(item); + subs.push_back(PyString_AsString(unicode)); + Py_DECREF(unicode); + } + else if (PyString_Check(item)) { + subs.push_back(PyString_AsString(item)); +#endif + }else{ + PyErr_SetString(PyExc_TypeError, "non-string object in sequence"); + return 0; + } + } + }else{ + PyErr_SetString(PyExc_TypeError, "subname must be either a string or sequence of string"); + return 0; + } + + bool transform = PyObject_IsTrue(doTransform); + + struct SubInfo { + App::DocumentObject *sobj; + Py::Object obj; + Py::Object pyObj; + Base::Matrix4D mat; + SubInfo(const Base::Matrix4D &mat):mat(mat){} + }; + + Base::Matrix4D mat; + if(pyMat!=Py_None) { + if(!PyObject_TypeCheck(pyMat,&Base::MatrixPy::Type)) { + PyErr_SetString(PyExc_TypeError, "expect argument 'matrix' to be of type Base.Matrix"); + return 0; + } + mat = *static_cast(pyMat)->getMatrixPtr(); + } + + PY_TRY { + std::vector ret; + for(const auto &sub : subs) { + ret.emplace_back(mat); + auto &info = ret.back(); + PyObject *pyObj = 0; + info.sobj = getDocumentObjectPtr()->getSubObject( + sub.c_str(),retType!=0&&retType!=2?0:&pyObj,&info.mat,transform,depth); + if(pyObj) + info.pyObj = Py::Object(pyObj,true); + if(info.sobj) + info.obj = Py::Object(info.sobj->getPyObject(),true); + } + if(ret.empty()) + Py_Return; + + if(single) { + if(retType==0) + return Py::new_reference_to(ret[0].pyObj); + else if(retType==1 && pyMat==Py_None) + return Py::new_reference_to(ret[0].obj); + else if(!ret[0].sobj) + Py_Return; + else if(retType==3) + return Py::new_reference_to(Py::Placement(Base::Placement(ret[0].mat))); + else if(retType==4) + return Py::new_reference_to(Py::Matrix(ret[0].mat)); + else if(retType==5 || retType==6) { + ret[0].sobj->getLinkedObject(true,&ret[0].mat,false); + if(retType==5) + return Py::new_reference_to(Py::Placement(Base::Placement(ret[0].mat))); + else + return Py::new_reference_to(Py::Matrix(ret[0].mat)); + } + Py::Tuple rret(retType==1?2:3); + rret.setItem(0,ret[0].obj); + rret.setItem(1,Py::Object(new Base::MatrixPy(ret[0].mat))); + if(retType!=1) + rret.setItem(2,ret[0].pyObj); + return Py::new_reference_to(rret); + } + Py::Tuple tuple(ret.size()); + for(size_t i=0;igetLinkedObject(true,&ret[i].mat,false); + if(retType==5) + tuple.setItem(i,Py::Placement(Base::Placement(ret[i].mat))); + else + tuple.setItem(i,Py::Matrix(ret[i].mat)); + } else { + Py::Tuple rret(retType==1?2:3); + rret.setItem(0,ret[i].obj); + rret.setItem(1,Py::Object(new Base::MatrixPy(ret[i].mat))); + if(retType!=1) + rret.setItem(2,ret[i].pyObj); + tuple.setItem(i,rret); + } + } + return Py::new_reference_to(tuple); + }PY_CATCH +} + +PyObject* DocumentObjectPy::getSubObjectList(PyObject *args) { + const char *subname; + if (!PyArg_ParseTuple(args, "s", &subname)) + return NULL; + Py::List res; + PY_TRY { + for(auto o : getDocumentObjectPtr()->getSubObjectList(subname)) + res.append(Py::asObject(o->getPyObject())); + return Py::new_reference_to(res); + }PY_CATCH +} + +PyObject* DocumentObjectPy::getSubObjects(PyObject *args) { + int reason = 0; + if (!PyArg_ParseTuple(args, "|i", &reason)) + return NULL; + + PY_TRY { + auto names = getDocumentObjectPtr()->getSubObjects(reason); + Py::Tuple pyObjs(names.size()); + for(size_t i=0;i(pyMat)->getMatrixPtr(); + mat = &_mat; + } + + PY_TRY { + auto linked = getDocumentObjectPtr()->getLinkedObject( + PyObject_IsTrue(recursive), mat, PyObject_IsTrue(transform),depth); + if(!linked) + linked = getDocumentObjectPtr(); + auto pyObj = Py::Object(linked->getPyObject(),true); + if(mat) { + Py::Tuple ret(2); + ret.setItem(0,pyObj); + ret.setItem(1,Py::Object(new Base::MatrixPy(*mat))); + return Py::new_reference_to(ret); + } + return Py::new_reference_to(pyObj); + } PY_CATCH; +} + +PyObject* DocumentObjectPy::isElementVisible(PyObject *args) +{ + char *element = 0; + if (!PyArg_ParseTuple(args, "s", &element)) + return NULL; + PY_TRY { + return Py_BuildValue("h", getDocumentObjectPtr()->isElementVisible(element)); + } PY_CATCH; +} + +PyObject* DocumentObjectPy::setElementVisible(PyObject *args) +{ + char *element = 0; + PyObject *visible = Py_True; + if (!PyArg_ParseTuple(args, "s|O", &element,&visible)) + return NULL; + PY_TRY { + return Py_BuildValue("h", getDocumentObjectPtr()->setElementVisible(element,PyObject_IsTrue(visible))); + } PY_CATCH; +} + +PyObject* DocumentObjectPy::hasChildElement(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return NULL; + PY_TRY { + return Py_BuildValue("O", getDocumentObjectPtr()->hasChildElement()?Py_True:Py_False); + } PY_CATCH; +} + PyObject* DocumentObjectPy::getParentGroup(PyObject *args) { if (!PyArg_ParseTuple(args, "")) @@ -467,3 +730,41 @@ int DocumentObjectPy::setCustomAttributes(const char* attr, PyObject *obj) return 0; } + +Py::Int DocumentObjectPy::getID() const { + return Py::Int(getDocumentObjectPtr()->getID()); +} + +Py::Boolean DocumentObjectPy::getRemoving() const { + return Py::Boolean(getDocumentObjectPtr()->testStatus(ObjectStatus::Remove)); +} + +PyObject *DocumentObjectPy::resolve(PyObject *args) +{ + const char *subname; + if (!PyArg_ParseTuple(args, "s",&subname)) + return NULL; // NULL triggers exception + + PY_TRY { + std::string elementName; + const char *subElement = 0; + App::DocumentObject *parent = 0; + auto obj = getDocumentObjectPtr()->resolve(subname,&parent,&elementName,&subElement); + + Py::Tuple ret(4); + ret.setItem(0,obj?Py::Object(obj->getPyObject(),true):Py::None()); + ret.setItem(1,parent?Py::Object(parent->getPyObject(),true):Py::None()); + ret.setItem(2,Py::String(elementName.c_str())); + ret.setItem(3,Py::String(subElement?subElement:"")); + return Py::new_reference_to(ret); + } PY_CATCH; + + Py_Return; +} + +Py::List DocumentObjectPy::getParents() const { + Py::List ret; + for(auto &v : getDocumentObjectPtr()->getParents()) + ret.append(Py::TupleN(Py::Object(v.first->getPyObject(),true),Py::String(v.second))); + return ret; +} diff --git a/src/App/DocumentPy.xml b/src/App/DocumentPy.xml index 7d989064df..eea4e29568 100644 --- a/src/App/DocumentPy.xml +++ b/src/App/DocumentPy.xml @@ -63,9 +63,20 @@ Commit an Undo/Redo transaction - + - Add an object with given type and name to the document + addObject(type, name=None, objProxy=None, viewProxy=None, attach=False, viewType=None) + +Add an object to document + +type (String): the type of the document object to create. +name (String): the optional name of the new object. +objProxy (Object): the Python binding object to attach to the new document object. +viewProxy (Object): the Python binding object to attach the view provider of this object. +attach (Boolean): if True, then bind the document object first before adding to the document + to allow Python code to override view provider type. Once bound, and before adding to + the document, it will try to call Python binding object's attach(obj) method. +viewType (String): override the view provider type directly, only effective when attach is False. @@ -123,6 +134,16 @@ Return a list of objects that match the specified type and name. Both parameters are optional. + + + + getLinksTo(obj, options=0, maxCount=0): return objects linked to 'obj' + + options: 1: recursive, 2: check link array. Options can combine. + maxCount: to limit the number of links returned + + + A list of supported types of objects diff --git a/src/App/DocumentPyImp.cpp b/src/App/DocumentPyImp.cpp index cd27cd4900..a3212c7c11 100644 --- a/src/App/DocumentPyImp.cpp +++ b/src/App/DocumentPyImp.cpp @@ -32,6 +32,7 @@ #include "DocumentObject.h" #include "DocumentObjectPy.h" #include "MergeDocuments.h" +#include "PropertyLinks.h" // inclusion of the generated files (generated By DocumentPy.xml) #include "DocumentPy.h" @@ -55,20 +56,12 @@ PyObject* DocumentPy::save(PyObject * args) if (!PyArg_ParseTuple(args, "")) // convert args: Python->C return NULL; // NULL triggers exception - try { + PY_TRY { if (!getDocumentPtr()->save()) { PyErr_SetString(PyExc_ValueError, "Object attribute 'FileName' is not set"); return NULL; } - } - catch (const Base::FileException& e) { - PyErr_SetString(PyExc_IOError, e.what()); - return 0; - } - catch (const Base::Exception& e) { - PyErr_SetString(PyExc_RuntimeError, e.what()); - return 0; - } + } PY_CATCH; const char* filename = getDocumentPtr()->FileName.getValue(); Base::FileInfo fi(filename); @@ -89,28 +82,10 @@ PyObject* DocumentPy::saveAs(PyObject * args) std::string utf8Name = fn; PyMem_Free(fn); - try { - if (!getDocumentPtr()->saveAs(utf8Name.c_str())) { - PyErr_SetString(PyExc_ValueError, "Object attribute 'FileName' is not set"); - return NULL; - } - } - catch (const Base::FileException& e) { - PyErr_SetString(PyExc_IOError, e.what()); - return 0; - } - catch (const Base::Exception& e) { - PyErr_SetString(PyExc_RuntimeError, e.what()); - return 0; - } - - Base::FileInfo fi(utf8Name); - if (!fi.isReadable()) { - PyErr_Format(PyExc_IOError, "No such file or directory: '%s'", utf8Name.c_str()); - return NULL; - } - - Py_Return; + PY_TRY { + getDocumentPtr()->saveAs(utf8Name.c_str()); + Py_Return; + }PY_CATCH } PyObject* DocumentPy::saveCopy(PyObject * args) @@ -118,18 +93,11 @@ PyObject* DocumentPy::saveCopy(PyObject * args) char* fn; if (!PyArg_ParseTuple(args, "s", &fn)) // convert args: Python->C return NULL; // NULL triggers exception - if (!getDocumentPtr()->saveCopy(fn)) { - PyErr_Format(PyExc_ValueError, "Object attribute 'FileName' is not set"); - return NULL; - } - Base::FileInfo fi(fn); - if (!fi.isReadable()) { - PyErr_Format(PyExc_IOError, "No such file or directory: '%s'", fn); - return NULL; - } - - Py_Return; + PY_TRY { + getDocumentPtr()->saveCopy(fn); + Py_Return; + }PY_CATCH } PyObject* DocumentPy::load(PyObject * args) @@ -219,17 +187,33 @@ PyObject* DocumentPy::exportGraphviz(PyObject * args) } } -PyObject* DocumentPy::addObject(PyObject *args) +PyObject* DocumentPy::addObject(PyObject *args, PyObject *kwd) { - char *sType,*sName=0; + char *sType,*sName=0,*sViewType=0; PyObject* obj=0; PyObject* view=0; - if (!PyArg_ParseTuple(args, "s|sOO", &sType,&sName,&obj,&view)) // convert args: Python->C - return NULL; // NULL triggers exception + PyObject *attach=Py_False; + static char *kwlist[] = {"type","name","objProxy","viewProxy","attach","viewType",NULL}; + if (!PyArg_ParseTupleAndKeywords(args,kwd,"s|sOOOs", + kwlist, &sType,&sName,&obj,&view,&attach,&sViewType)) + return NULL; - DocumentObject *pcFtr; + DocumentObject *pcFtr = 0; - pcFtr = getDocumentPtr()->addObject(sType,sName); + if(!obj || !PyObject_IsTrue(attach)) { + pcFtr = getDocumentPtr()->addObject(sType,sName,true,sViewType); + }else{ + Base::BaseClass* base = static_cast(Base::Type::createInstanceByName(sType,true)); + if(base) { + if (!base->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) { + delete base; + std::stringstream str; + str << "'" << sType << "' is not a document object type"; + throw Base::TypeError(str.str()); + } + pcFtr = static_cast(base); + } + } if (pcFtr) { // Allows to hide the handling with Proxy in client python code if (obj) { @@ -243,6 +227,21 @@ PyObject* DocumentPy::addObject(PyObject *args) } pyftr.setAttr("Proxy", pyobj); + if(PyObject_IsTrue(attach)) { + getDocumentPtr()->addObject(pcFtr,sName); + + try { + Py::Callable method(pyobj.getAttr("attach")); + if(!method.isNone()) { + Py::TupleN arg(pyftr); + method.apply(arg); + } + }catch (Py::Exception&) { + Base::PyException e; + e.ReportException(); + } + } + // if a document class is set we also need a view provider defined which must be // something different to None Py::Object pyvp; @@ -416,11 +415,14 @@ PyObject* DocumentPy::recompute(PyObject * args) PyObject* DocumentPy::getObject(PyObject *args) { - char *sName; - if (!PyArg_ParseTuple(args, "s",&sName)) // convert args: Python->C - return NULL; // NULL triggers exception + long id = -1; + char *sName = 0; + if (!PyArg_ParseTuple(args, "s",&sName)) { // convert args: Python->C + if(!PyArg_ParseTuple(args, "l", &id)) + return NULL; // NULL triggers exception + } - DocumentObject *pcFtr = getDocumentPtr()->getObject(sName); + DocumentObject *pcFtr = sName?getDocumentPtr()->getObject(sName):getDocumentPtr()->getObjectByID(id); if (pcFtr) return pcFtr->getPyObject(); else @@ -695,3 +697,31 @@ int DocumentPy::setCustomAttributes(const char* attr, PyObject *) return 0; } + +PyObject* DocumentPy::getLinksTo(PyObject *args) +{ + PyObject *pyobj = Py_None; + int options = 0; + short count = 0; + if (!PyArg_ParseTuple(args, "|Oih", &pyobj,&options, &count)) + return NULL; + + PY_TRY { + DocumentObject *obj = 0; + if(pyobj!=Py_None) { + if(!PyObject_TypeCheck(pyobj,&DocumentObjectPy::Type)) { + PyErr_SetString(PyExc_TypeError, "Expect the first argument of type document object"); + return 0; + } + obj = static_cast(pyobj)->getDocumentObjectPtr(); + } + std::set links; + getDocumentPtr()->getLinksTo(links,obj,options,count); + Py::Tuple ret(links.size()); + int i=0; + for(auto o : links) + ret.setItem(i++,Py::Object(o->getPyObject(),true)); + return Py::new_reference_to(ret); + }PY_CATCH +} + diff --git a/src/App/Extension.h b/src/App/Extension.h index 33d1853555..8889049e4b 100644 --- a/src/App/Extension.h +++ b/src/App/Extension.h @@ -301,17 +301,24 @@ private: }; // Property define -#define EXTENSION_ADD_PROPERTY(_prop_, _defaultval_) \ +#define _EXTENSION_ADD_PROPERTY(_name, _prop_, _defaultval_) \ do { \ this->_prop_.setValue _defaultval_;\ - propertyData.addProperty(static_cast(this), #_prop_, &this->_prop_); \ + propertyData.addProperty(static_cast(this), _name, &this->_prop_); \ + } while (0) + + +#define EXTENSION_ADD_PROPERTY(_prop_, _defaultval_) \ + _EXTENSION_ADD_PROPERTY(#_prop_, _prop_, _defaultval_) + +#define _EXTENSION_ADD_PROPERTY_TYPE(_name, _prop_, _defaultval_, _group_,_type_,_Docu_) \ + do { \ + this->_prop_.setValue _defaultval_;\ + propertyData.addProperty(static_cast(this), _name, &this->_prop_, (_group_),(_type_),(_Docu_)); \ } while (0) #define EXTENSION_ADD_PROPERTY_TYPE(_prop_, _defaultval_, _group_,_type_,_Docu_) \ - do { \ - this->_prop_.setValue _defaultval_;\ - propertyData.addProperty(static_cast(this), #_prop_, &this->_prop_, (_group_),(_type_),(_Docu_)); \ - } while (0) + _EXTENSION_ADD_PROPERTY_TYPE(#_prop_, _prop_, _defaultval_, _group_,_type_,_Docu_) /** diff --git a/src/App/ExtensionContainer.cpp b/src/App/ExtensionContainer.cpp index 461566a42f..6e683ada17 100644 --- a/src/App/ExtensionContainer.cpp +++ b/src/App/ExtensionContainer.cpp @@ -94,8 +94,8 @@ bool ExtensionContainer::hasExtension(const std::string& name) const { } -Extension* ExtensionContainer::getExtension(Base::Type t, bool derived) const { - +Extension* ExtensionContainer::getExtension(Base::Type t, bool derived, bool no_except) const { + auto result = _extensions.find(t); if((result == _extensions.end()) && derived) { //we need to check for derived types @@ -103,7 +103,7 @@ Extension* ExtensionContainer::getExtension(Base::Type t, bool derived) const { if(entry.first.isDerivedFrom(t)) return entry.second; } - + if(no_except) return 0; //if we arrive here we don't have anything matching throw Base::TypeError("ExtensionContainer::getExtension: No extension of given type available"); } @@ -111,6 +111,7 @@ Extension* ExtensionContainer::getExtension(Base::Type t, bool derived) const { return result->second; } else { + if(no_except) return 0; //if we arrive here we don't have anything matching throw Base::TypeError("ExtensionContainer::getExtension: No extension of given type available"); } diff --git a/src/App/ExtensionContainer.h b/src/App/ExtensionContainer.h index 4f0cd0ba52..5cb141e36c 100644 --- a/src/App/ExtensionContainer.h +++ b/src/App/ExtensionContainer.h @@ -127,24 +127,24 @@ public: bool hasExtension(Base::Type, bool derived=true) const; //returns first of type (or derived from if set to true) and throws otherwise bool hasExtension(const std::string& name) const; //this version does not check derived classes bool hasExtensions() const; - App::Extension* getExtension(Base::Type, bool derived = true) const; + App::Extension* getExtension(Base::Type, bool derived = true, bool no_except=false) const; App::Extension* getExtension(const std::string& name) const; //this version does not check derived classes //returns first of type (or derived from) and throws otherwise template - ExtensionT* getExtensionByType() const { - return dynamic_cast(getExtension(ExtensionT::getExtensionClassTypeId())); + ExtensionT* getExtensionByType(bool no_except=false, bool derived=true) const { + return static_cast(getExtension(ExtensionT::getExtensionClassTypeId(),derived,no_except)); }; //get all extensions which have the given base class std::vector getExtensionsDerivedFrom(Base::Type type) const; template std::vector getExtensionsDerivedFromType() const { - auto vec = getExtensionsDerivedFrom(ExtensionT::getExtensionClassTypeId()); std::vector typevec; - for(auto ext : vec) - typevec.push_back(dynamic_cast(ext)); - + for(auto entry : _extensions) { + if(entry.first.isDerivedFrom(ExtensionT::getExtensionClassTypeId())) + typevec.push_back(static_cast(entry.second)); + } return typevec; }; diff --git a/src/App/GeoFeatureGroupExtension.cpp b/src/App/GeoFeatureGroupExtension.cpp index c311ab491e..0024cd5d3f 100644 --- a/src/App/GeoFeatureGroupExtension.cpp +++ b/src/App/GeoFeatureGroupExtension.cpp @@ -34,6 +34,7 @@ #include "Origin.h" #include "OriginGroupExtension.h" #include +#include //#include "GeoFeatureGroupPy.h" //#include "FeaturePythonPyImp.h" @@ -100,11 +101,9 @@ DocumentObject* GeoFeatureGroupExtension::getGroupOfObject(const DocumentObject* //There is a chance that a derived geofeaturegroup links with a local link and hence is not //the parent group even though it links to the object. We use hasObject as one and only truth //if it has the object within the group - if(inObj->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId()) && - inObj->getExtensionByType()->hasObject(obj)) { - + auto group = inObj->getExtensionByType(true); + if(group && group->hasObject(obj)) return inObj; - } } return nullptr; @@ -124,10 +123,9 @@ Base::Placement GeoFeatureGroupExtension::recursiveGroupPlacement(GeoFeatureGrou auto inList = group->getExtendedObject()->getInList(); for(auto* link : inList) { - if(link->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())){ - if (link->getExtensionByType()->hasObject(group->getExtendedObject())) - return recursiveGroupPlacement(link->getExtensionByType()) * group->placement().getValue(); - } + auto parent = link->getExtensionByType(true); + if(parent && parent->hasObject(group->getExtendedObject())) + return recursiveGroupPlacement(parent) * group->placement().getValue(); } return group->placement().getValue(); @@ -205,9 +203,10 @@ void GeoFeatureGroupExtension::extensionOnChanged(const Property* p) { //would return anyone of it and hence it is possible that we miss an error. We need a custom check auto list = obj->getInList(); for (auto in : list) { - if(in->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId()) && //is GeoFeatureGroup? - in != getExtendedObject() && //is a different one? - in->getExtensionByType()->hasObject(obj)) { //is not a non-grouping link? + if(in == getExtendedObject()) + continue; + auto parent = in->getExtensionByType(true); + if(parent && parent->hasObject(obj)) { error = true; corrected.erase(std::remove(corrected.begin(), corrected.end(), obj), corrected.end()); } @@ -285,8 +284,6 @@ std::vector< DocumentObject* > GeoFeatureGroupExtension::getScopedObjectsFromLin return result; } - - void GeoFeatureGroupExtension::getCSOutList(const App::DocumentObject* obj, std::vector< DocumentObject* >& vec) { @@ -373,6 +370,59 @@ void GeoFeatureGroupExtension::recursiveCSRelevantLinks(const DocumentObject* ob } } +bool GeoFeatureGroupExtension::extensionGetSubObject(DocumentObject *&ret, const char *subname, + PyObject **pyObj, Base::Matrix4D *mat, bool transform, int depth) const +{ + ret = 0; + const char *dot; + if(!subname || *subname==0) { + auto obj = dynamic_cast(getExtendedContainer()); + ret = const_cast(obj); + if(mat && transform) + *mat *= const_cast(this)->placement().getValue().toMatrix(); + }else if((dot=strchr(subname,'.'))) { + if(subname[0]!='$') + ret = Group.find(std::string(subname,dot)); + else{ + std::string name = std::string(subname+1,dot); + for(auto child : Group.getValues()) { + if(name == child->Label.getStrValue()){ + ret = child; + break; + } + } + } + if(ret) { + if(dot) ++dot; + if(dot && *dot && !ret->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) { + // Consider this + // Body + // | -- Pad + // | -- Sketch + // + // Suppose we want to getSubObject(Body,"Pad.Sketch.") + // Because of the special property of geo feature group, both + // Pad and Sketch are children of Body, so we can't call + // getSubObject(Pad,"Sketch.") as this will transform Sketch + // using Pad's placement. + // + const char *next = strchr(dot,'.'); + if(next) { + App::DocumentObject *nret=0; + extensionGetSubObject(nret,dot,pyObj,mat,transform,depth+1); + if(nret) { + ret = nret; + return true; + } + } + } + if(mat && transform) + *mat *= const_cast(this)->placement().getValue().toMatrix(); + ret = ret->getSubObject(dot?dot:"",pyObj,mat,true,depth+1); + } + } + return true; +} bool GeoFeatureGroupExtension::areLinksValid(const DocumentObject* obj) { @@ -448,6 +498,14 @@ void GeoFeatureGroupExtension::getInvalidLinkObjects(const DocumentObject* obj, } } +bool GeoFeatureGroupExtension::extensionGetSubObjects(std::vector &ret, int) const { + for(auto obj : Group.getValues()) { + if(obj && obj->getNameInDocument() && !obj->testStatus(ObjectStatus::GeoExcluded)) + ret.push_back(std::string(obj->getNameInDocument())+'.'); + } + return true; +} + // Python feature --------------------------------------------------------- diff --git a/src/App/GeoFeatureGroupExtension.h b/src/App/GeoFeatureGroupExtension.h index 7db3eac5c9..17fc12362d 100644 --- a/src/App/GeoFeatureGroupExtension.h +++ b/src/App/GeoFeatureGroupExtension.h @@ -50,6 +50,7 @@ namespace App */ class AppExport GeoFeatureGroupExtension : public App::GroupExtension { + typedef App::GroupExtension inherited; EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(App::GeoFeatureGroupExtension); public: @@ -95,6 +96,11 @@ public: return obj->hasExtension(GroupExtension::getExtensionClassTypeId()) && !obj->hasExtension(GeoFeatureGroupExtension::getExtensionClassTypeId()); } + + virtual bool extensionGetSubObject(DocumentObject *&ret, const char *subname, PyObject **pyObj, + Base::Matrix4D *mat, bool transform, int depth) const override; + + virtual bool extensionGetSubObjects(std::vector &ret, int reason) const override; virtual std::vector< DocumentObject* > addObjects(std::vector< DocumentObject* > obj) override; virtual std::vector< DocumentObject* > removeObjects(std::vector< DocumentObject* > obj) override; diff --git a/src/App/GroupExtension.cpp b/src/App/GroupExtension.cpp index 21341fec2e..45a956b837 100644 --- a/src/App/GroupExtension.cpp +++ b/src/App/GroupExtension.cpp @@ -33,6 +33,7 @@ #include "FeaturePythonPyImp.h" #include "GeoFeatureGroupExtension.h" #include +#include using namespace App; @@ -251,15 +252,12 @@ bool GroupExtension::recursiveHasObject(const DocumentObject* obj, const GroupEx return false; } - bool GroupExtension::isChildOf(const GroupExtension* group, bool recursive) const { return group->hasObject(getExtendedObject(), recursive); } - - -std::vector GroupExtension::getObjects() const +const std::vector &GroupExtension::getObjects() const { return Group.getValues(); } @@ -348,6 +346,41 @@ void GroupExtension::extensionOnChanged(const Property* p) { App::Extension::extensionOnChanged(p); } +bool GroupExtension::extensionGetSubObject(DocumentObject *&ret, const char *subname, + PyObject **pyObj, Base::Matrix4D *mat, bool /*transform*/, int depth) const +{ + const char *dot; + if(!subname || *subname==0) { + auto obj = Base::freecad_dynamic_cast(getExtendedContainer()); + ret = const_cast(obj); + return true; + } + dot=strchr(subname,'.'); + if(!dot) + return false; + if(subname[0]!='$') + ret = Group.find(std::string(subname,dot)); + else{ + std::string name = std::string(subname+1,dot); + for(auto child : Group.getValues()) { + if(name == child->Label.getStrValue()){ + ret = child; + break; + } + } + } + if(!ret) + return false; + return ret->getSubObject(dot+1,pyObj,mat,true,depth+1); +} + +bool GroupExtension::extensionGetSubObjects(std::vector &ret, int) const { + for(auto obj : Group.getValues()) { + if(obj && obj->getNameInDocument()) + ret.push_back(std::string(obj->getNameInDocument())+'.'); + } + return true; +} namespace App { EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::GroupExtensionPython, App::GroupExtension) diff --git a/src/App/GroupExtension.h b/src/App/GroupExtension.h index 772f8e5592..c1c936b317 100644 --- a/src/App/GroupExtension.h +++ b/src/App/GroupExtension.h @@ -38,6 +38,7 @@ class GroupExtensionPy; class AppExport GroupExtension : public DocumentObjectExtension { EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(App::GroupExtension); + typedef DocumentObjectExtension inherited; public: /// Constructor @@ -91,7 +92,7 @@ public: bool isChildOf(const GroupExtension* group, bool recursive = true) const; /** Returns a list of all objects this group does have. */ - std::vector getObjects() const; + const std::vector &getObjects() const; /** Returns a list of all objects of \a typeId this group does have. */ std::vector getObjectsOfType(const Base::Type& typeId) const; @@ -109,7 +110,12 @@ public: virtual PyObject* getExtensionPyObject(void) override; virtual void extensionOnChanged(const Property* p) override; - + + virtual bool extensionGetSubObject(DocumentObject *&ret, const char *subname, + PyObject **pyObj, Base::Matrix4D *mat, bool transform, int depth) const override; + + virtual bool extensionGetSubObjects(std::vector &ret, int reason) const override; + /// Properties PropertyLinkList Group; diff --git a/src/App/Origin.cpp b/src/App/Origin.cpp index b626987180..b72ad3226e 100644 --- a/src/App/Origin.cpp +++ b/src/App/Origin.cpp @@ -69,7 +69,7 @@ App::OriginFeature *Origin::getOriginFeature( const char *role) const { } else { std::stringstream err; - err << "Origin \"" << getNameInDocument () << "\" doesn't contain feature with role \"" + err << "Origin \"" << getFullName () << "\" doesn't contain feature with role \"" << role << '"'; throw Base::RuntimeError ( err.str().c_str () ); } @@ -81,7 +81,7 @@ App::Line *Origin::getAxis( const char *role ) const { return static_cast (feat); } else { std::stringstream err; - err << "Origin \"" << getNameInDocument () << "\" contains bad Axis object for role \"" + err << "Origin \"" << getFullName () << "\" contains bad Axis object for role \"" << role << '"'; throw Base::RuntimeError ( err.str().c_str () ); } @@ -93,7 +93,7 @@ App::Plane *Origin::getPlane( const char *role ) const { return static_cast (feat); } else { std::stringstream err; - err << "Origin \"" << getNameInDocument () << "\" comtains bad Plane object for role \"" + err << "Origin \"" << getFullName () << "\" comtains bad Plane object for role \"" << role << '"'; throw Base::RuntimeError ( err.str().c_str () ); } diff --git a/src/App/OriginGroupExtension.cpp b/src/App/OriginGroupExtension.cpp index b746067a1e..d0e061542d 100644 --- a/src/App/OriginGroupExtension.cpp +++ b/src/App/OriginGroupExtension.cpp @@ -53,19 +53,42 @@ App::Origin *OriginGroupExtension::getOrigin () const { if ( !originObj ) { std::stringstream err; - err << "Can't find Origin for \"" << getExtendedObject()->getNameInDocument () << "\""; + err << "Can't find Origin for \"" << getExtendedObject()->getFullName () << "\""; throw Base::RuntimeError ( err.str().c_str () ); } else if (! originObj->isDerivedFrom ( App::Origin::getClassTypeId() ) ) { std::stringstream err; - err << "Bad object \"" << originObj->getNameInDocument () << "\"(" << originObj->getTypeId().getName() - << ") linked to the Origin of \"" << getExtendedObject()->getNameInDocument () << "\""; + err << "Bad object \"" << originObj->getFullName () << "\"(" << originObj->getTypeId().getName() + << ") linked to the Origin of \"" << getExtendedObject()->getFullName () << "\""; throw Base::RuntimeError ( err.str().c_str () ); } else { return static_cast ( originObj ); } } +bool OriginGroupExtension::extensionGetSubObject(DocumentObject *&ret, const char *subname, + PyObject **pyObj, Base::Matrix4D *mat, bool transform, int depth) const +{ + App::DocumentObject *originObj = Origin.getValue (); + const char *dot; + if(originObj && originObj->getNameInDocument() && + subname && (dot=strchr(subname,'.'))) + { + bool found; + if(subname[0] == '$') + found = std::string(subname+1,dot)==originObj->Label.getValue(); + else + found = std::string(subname,dot)==originObj->getNameInDocument(); + if(found) { + if(mat && transform) + *mat *= const_cast(this)->placement().getValue().toMatrix(); + ret = originObj->getSubObject(dot+1,pyObj,mat,true,depth+1); + return true; + } + } + return GeoFeatureGroupExtension::extensionGetSubObject(ret,subname,pyObj,mat,transform,depth); +} + App::DocumentObject *OriginGroupExtension::getGroupOfObject (const DocumentObject* obj) { if(!obj) diff --git a/src/App/OriginGroupExtension.h b/src/App/OriginGroupExtension.h index fdeb7ca982..73c301b7a0 100644 --- a/src/App/OriginGroupExtension.h +++ b/src/App/OriginGroupExtension.h @@ -67,6 +67,9 @@ public: virtual std::vector addObjects(std::vector obj) override; virtual bool hasObject(const DocumentObject* obj, bool recursive = false) const override; + virtual bool extensionGetSubObject(DocumentObject *&ret, const char *subname, PyObject **pyObj, + Base::Matrix4D *mat, bool transform, int depth) const override; + protected: /// Checks integrity of the Origin virtual App::DocumentObjectExecReturn *extensionExecute () override; diff --git a/src/Mod/Part/App/DatumFeature.cpp b/src/Mod/Part/App/DatumFeature.cpp index 6d29603b60..75381c085c 100644 --- a/src/Mod/Part/App/DatumFeature.cpp +++ b/src/Mod/Part/App/DatumFeature.cpp @@ -26,6 +26,8 @@ #ifndef _PreComp_ #endif +#include "OCCError.h" +#include "PartPyCXX.h" #include "DatumFeature.h" @@ -58,6 +60,31 @@ TopoDS_Shape Datum::getShape() const return sh.getShape(); } +App::DocumentObject *Datum::getSubObject(const char *subname, + PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const +{ + // For the sake of simplicity, we don't bother to check for subname, just + // return the shape as it is, because a datum object only holds shape with + // one single geometry element. + (void)subname; + (void)depth; + + if(pmat && transform) + *pmat *= Placement.getValue().toMatrix(); + + if(!pyObj) + return const_cast(this); + + Base::PyGILStateLocker lock; + PY_TRY { + TopoShape ts(getShape().Located(TopLoc_Location())); + if(pmat && !ts.isNull()) + ts.transformShape(*pmat,false,true); + *pyObj = Py::new_reference_to(shape2pyshape(ts.getShape())); + return const_cast(this); + } PY_CATCH_OCC +} + Base::Vector3d Datum::getBasePoint () const { return Placement.getValue().getPosition(); } diff --git a/src/Mod/Part/App/DatumFeature.h b/src/Mod/Part/App/DatumFeature.h index 455f621a26..fb80ab82cb 100644 --- a/src/Mod/Part/App/DatumFeature.h +++ b/src/Mod/Part/App/DatumFeature.h @@ -49,11 +49,13 @@ public: virtual const char* getViewProviderName(void) const = 0; /// Return a shape including Placement representing the datum feature - TopoDS_Shape getShape() const; + virtual TopoDS_Shape getShape() const; /// Returns a point of the feature it counts as it's base virtual Base::Vector3d getBasePoint () const; + virtual App::DocumentObject *getSubObject(const char *subname, PyObject **pyObj, + Base::Matrix4D *mat, bool transform, int depth) const override; protected: void onDocumentRestored(); void handleChangedPropertyName(Base::XMLReader &reader, const char* TypeName, const char* PropName); diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index 93126d36ba..3650dea015 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -42,7 +42,8 @@ # include # include # include - +# include +# include # include # include # include @@ -59,13 +60,18 @@ #include #include #include +#include #include +#include +#include "PartPyCXX.h" #include "PartFeature.h" #include "PartFeaturePy.h" +#include "TopoShapePy.h" using namespace Part; +FC_LOG_LEVEL_INIT("Part",true,true); PROPERTY_SOURCE(Part::Feature, App::GeoFeature) @@ -112,20 +118,74 @@ PyObject *Feature::getPyObject(void) return Py::new_reference_to(PythonObject); } -std::vector Feature::getPySubObjects(const std::vector& NameVec) const +App::DocumentObject *Feature::getSubObject(const char *subname, + PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const { - try { - std::vector temp; - for (std::vector::const_iterator it=NameVec.begin();it!=NameVec.end();++it) { - PyObject *obj = Shape.getShape().getPySubShape((*it).c_str()); - if (obj) - temp.push_back(obj); - } - return temp; + // having '.' inside subname means it is referencing some children object, + // instead of any sub-element from ourself + if(subname && !Data::ComplexGeoData::isMappedElement(subname) && strchr(subname,'.')) + return App::DocumentObject::getSubObject(subname,pyObj,pmat,transform,depth); + + if(pmat && transform) + *pmat *= Placement.getValue().toMatrix(); + + if(!pyObj) { +#if 0 + if(subname==0 || *subname==0 || Shape.getShape().hasSubShape(subname)) + return const_cast(this); + return nullptr; +#else + // TopoShape::hasSubShape is kind of slow, let's cut outself some slack here. + return const_cast(this); +#endif } - catch (Standard_Failure&) { - //throw Py::ValueError(e.GetMessageString()); - return std::vector(); + + try { + TopoShape ts(Shape.getShape()); + bool doTransform = pmat && *pmat!=ts.getTransform(); + if(doTransform) { + ts.setShape(ts.getShape().Located(TopLoc_Location()),false); + ts.initCache(1); + } + if(subname && *subname && !ts.isNull()) + ts = ts.getSubTopoShape(subname,true); + if(doTransform && !ts.isNull()) { + static int sCopy = -1; + if(sCopy<0) { + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Part/General"); + sCopy = hGrp->GetBool("CopySubShape",false)?1:0; + } + bool copy = sCopy?true:false; + if(!copy) { + // Work around OCC bug on transforming circular edge with an + // offseted surface. The bug probably affect other shape type, + // too. + TopExp_Explorer exp(ts.getShape(),TopAbs_EDGE); + if(exp.More()) { + auto edge = TopoDS::Edge(exp.Current()); + exp.Next(); + if(!exp.More()) { + BRepAdaptor_Curve curve(edge); + copy = curve.GetType() == GeomAbs_Circle; + } + } + } + ts.transformShape(*pmat,copy,true); + } + *pyObj = Py::new_reference_to(shape2pyshape(ts)); + return const_cast(this); + }catch(Standard_Failure &e) { + std::ostringstream str; + Standard_CString msg = e.GetMessageString(); + str << typeid(e).name() << " "; + if (msg) {str << msg;} + else {str << "No OCCT Exception Message";} + str << ": " << getFullName(); + if (subname) + str << '.' << subname; + FC_ERR(str.str()); + return 0; } } diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index 7484c890f4..499e9b3c7b 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -31,6 +31,8 @@ #include // includes for findAllFacesCutBy() #include +#include +#include class gp_Dir; class BRepBuilderAPI_MakeShape; @@ -63,10 +65,12 @@ public: virtual const App::PropertyComplexGeoData* getPropertyOfGeometry() const; virtual PyObject* getPyObject(void); - virtual std::vector getPySubObjects(const std::vector&) const; TopLoc_Location getLocation() const; - + + virtual DocumentObject *getSubObject(const char *subname, PyObject **pyObj, + Base::Matrix4D *mat, bool transform, int depth) const override; + protected: /// recompute only this object virtual App::DocumentObjectExecReturn *recompute(void); diff --git a/src/Mod/PartDesign/App/Body.cpp b/src/Mod/PartDesign/App/Body.cpp index 849ccd381b..6ab7bc16ef 100644 --- a/src/Mod/PartDesign/App/Body.cpp +++ b/src/Mod/PartDesign/App/Body.cpp @@ -497,3 +497,29 @@ PyObject *Body::getPyObject(void) } return Py::new_reference_to(PythonObject); } + +std::vector Body::getSubObjects(int reason) const { + if(reason==GS_SELECT && !showTip) + return Part::BodyBase::getSubObjects(reason); + return {}; +} + +App::DocumentObject *Body::getSubObject(const char *subname, + PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const +{ + if(!pyObj || showTip || + (subname && !Data::ComplexGeoData::isMappedElement(subname) && strchr(subname,'.'))) + return Part::BodyBase::getSubObject(subname,pyObj,pmat,transform,depth); + + // We return the shape only if there are feature visibile inside + for(auto obj : Group.getValues()) { + if(obj->Visibility.getValue() && + obj->isDerivedFrom(PartDesign::Feature::getClassTypeId())) + { + return Part::BodyBase::getSubObject(subname,pyObj,pmat,transform,depth); + } + } + if(pmat && transform) + *pmat *= Placement.getValue().toMatrix(); + return const_cast(this); +} diff --git a/src/Mod/PartDesign/App/Body.h b/src/Mod/PartDesign/App/Body.h index 6d5e4032cf..7dd32ce75a 100644 --- a/src/Mod/PartDesign/App/Body.h +++ b/src/Mod/PartDesign/App/Body.h @@ -120,6 +120,13 @@ public: PyObject *getPyObject(void) override; + virtual std::vector getSubObjects(int reason=0) const override; + virtual App::DocumentObject *getSubObject(const char *subname, + PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const override; + + void setShowTip(bool enable) { + showTip = enable; + } protected: virtual void onSettingDocument() override; @@ -146,6 +153,7 @@ protected: private: boost::signals2::scoped_connection connection; + bool showTip = false; }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/App/DatumCS.cpp b/src/Mod/PartDesign/App/DatumCS.cpp index 734aba6fd3..e78eeb1126 100644 --- a/src/Mod/PartDesign/App/DatumCS.cpp +++ b/src/Mod/PartDesign/App/DatumCS.cpp @@ -28,6 +28,8 @@ # include #endif +#include +#include #include "DatumCS.h" using namespace PartDesign; @@ -77,3 +79,32 @@ Base::Vector3d CoordinateSystem::getZAxis() rot.multVec(Base::Vector3d(0,0,1), normal); return normal; } + +App::DocumentObject *CoordinateSystem::getSubObject(const char *subname, + PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int) const +{ + if(pmat && transform) + *pmat *= Placement.getValue().toMatrix(); + + if(!pyObj) + return const_cast(this); + + gp_Dir dir(0,0,1); + if(subname) { + if(strcmp(subname,"X")==0) + dir = gp_Dir(1,0,0); + else if(strcmp(subname,"Y")==0) + dir = gp_Dir(0,1,0); + } + + Base::PyGILStateLocker lock; + PY_TRY { + BRepBuilderAPI_MakeFace builder(gp_Pln(gp_Pnt(0,0,0), dir)); + Part::TopoShape ts(builder.Shape()); + if(pmat) + ts.transformShape(*pmat,false,true); + *pyObj = Py::new_reference_to(Part::shape2pyshape(ts)); + return const_cast(this); + } PY_CATCH_OCC +} + diff --git a/src/Mod/PartDesign/App/DatumCS.h b/src/Mod/PartDesign/App/DatumCS.h index fddf2c1e7d..47f763a795 100644 --- a/src/Mod/PartDesign/App/DatumCS.h +++ b/src/Mod/PartDesign/App/DatumCS.h @@ -44,6 +44,9 @@ public: Base::Vector3d getXAxis(); Base::Vector3d getYAxis(); Base::Vector3d getZAxis(); + + virtual App::DocumentObject *getSubObject(const char *subname, + PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const override; }; } //namespace PartDesign