/*************************************************************************** * Copyright (c) Jürgen Riegel (juergen.riegel@web.de) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #endif #include #include #include #include "Document.h" #include "DocumentObject.h" #include "DocumentObjectGroup.h" #include "PropertyLinks.h" #include "PropertyExpressionEngine.h" #include "DocumentObjectExtension.h" #include "GeoFeatureGroupExtension.h" #include #include using namespace App; PROPERTY_SOURCE(App::DocumentObject, App::TransactionalObject) DocumentObjectExecReturn *DocumentObject::StdReturn = 0; //=========================================================================== // DocumentObject //=========================================================================== DocumentObject::DocumentObject(void) : ExpressionEngine(),_pDoc(0),pcNameInDocument(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(ExpressionEngine,(),"Base",Prop_Hidden,"Property expressions"); } DocumentObject::~DocumentObject(void) { if (!PythonObject.is(Py::_None())){ Base::PyGILStateLocker lock; // Remark: The API of Py::Object has been changed to set whether the wrapper owns the passed // Python object or not. In the constructor we forced the wrapper to own the object so we need // not to dec'ref the Python object any more. // But we must still invalidate the Python object because it need not to be // destructed right now because the interpreter can own several references to it. Base::PyObjectBase* obj = (Base::PyObjectBase*)PythonObject.ptr(); // Call before decrementing the reference counter, otherwise a heap error can occur obj->setInvalid(); } } App::DocumentObjectExecReturn *DocumentObject::recompute(void) { //check if the links are valid before making the recompute if(!GeoFeatureGroupExtension::areLinksValid(this)) { #if 1 Base::Console().Warning("%s / %s: Links go out of the allowed scope\n", getTypeId().getName(), getNameInDocument()); #else return new App::DocumentObjectExecReturn("Links go out of the allowed scope", this); #endif } // set/unset the execution bit Base::ObjectStatusLocker exe(App::Recompute, this); return this->execute(); } DocumentObjectExecReturn *DocumentObject::execute(void) { //call all extensions auto vector = getExtensionsDerivedFromType(); for(auto ext : vector) { auto ret = ext->extensionExecute(); if (ret != StdReturn) return ret; } return StdReturn; } bool DocumentObject::recomputeFeature() { Document* doc = this->getDocument(); if (doc) doc->recomputeFeature(this); return isValid(); } /** * @brief Set this document object touched. * Touching a document object does not mean to recompute it, it only means that * other document objects that link it (i.e. its InList) will be recomputed. * If it should be forced to recompute a document object then use * \ref enforceRecompute() instead. */ void DocumentObject::touch(void) { StatusBits.set(ObjectStatus::Touch); } /** * @brief Check whether the document object is touched or not. * @return true if document object is touched, false if not. */ bool DocumentObject::isTouched() const { return ExpressionEngine.isTouched() || StatusBits.test(ObjectStatus::Touch); } /** * @brief Enforces this document object to be recomputed. * This can be useful to recompute the feature without * having to change one of its input properties. */ void DocumentObject::enforceRecompute(void) { StatusBits.set(ObjectStatus::Enforce); StatusBits.set(ObjectStatus::Touch); } /** * @brief Check whether the document object must be recomputed or not. * This means that the 'Enforce' flag is set or that \ref mustExecute() * returns a value > 0. * @return true if document object must be recomputed, false if not. */ bool DocumentObject::mustRecompute(void) const { if (StatusBits.test(ObjectStatus::Enforce)) return true; return mustExecute() > 0; } short DocumentObject::mustExecute(void) const { if (ExpressionEngine.isTouched()) return 1; //ask all extensions auto vector = getExtensionsDerivedFromType(); for(auto ext : vector) { if (ext->extensionMustExecute()) return 1; } return 0; } const char* DocumentObject::getStatusString(void) const { if (isError()) { const char* text = getDocument()->getErrorDescription(this); return text ? text : "Error"; } else if (isTouched()) return "Touched"; else return "Valid"; } const char *DocumentObject::getNameInDocument(void) 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 // to an object that has been removed from the document. In this case we should rather // return 0. //assert(pcNameInDocument); if (!pcNameInDocument) return 0; return pcNameInDocument->c_str(); } bool DocumentObject::isAttachedToDocument() const { return (pcNameInDocument != 0); } const char* DocumentObject::detachFromDocument() { const std::string* name = pcNameInDocument; pcNameInDocument = 0; return name ? name->c_str() : 0; } std::vector DocumentObject::getOutList(void) const { std::vector List; std::vector ret; getPropertyList(List); for (std::vector::const_iterator It = List.begin();It != List.end(); ++It) { if ((*It)->isDerivedFrom(PropertyLinkList::getClassTypeId())) { const std::vector &OutList = static_cast(*It)->getValues(); for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { if (*It2) ret.push_back(*It2); } } else if ((*It)->isDerivedFrom(PropertyLinkSubList::getClassTypeId())) { const std::vector &OutList = static_cast(*It)->getValues(); for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { if (*It2) ret.push_back(*It2); } } else if ((*It)->isDerivedFrom(PropertyLink::getClassTypeId())) { if (static_cast(*It)->getValue()) ret.push_back(static_cast(*It)->getValue()); } else if ((*It)->isDerivedFrom(PropertyLinkSub::getClassTypeId())) { if (static_cast(*It)->getValue()) ret.push_back(static_cast(*It)->getValue()); } } // Get document objects that this document object relies on ExpressionEngine.getDocumentObjectDeps(ret); return ret; } std::vector DocumentObject::getOutListOfProperty(App::Property* prop) const { std::vector ret; if (!prop || prop->getContainer() != this) return ret; if (prop->isDerivedFrom(PropertyLinkList::getClassTypeId())) { const std::vector &OutList = static_cast(prop)->getValues(); for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { if (*It2) ret.push_back(*It2); } } else if (prop->isDerivedFrom(PropertyLinkSubList::getClassTypeId())) { const std::vector &OutList = static_cast(prop)->getValues(); for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { if (*It2) ret.push_back(*It2); } } else if (prop->isDerivedFrom(PropertyLink::getClassTypeId())) { if (static_cast(prop)->getValue()) ret.push_back(static_cast(prop)->getValue()); } else if (prop->isDerivedFrom(PropertyLinkSub::getClassTypeId())) { if (static_cast(prop)->getValue()) ret.push_back(static_cast(prop)->getValue()); } else if (prop == &ExpressionEngine) { // Get document objects that this document object relies on ExpressionEngine.getDocumentObjectDeps(ret); } return ret; } #ifdef USE_OLD_DAG std::vector DocumentObject::getInList(void) const { if (_pDoc) return _pDoc->getInList(this); else return std::vector(); } #else // ifndef USE_OLD_DAG std::vector DocumentObject::getInList(void) const { return _inList; } #endif // if USE_OLD_DAG void _getInListRecursive(std::set& objSet, const DocumentObject* obj, const DocumentObject* checkObj, int depth) { for (const auto objIt : obj->getInList()) { // if the check object is in the recursive inList we have a cycle! if (objIt == checkObj || depth <= 0) { throw Base::BadGraphError("DocumentObject::getInListRecursive(): cyclic dependency detected!"); } // if the element was already in the set then there is no need to process it again auto pair = objSet.insert(objIt); if (pair.second) _getInListRecursive(objSet, objIt, checkObj, depth-1); } } std::vector DocumentObject::getInListRecursive(void) const { // number of objects in document is a good estimate in result size int maxDepth = getDocument()->countObjects() + 2; std::set result; // using a rcursie helper to collect all InLists _getInListRecursive(result, this, this, maxDepth); std::vector array; array.insert(array.begin(), result.begin(), result.end()); return array; } void _getOutListRecursive(std::set& objSet, const DocumentObject* obj, const DocumentObject* checkObj, int depth) { for (const auto objIt : obj->getOutList()) { // if the check object is in the recursive inList we have a cycle! if (objIt == checkObj || depth <= 0) { throw Base::BadGraphError("DocumentObject::getOutListRecursive(): cyclic dependency detected!"); } // if the element was already in the set then there is no need to process it again auto pair = objSet.insert(objIt); if (pair.second) _getOutListRecursive(objSet, objIt, checkObj, depth-1); } } std::vector DocumentObject::getOutListRecursive(void) const { // number of objects in document is a good estimate in result size int maxDepth = getDocument()->countObjects() + 2; std::set result; // using a recursive helper to collect all OutLists _getOutListRecursive(result, this, this, maxDepth); std::vector array; array.insert(array.begin(), result.begin(), result.end()); return array; } // helper for isInInListRecursive() bool _isInInListRecursive(const DocumentObject* act, const DocumentObject* checkObj, int depth) { #ifndef USE_OLD_DAG for (auto obj : act->getInList()) { if (obj == checkObj) return true; // if we reach the depth limit we have a cycle! if (depth <= 0) { throw Base::BadGraphError("DocumentObject::isInInListRecursive(): cyclic dependency detected!"); } if (_isInInListRecursive(obj, checkObj, depth - 1)) return true; } #else (void)act; (void)checkObj; (void)depth; #endif return false; } bool DocumentObject::isInInListRecursive(DocumentObject *linkTo) const { int maxDepth = getDocument()->countObjects() + 2; return _isInInListRecursive(this, linkTo, maxDepth); } bool DocumentObject::isInInList(DocumentObject *linkTo) const { #ifndef USE_OLD_DAG if (std::find(_inList.begin(), _inList.end(), linkTo) != _inList.end()) return true; else return false; #else (void)linkTo; return false; #endif } // helper for isInOutListRecursive() bool _isInOutListRecursive(const DocumentObject* act, const DocumentObject* checkObj, int depth) { #ifndef USE_OLD_DAG for (auto obj : act->getOutList()) { if (obj == checkObj) return true; // if we reach the depth limit we have a cycle! if (depth <= 0) { throw Base::BadGraphError("DocumentObject::isInOutListRecursive(): cyclic dependency detected!"); } if (_isInOutListRecursive(obj, checkObj, depth - 1)) return true; } #else (void)act; (void)checkObj; (void)depth; #endif return false; } bool DocumentObject::isInOutListRecursive(DocumentObject *linkTo) const { int maxDepth = getDocument()->countObjects() + 2; return _isInOutListRecursive(this, linkTo, maxDepth); } std::vector > DocumentObject::getPathsByOutList(App::DocumentObject* to) const { return _pDoc->getPathsByOutList(this, to); } DocumentObjectGroup* DocumentObject::getGroup() const { return dynamic_cast(GroupExtension::getGroupOfObject(this)); } bool DocumentObject::testIfLinkDAGCompatible(DocumentObject *linkTo) const { std::vector linkTo_in_vector; linkTo_in_vector.push_back(linkTo); return this->testIfLinkDAGCompatible(linkTo_in_vector); } bool DocumentObject::testIfLinkDAGCompatible(const std::vector &linksTo) const { Document* doc = this->getDocument(); if (!doc) throw Base::RuntimeError("DocumentObject::testIfLinkIsDAG: object is not in any document."); std::vector deplist = doc->getDependencyList(linksTo); if( std::find(deplist.begin(),deplist.end(),this) != deplist.end() ) //found this in dependency list return false; else return true; } bool DocumentObject::testIfLinkDAGCompatible(PropertyLinkSubList &linksTo) const { const std::vector &linksTo_in_vector = linksTo.getValues(); return this->testIfLinkDAGCompatible(linksTo_in_vector); } bool DocumentObject::testIfLinkDAGCompatible(PropertyLinkSub &linkTo) const { std::vector linkTo_in_vector; linkTo_in_vector.reserve(1); linkTo_in_vector.push_back(linkTo.getValue()); return this->testIfLinkDAGCompatible(linkTo_in_vector); } void DocumentObject::onLostLinkToObject(DocumentObject*) { } App::Document *DocumentObject::getDocument(void) const { return _pDoc; } void DocumentObject::setDocument(App::Document* doc) { _pDoc=doc; onSettingDocument(); } void DocumentObject::onAboutToRemoveProperty(const char* prop) { if (_pDoc) _pDoc->removePropertyOfObject(this, prop); } void DocumentObject::onBeforeChange(const Property* prop) { // Store current name in oldLabel, to be able to easily retrieve old name of document object later // when renaming expressions. if (prop == &Label) oldLabel = Label.getStrValue(); if (_pDoc) onBeforeChangeProperty(_pDoc, prop); } /// get called by the container when a Property was changed void DocumentObject::onChanged(const Property* prop) { if (_pDoc) _pDoc->onChangedProperty(this,prop); if (prop == &Label && _pDoc && oldLabel != Label.getStrValue()) _pDoc->signalRelabelObject(*this); // set object touched if it is an input property if (!(prop->getType() & Prop_Output)) { StatusBits.set(ObjectStatus::Touch); // must execute on document recompute if (!(prop->getType() & Prop_NoRecompute)) StatusBits.set(ObjectStatus::Enforce); } //call the parent for appropriate handling TransactionalObject::onChanged(prop); } PyObject *DocumentObject::getPyObject(void) { if (PythonObject.is(Py::_None())) { // ref counter is set to 1 PythonObject = Py::Object(new DocumentObjectPy(this),true); } return Py::new_reference_to(PythonObject); } std::vector DocumentObject::getPySubObjects(const std::vector&) const { // default implementation returns nothing return std::vector(); } void DocumentObject::Save (Base::Writer &writer) const { if (this->getNameInDocument()) writer.ObjectName = this->getNameInDocument(); App::ExtensionContainer::Save(writer); } /** * @brief Associate the expression \expr with the object identifier \a path in this document object. * @param path Target object identifier for the result of the expression * @param expr Expression tree * @param comment Optional comment describing the expression */ void DocumentObject::setExpression(const ObjectIdentifier &path, boost::shared_ptr expr, const char * comment) { ExpressionEngine.setValue(path, expr, comment); connectRelabelSignals(); } /** * @brief Get expression information associated with \a path. * @param path Object identifier * @return Expression info, containing expression and optional comment. */ const PropertyExpressionEngine::ExpressionInfo DocumentObject::getExpression(const ObjectIdentifier &path) const { boost::any value = ExpressionEngine.getPathValue(path); if (value.type() == typeid(PropertyExpressionEngine::ExpressionInfo)) return boost::any_cast(value); else return PropertyExpressionEngine::ExpressionInfo(); } /** * @brief Invoke ExpressionEngine's renameObjectIdentifier, to possibly rewrite expressions using * the \a paths map with current and new identifiers. * * @param paths */ void DocumentObject::renameObjectIdentifiers(const std::map &paths) { ExpressionEngine.renameObjectIdentifiers(paths); } /** * @brief Helper function that sets up a signal to track document object renames. */ void DocumentObject::connectRelabelSignals() { // Only keep signal if the ExpressionEngine has at least one expression if (ExpressionEngine.numExpressions() > 0) { // Not already connected? if (!onRelabledObjectConnection.connected()) { onRelabledObjectConnection = getDocument()->signalRelabelObject .connect(boost::bind(&PropertyExpressionEngine::slotObjectRenamed, &ExpressionEngine, _1)); } // Connect to signalDeletedObject, to properly track deletion of other objects // that might be referenced in an expression if (!onDeletedObjectConnection.connected()) { onDeletedObjectConnection = getDocument()->signalDeletedObject .connect(boost::bind(&PropertyExpressionEngine::slotObjectDeleted, &ExpressionEngine, _1)); } try { // Crude method to resolve all expression dependencies ExpressionEngine.execute(); } catch (...) { // Ignore any error } } else { // Disconnect signals; nothing to track now onRelabledObjectConnection.disconnect(); onRelabledDocumentConnection.disconnect(); onDeletedObjectConnection.disconnect(); } } void DocumentObject::onDocumentRestored() { //call all extensions auto vector = getExtensionsDerivedFromType(); for(auto ext : vector) ext->onExtendedDocumentRestored(); } void DocumentObject::onSettingDocument() { //call all extensions auto vector = getExtensionsDerivedFromType(); for(auto ext : vector) ext->onExtendedSettingDocument(); } void DocumentObject::setupObject() { //call all extensions auto vector = getExtensionsDerivedFromType(); for(auto ext : vector) ext->onExtendedSetupObject(); } void DocumentObject::unsetupObject() { //call all extensions auto vector = getExtensionsDerivedFromType(); for(auto ext : vector) ext->onExtendedUnsetupObject(); } void App::DocumentObject::_removeBackLink(DocumentObject* rmvObj) { #ifndef USE_OLD_DAG //do not use erase-remove idom, as this erases ALL entries that match. we only want to remove a //single one. auto it = std::find(_inList.begin(), _inList.end(), rmvObj); if(it != _inList.end()) _inList.erase(it); #else (void)rmvObj; #endif } void App::DocumentObject::_addBackLink(DocumentObject* newObj) { #ifndef USE_OLD_DAG //we need to add all links, even if they are available multiple times. The reason for this is the //removal: If a link loses this object it removes the backlink. If we would have added it only once //this removal would clear the object from the inlist, even though there may be other link properties //from this object that link to us. _inList.push_back(newObj); #else (void)newObj; #endif //USE_OLD_DAG }