From ff1d1cd3414360ebb66e656b6ee35dd102b72590 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 24 Jun 2019 16:30:08 +0800 Subject: [PATCH] App: add New APIs for future Link function DocumentObject: * getSubObject(): the most important API for Link to work with hierarchies. The function is a inspired from and replaces the getPySubObjects(). It returns a child object following a dot separated subname reference, and can optionally return accumulated transformation, and/or a python object of the refered sub-object/element. The default implementation here is to look for link type property, and search for the referenced object. This patch also include other specialized implementation of this API, such as (GeoFeature)GroupExtension (through extensionGetSubObject()), PartDesign::Body, and so on. A link type object is expected to call the linked object's getSubObject() for resolving. * getSubObjectList(): helper function to return a list of object referenced in the given subname. * getSubObjects(): return a list of subname references of all children objects. The purpose of this function is similar to ViewProvider::claimChildren(). Container type object is expected to implement this function. The reason it returns subname references instead of just object is to allow the container to skip hierarchies. For example, the Assembly3 container uses this to skip the constraint and element group. * getLinkedObject(), obtain the linked object, and optionally with the accumulated transformation. It is expected to return a linked object or the object itself if it is not a link. In case there are multiple levels of linking involved, this function allows the caller to retrieve the linked object recursively. * hasChildElement(), set/isElementVisible(), controls the children visibility for a group type object. Because the child object may be claimed by other objects, it is essential to have independent control of children visibilities. These APIs are designed to abstract how group manages the child visibility. For performance reason, these function are meant to control only the immediate child object. * resolve(), helper function to parse subname reference and resolve the final object, and optionally the immediate parent of the final object, the final object reference name (for calling `set/isElementVisible()`), and the subname reference if there is one. * touch(), add optional argument 'noRecompute' for better backward compatibility with the NoRecompute flag. By default, touch() causes recompute unless noRecompute is true * signalChanged/signalBeforeChange, two new signal for tracking changes of a specific object. * getViewProviderNameOverride(), return a string of the view provider type of this object. This allows Python class to override the view provider of an object. This feature will be used by ViewProviderLink which is designed to work with any object that has LinkBaseExtension. * canLinkProperties(), will be used by Gui::PropertyView to display linked object properties together with the object's own properties. * redirectSubname(), will be used by Gui::Tree to allow an object to redirect selection to some other object when (pre)selected in the tree view. * Visibility, new property serve as the same purpose as view provider property of the same name. It is added here so that App namespace code can check for visibility without Gui module. This is useful, for example, when constructing a compound shape of a container that respects the children visibility. * (has)hasHiddenMarker(), return or check for a special sub-element name used as marker for overriding sub-object visibility. Will be used by Gui::ViewProvider, it is put here for the same reason as adding Visibility property. * getID(), return object internal identifier. Each object is now assigned an integer identifier that is unique within its containing document. Document: * ShowHidden, new property to tell tree view whether to show hidden object items. * signalTouchedObject, new signal triggered when manually touch an object when calling its touch() function * getObjectByID(), get object by its identifier * addObject() is modified to allow overriding view provider * has/getLinksTo(), helper function to obtain links to a given object. Application: * checkLinkDepth(), helper function to check recursive depth for link traversal. The depth is checked against the total object count of all opened documents. The count (_objCount) is internally updated whenever object is added or removed. * has/getLinksTo(), same as Document::has/getLinksTo() but return links from all opened documents. GroupExtension/OriginGroupExtension/DatumFeature/DatumCS/Part::Feature: implement sepcialized getSubObject/getSubObjects(). --- src/App/Application.cpp | 52 ++++- src/App/Application.h | 50 ++++- src/App/ApplicationPy.cpp | 44 +++- src/App/Document.cpp | 209 +++++++++++++++--- src/App/Document.h | 31 ++- src/App/DocumentObject.cpp | 317 ++++++++++++++++++++++++++- src/App/DocumentObject.h | 184 +++++++++++++++- src/App/DocumentObjectExtension.cpp | 17 ++ src/App/DocumentObjectExtension.h | 29 ++- src/App/DocumentObjectPy.xml | 115 ++++++++++ src/App/DocumentObjectPyImp.cpp | 313 +++++++++++++++++++++++++- src/App/DocumentPy.xml | 25 ++- src/App/DocumentPyImp.cpp | 136 +++++++----- src/App/Extension.h | 19 +- src/App/ExtensionContainer.cpp | 7 +- src/App/ExtensionContainer.h | 14 +- src/App/GeoFeatureGroupExtension.cpp | 84 +++++-- src/App/GeoFeatureGroupExtension.h | 6 + src/App/GroupExtension.cpp | 41 +++- src/App/GroupExtension.h | 10 +- src/App/Origin.cpp | 6 +- src/App/OriginGroupExtension.cpp | 29 ++- src/App/OriginGroupExtension.h | 3 + src/Mod/Part/App/DatumFeature.cpp | 27 +++ src/Mod/Part/App/DatumFeature.h | 4 +- src/Mod/Part/App/PartFeature.cpp | 86 ++++++-- src/Mod/Part/App/PartFeature.h | 8 +- src/Mod/PartDesign/App/Body.cpp | 26 +++ src/Mod/PartDesign/App/Body.h | 8 + src/Mod/PartDesign/App/DatumCS.cpp | 31 +++ src/Mod/PartDesign/App/DatumCS.h | 3 + 31 files changed, 1762 insertions(+), 172 deletions(-) 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