Download src/App/DocumentObject.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 54.3 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/App/DocumentObject.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/App/DocumentObject.cpp
-
curl -L -o DocumentObject.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/App/DocumentObject.cpp
54.3 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /*************************************************************************** | |
| * Copyright (c) 2011 Jürgen Riegel <juergen.riegel@web.de> * | |
| * Copyright (c) 2011 Werner Mayer <wmayer[at]users.sourceforge.net> * | |
| * * | |
| * 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 * | |
| * * | |
| ***************************************************************************/ | |
| FC_LOG_LEVEL_INIT("App", true, true) | |
| using namespace App; | |
| PROPERTY_SOURCE(App::DocumentObject, App::TransactionalObject) | |
| DocumentObjectExecReturn* DocumentObject::StdReturn = nullptr; | |
| //=========================================================================== | |
| // DocumentObject | |
| //=========================================================================== | |
| DocumentObject::DocumentObject() | |
| : ExpressionEngine() | |
| { | |
| // 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() | |
| { | |
| 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 = static_cast<Base::PyObjectBase*>(PythonObject.ptr()); | |
| // Call before decrementing the reference counter, otherwise a heap error can occur | |
| obj->setInvalid(); | |
| } | |
| } | |
| void DocumentObject::printInvalidLinks() const | |
| { | |
| try { | |
| // Get objects that have invalid link scope, and print their names. | |
| // Truncate the invalid object list name strings for readability, if they happen to be very | |
| // long. | |
| std::vector<App::DocumentObject*> invalid_linkobjs; | |
| std::string objnames, scopenames; | |
| GeoFeatureGroupExtension::getInvalidLinkObjects(this, invalid_linkobjs); | |
| for (auto& obj : invalid_linkobjs) { | |
| objnames += obj->getNameInDocument(); | |
| objnames += " "; | |
| for (auto& scope : obj->getParents()) { | |
| if (scopenames.length() > 80) { | |
| scopenames += "... "; | |
| break; | |
| } | |
| scopenames += scope.first->getNameInDocument(); | |
| scopenames += " "; | |
| } | |
| if (objnames.length() > 80) { | |
| objnames += "... "; | |
| break; | |
| } | |
| } | |
| if (objnames.empty()) { | |
| objnames = "N/A"; | |
| } | |
| else { | |
| objnames.pop_back(); | |
| } | |
| if (scopenames.empty()) { | |
| scopenames = "N/A"; | |
| } | |
| else { | |
| scopenames.pop_back(); | |
| } | |
| Base::Console().warning("%s: Link(s) to object(s) '%s' go out of the allowed scope '%s'. " | |
| "Instead, the linked object(s) reside within '%s'.\n", | |
| getTypeId().getName(), | |
| objnames.c_str(), | |
| getNameInDocument(), | |
| scopenames.c_str()); | |
| } | |
| catch (const Base::Exception& e) { | |
| e.reportException(); | |
| } | |
| } | |
| App::DocumentObjectExecReturn* DocumentObject::recompute() | |
| { | |
| // check if the links are valid before making the recompute | |
| if (!GeoFeatureGroupExtension::areLinksValid(this)) { | |
| printInvalidLinks(); | |
| } | |
| // set/unset the execution bit | |
| Base::ObjectStatusLocker<ObjectStatus, DocumentObject> exe(App::Recompute, this); | |
| // mark the object to recompute its extensions | |
| this->setStatus(App::RecomputeExtension, true); | |
| auto ret = this->execute(); | |
| if (ret == StdReturn) { | |
| // most feature classes don't call the execute() method of its base class | |
| // so execute the extensions now | |
| if (this->testStatus(App::RecomputeExtension)) { | |
| ret = executeExtensions(); | |
| } | |
| } | |
| return ret; | |
| } | |
| DocumentObjectExecReturn* DocumentObject::execute() | |
| { | |
| return executeExtensions(); | |
| } | |
| App::DocumentObjectExecReturn* DocumentObject::executeExtensions() | |
| { | |
| // execute extensions but stop on error | |
| this->setStatus(App::RecomputeExtension, false); // reset the flag | |
| auto vector = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : vector) { | |
| auto ret = ext->extensionExecute(); | |
| if (ret != StdReturn) { | |
| return ret; | |
| } | |
| } | |
| return StdReturn; | |
| } | |
| bool DocumentObject::recomputeFeature(bool recursive) | |
| { | |
| Document* doc = this->getDocument(); | |
| if (doc) { | |
| return doc->recomputeFeature(this, recursive); | |
| } | |
| 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(bool noRecompute) | |
| { | |
| if (!noRecompute) { | |
| StatusBits.set(ObjectStatus::Enforce); | |
| } | |
| StatusBits.set(ObjectStatus::Touch); | |
| if (_pDoc) { | |
| _pDoc->signalTouchedObject(*this); | |
| } | |
| } | |
| /** | |
| * @brief Set this document object freezed. | |
| * A freezed document object does not recompute ever. | |
| */ | |
| void DocumentObject::freeze() | |
| { | |
| StatusBits.set(ObjectStatus::Freeze); | |
| // store read-only property names | |
| this->readOnlyProperties.clear(); | |
| std::vector<std::pair<const char*, Property*>> list; | |
| static_cast<App::PropertyContainer*>(this)->getPropertyNamedList(list); | |
| for (auto pair: list){ | |
| if (pair.second->isReadOnly()){ | |
| this->readOnlyProperties.push_back(pair.first); | |
| } else { | |
| pair.second->setReadOnly(true); | |
| } | |
| } | |
| // use the signalTouchedObject to refresh the Gui | |
| if (_pDoc) { | |
| _pDoc->signalTouchedObject(*this); | |
| } | |
| } | |
| /** | |
| * @brief Set this document object unfreezed. | |
| * A freezed document object does not recompute ever. | |
| */ | |
| void DocumentObject::unfreeze(bool noRecompute) | |
| { | |
| StatusBits.reset(ObjectStatus::Freeze); | |
| // reset read-only property status | |
| std::vector<std::pair<const char*, Property*>> list; | |
| static_cast<App::PropertyContainer*>(this)->getPropertyNamedList(list); | |
| for (auto pair: list){ | |
| if (! std::count(readOnlyProperties.begin(), readOnlyProperties.end(), pair.first)){ | |
| pair.second->setReadOnly(false); | |
| } | |
| } | |
| touch(noRecompute); | |
| } | |
| /** | |
| * @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() | |
| { | |
| touch(false); | |
| } | |
| /** | |
| * @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() const | |
| { | |
| if (StatusBits.test(ObjectStatus::Freeze)) { | |
| return false; | |
| } | |
| if (StatusBits.test(ObjectStatus::Enforce)) { | |
| return true; | |
| } | |
| return mustExecute() > 0; | |
| } | |
| short DocumentObject::mustExecute() const | |
| { | |
| if (ExpressionEngine.isTouched()) { | |
| return 1; | |
| } | |
| // ask all extensions | |
| auto vector = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : vector) { | |
| if (ext->extensionMustExecute()) { | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| const char* DocumentObject::getStatusString() const | |
| { | |
| if (isError()) { | |
| const char* text = getDocument()->getErrorDescription(this); | |
| return text ? text : "Error"; | |
| } | |
| else if (isFreezed()){ | |
| return "Freezed"; | |
| } | |
| else if (isTouched()) { | |
| return "Touched"; | |
| } | |
| else { | |
| return "Valid"; | |
| } | |
| } | |
| std::string DocumentObject::getFullName() const | |
| { | |
| if (!getDocument() || !isAttachedToDocument()) { | |
| return "?"; | |
| } | |
| std::string name(getDocument()->getName()); | |
| name += '#'; | |
| name += *pcNameInDocument; | |
| return name; | |
| } | |
| std::string DocumentObject::getFullLabel() const | |
| { | |
| if (!getDocument()) { | |
| return "?"; | |
| } | |
| auto name = getDocument()->Label.getStrValue(); | |
| name += "#"; | |
| name += Label.getStrValue(); | |
| return name; | |
| } | |
| const char* DocumentObject::getDagKey() const | |
| { | |
| if (!pcNameInDocument) { | |
| return nullptr; | |
| } | |
| return pcNameInDocument->c_str(); | |
| } | |
| 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 | |
| // to an object that has been removed from the document. In this case we should rather | |
| // return 0. | |
| // assert(pcNameInDocument); | |
| if (!pcNameInDocument) { | |
| return nullptr; | |
| } | |
| return pcNameInDocument->c_str(); | |
| } | |
| int DocumentObject::isExporting() const | |
| { | |
| if (!getDocument() || !isAttachedToDocument()) { | |
| return 0; | |
| } | |
| return getDocument()->isExporting(this); | |
| } | |
| std::string DocumentObject::getExportName(bool forced) const | |
| { | |
| if (!isAttachedToDocument()) { | |
| return {}; | |
| } | |
| if (!forced && !isExporting()) { | |
| return *pcNameInDocument; | |
| } | |
| // '@' is an invalid character for an internal name, which ensures the | |
| // following returned name will be unique in any document. Saving external | |
| // object like that shall only happens in Document::exportObjects(). We | |
| // shall strip out this '@' and the following document name during restoring. | |
| return *pcNameInDocument + '@' + getDocument()->getName(); | |
| } | |
| bool DocumentObject::isAttachedToDocument() const | |
| { | |
| return (pcNameInDocument != nullptr); | |
| } | |
| const char* DocumentObject::detachFromDocument() | |
| { | |
| const std::string* name = pcNameInDocument; | |
| pcNameInDocument = nullptr; | |
| return name ? name->c_str() : nullptr; | |
| } | |
| const std::vector<DocumentObject*>& DocumentObject::getOutList() const | |
| { | |
| if (!_outListCached) { | |
| _outList.clear(); | |
| getOutList(0, _outList); | |
| _outListCached = true; | |
| } | |
| return _outList; | |
| } | |
| std::vector<DocumentObject*> DocumentObject::getOutList(int options) const | |
| { | |
| std::vector<DocumentObject*> res; | |
| getOutList(options, res); | |
| return res; | |
| } | |
| void DocumentObject::getOutList(int options, std::vector<DocumentObject*>& res) const | |
| { | |
| if (_outListCached && !options) { | |
| res.insert(res.end(), _outList.begin(), _outList.end()); | |
| return; | |
| } | |
| std::vector<Property*> props; | |
| getPropertyList(props); | |
| bool noHidden = !!(options & OutListNoHidden); | |
| std::size_t size = res.size(); | |
| for (auto prop : props) { | |
| auto link = freecad_cast<PropertyLinkBase*>(prop); | |
| if (link) { | |
| link->getLinks(res, noHidden); | |
| } | |
| } | |
| if (!(options & OutListNoExpression)) { | |
| ExpressionEngine.getLinks(res); | |
| } | |
| if (options & OutListNoXLinked) { | |
| for (auto it = res.begin() + size; it != res.end();) { | |
| auto obj = *it; | |
| if (obj && obj->getDocument() != getDocument()) { | |
| it = res.erase(it); | |
| } | |
| else { | |
| ++it; | |
| } | |
| } | |
| } | |
| } | |
| std::vector<App::DocumentObject*> DocumentObject::getOutListOfProperty(App::Property* prop) const | |
| { | |
| std::vector<DocumentObject*> ret; | |
| if (!prop || prop->getContainer() != this) { | |
| return ret; | |
| } | |
| auto link = freecad_cast<PropertyLinkBase*>(prop); | |
| if (link) { | |
| link->getLinks(ret); | |
| } | |
| return ret; | |
| } | |
| const std::vector<App::DocumentObject*>& DocumentObject::getInList() const | |
| { | |
| return _inList; | |
| } | |
| // The original algorithm is highly inefficient in some special case. | |
| // Considering an object is linked by every other objects. After excluding this | |
| // object, there is another object linked by every other of the remaining | |
| // objects, and so on. The vector 'result' above will be of magnitude n^2. | |
| // Even if we replace the vector with a set, we still need to visit that amount | |
| // of objects. And this may not be the worst case. getInListEx() has no such | |
| // problem. | |
| std::vector<App::DocumentObject*> DocumentObject::getInListRecursive() const | |
| { | |
| std::set<App::DocumentObject*> inSet; | |
| std::vector<App::DocumentObject*> res; | |
| getInListEx(inSet, true, &res); | |
| return res; | |
| } | |
| // More efficient algorithm to find the recursive inList of an object, | |
| // including possible external parents. One shortcoming of this algorithm is | |
| // it does not detect cyclic reference, althgouth it won't crash either. | |
| void DocumentObject::getInListEx(std::set<App::DocumentObject*>& inSet, | |
| bool recursive, | |
| std::vector<App::DocumentObject*>* inList) const | |
| { | |
| if (!recursive) { | |
| inSet.insert(_inList.begin(), _inList.end()); | |
| if (inList) { | |
| *inList = _inList; | |
| } | |
| return; | |
| } | |
| std::stack<DocumentObject*> pendings; | |
| pendings.push(const_cast<DocumentObject*>(this)); | |
| while (!pendings.empty()) { | |
| auto obj = pendings.top(); | |
| pendings.pop(); | |
| for (auto o : obj->getInList()) { | |
| if (o && o->isAttachedToDocument() && inSet.insert(o).second) { | |
| pendings.push(o); | |
| if (inList) { | |
| inList->push_back(o); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| std::set<App::DocumentObject*> DocumentObject::getInListEx(bool recursive) const | |
| { | |
| std::set<App::DocumentObject*> ret; | |
| getInListEx(ret, recursive); | |
| return ret; | |
| } | |
| void _getOutListRecursive(std::set<DocumentObject*>& 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<App::DocumentObject*> DocumentObject::getOutListRecursive() const | |
| { | |
| // number of objects in document is a good estimate in result size | |
| int maxDepth = GetApplication().checkLinkDepth(0); | |
| std::set<App::DocumentObject*> result; | |
| // using a recursive helper to collect all OutLists | |
| _getOutListRecursive(result, this, this, maxDepth); | |
| std::vector<App::DocumentObject*> array; | |
| array.insert(array.begin(), result.begin(), result.end()); | |
| return array; | |
| } | |
| // helper for isInInListRecursive() | |
| bool _isInInListRecursive(const DocumentObject* act, const DocumentObject* checkObj, int depth) | |
| { | |
| 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; | |
| } | |
| } | |
| return false; | |
| } | |
| bool DocumentObject::isInInListRecursive(DocumentObject* linkTo) const | |
| { | |
| return this == linkTo || getInListEx(true).contains(linkTo); | |
| } | |
| bool DocumentObject::isInInList(DocumentObject* linkTo) const | |
| { | |
| if (std::ranges::find(_inList, linkTo) != _inList.end()) { | |
| return true; | |
| } | |
| else { | |
| return false; | |
| } | |
| } | |
| // helper for isInOutListRecursive() | |
| bool _isInOutListRecursive(const DocumentObject* act, const DocumentObject* checkObj, int depth) | |
| { | |
| 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; | |
| } | |
| } | |
| return false; | |
| } | |
| bool DocumentObject::isInOutListRecursive(DocumentObject* linkTo) const | |
| { | |
| int maxDepth = getDocument()->countObjects() + 2; | |
| return _isInOutListRecursive(this, linkTo, maxDepth); | |
| } | |
| std::vector<std::list<App::DocumentObject*>> | |
| DocumentObject::getPathsByOutList(App::DocumentObject* to) const | |
| { | |
| return _pDoc->getPathsByOutList(this, to); | |
| } | |
| DocumentObjectGroup* DocumentObject::getGroup() const | |
| { | |
| return freecad_cast<DocumentObjectGroup*>(GroupExtension::getGroupOfObject(this)); | |
| } | |
| bool DocumentObject::testIfLinkDAGCompatible(DocumentObject* linkTo) const | |
| { | |
| std::vector<App::DocumentObject*> linkTo_in_vector; | |
| linkTo_in_vector.push_back(linkTo); | |
| return this->testIfLinkDAGCompatible(linkTo_in_vector); | |
| } | |
| bool DocumentObject::testIfLinkDAGCompatible(const std::vector<DocumentObject*>& linksTo) const | |
| { | |
| auto inLists = getInListEx(true); | |
| inLists.emplace(const_cast<DocumentObject*>(this)); | |
| for (auto obj : linksTo) { | |
| if (inLists.contains(obj)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool DocumentObject::testIfLinkDAGCompatible(PropertyLinkSubList& linksTo) const | |
| { | |
| const std::vector<App::DocumentObject*>& linksTo_in_vector = linksTo.getValues(); | |
| return this->testIfLinkDAGCompatible(linksTo_in_vector); | |
| } | |
| bool DocumentObject::testIfLinkDAGCompatible(PropertyLinkSub& linkTo) const | |
| { | |
| std::vector<App::DocumentObject*> 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() const | |
| { | |
| return _pDoc; | |
| } | |
| void DocumentObject::setDocument(App::Document* doc) | |
| { | |
| _pDoc = doc; | |
| onSettingDocument(); | |
| } | |
| bool DocumentObject::removeDynamicProperty(const char* name) | |
| { | |
| if (!_pDoc || testStatus(ObjectStatus::Destroy)) { | |
| return false; | |
| } | |
| Property* prop = getDynamicPropertyByName(name); | |
| if (!prop || prop->testStatus(App::Property::LockDynamic)) { | |
| return false; | |
| } | |
| if (prop->isDerivedFrom<PropertyLinkBase>()) { | |
| clearOutListCache(); | |
| } | |
| _pDoc->addOrRemovePropertyOfObject(this, prop, false); | |
| auto expressions = ExpressionEngine.getExpressions(); | |
| std::vector<App::ObjectIdentifier> removeExpr; | |
| for (const auto& it : expressions) { | |
| if (it.first.getProperty() == prop) { | |
| removeExpr.push_back(it.first); | |
| } | |
| } | |
| for (const auto& it : removeExpr) { | |
| ExpressionEngine.setValue(it, std::shared_ptr<Expression>()); | |
| } | |
| return TransactionalObject::removeDynamicProperty(name); | |
| } | |
| bool DocumentObject::renameDynamicProperty(Property* prop, const char* name) | |
| { | |
| std::string oldName = prop->getName(); | |
| auto expressions = ExpressionEngine.getExpressions(); | |
| std::vector<std::shared_ptr<Expression>> expressionsToMove; | |
| std::vector<App::ObjectIdentifier> idsWithExprsToRemove; | |
| for (const auto& [id, expr] : expressions) { | |
| if (id.getProperty() == prop) { | |
| idsWithExprsToRemove.push_back(id); | |
| expressionsToMove.emplace_back(expr->copy()); | |
| } | |
| } | |
| for (const auto& it : idsWithExprsToRemove) { | |
| ExpressionEngine.setValue(it, std::shared_ptr<Expression>()); | |
| } | |
| bool renamed = TransactionalObject::renameDynamicProperty(prop, name); | |
| if (renamed && _pDoc) { | |
| _pDoc->renamePropertyOfObject(this, prop, oldName.c_str()); | |
| } | |
| App::ObjectIdentifier idNewProp(prop->getContainer(), std::string(name)); | |
| for (auto& exprToMove : expressionsToMove) { | |
| ExpressionEngine.setValue(idNewProp, exprToMove); | |
| } | |
| return renamed; | |
| } | |
| App::Property* DocumentObject::addDynamicProperty(const char* type, | |
| const char* name, | |
| const char* group, | |
| const char* doc, | |
| short attr, | |
| bool ro, | |
| bool hidden) | |
| { | |
| auto prop = TransactionalObject::addDynamicProperty(type, name, group, doc, attr, ro, hidden); | |
| if (prop && _pDoc) { | |
| _pDoc->addOrRemovePropertyOfObject(this, prop, true); | |
| } | |
| return prop; | |
| } | |
| void DocumentObject::onBeforeChange(const Property* prop) | |
| { | |
| if (isFreezed() && prop != &Visibility) { | |
| return; | |
| } | |
| // 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); | |
| } | |
| signalBeforeChange(*this, *prop); | |
| } | |
| std::vector<std::pair<Property*, std::unique_ptr<Property>>> | |
| DocumentObject::onProposedLabelChange(std::string& newLabel) | |
| { | |
| // Note that this work can't be done in onBeforeChangeLabel because FeaturePython overrides this | |
| // method and does not initially base-call it. | |
| // We re only called if the new label differs from the old one, and our code to check duplicates | |
| // may not work if this is not the case. | |
| std::string oldLabel = Label.getStrValue(); | |
| assert(newLabel != oldLabel); | |
| if (!isAttachedToDocument()) { | |
| return {}; | |
| } | |
| App::Document* doc = getDocument(); | |
| if (doc->isPerformingTransaction() | |
| || (doc->testStatus(App::Document::Restoring) | |
| && !doc->testStatus(App::Document::Importing))) { | |
| return {}; | |
| } | |
| static ParameterGrp::handle _hPGrp; | |
| if (!_hPGrp) { | |
| _hPGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document"); | |
| } | |
| if (doc && !newLabel.empty() && !_hPGrp->GetBool("DuplicateLabels") && !allowDuplicateLabel() | |
| && doc->containsLabel(newLabel)) { | |
| // The label already exists but settings are such that duplicate labels should not be assigned. | |
| std::string objName = getNameInDocument(); | |
| if (!doc->containsLabel(objName) && doc->haveSameBaseName(objName, newLabel)) { | |
| // The object name is not already a Label and the base name of the proposed label | |
| // equals the base name of the object Name, so we use the object Name as the replacement Label. | |
| newLabel = objName; | |
| } | |
| else { | |
| // Otherwise we generate a unique Label using newLabel as a prototype name. In doing so, | |
| // we must also act as if the current value of the property is not an existing Label | |
| // entry. | |
| // We deregister the old label so it does not interfere with making the new label, | |
| // and re-register it after. This is probably a bit less efficient that having a special | |
| // make-unique-label-as-if-this-one-did-not-exist method, but such a method would be a real | |
| // ugly wart. | |
| doc->unregisterLabel(oldLabel); | |
| newLabel = doc->makeUniqueLabel(newLabel); | |
| doc->registerLabel(oldLabel); | |
| } | |
| } | |
| // Despite our efforts to make a unique label, onBeforeLabelChange can change it. | |
| onBeforeChangeLabel(newLabel); | |
| if (oldLabel == newLabel || getDocument()->testStatus(App::Document::Restoring)) { | |
| // Don't update label reference if we are restoring or if the label is unchanged. | |
| // When importing (which also counts as restoring), it is possible the | |
| // new object changes its label. However, we cannot update label | |
| // references here, because object being restored is not based on | |
| // dependency order. It can only be done in afterRestore(). | |
| // | |
| // See PropertyLinkBase::restoreLabelReference() for more details. | |
| return {}; | |
| } | |
| return PropertyLinkBase::updateLabelReferences(this, newLabel.c_str()); | |
| } | |
| void DocumentObject::onEarlyChange(const Property* prop) | |
| { | |
| if (isFreezed() && prop != &Visibility) { | |
| return; | |
| } | |
| if (GetApplication().isClosingAll()) { | |
| return; | |
| } | |
| if (!GetApplication().isRestoring() && !prop->testStatus(Property::PartialTrigger) | |
| && getDocument() && getDocument()->testStatus(Document::PartialDoc)) { | |
| static App::Document* warnedDoc; | |
| if (warnedDoc != getDocument()) { | |
| warnedDoc = getDocument(); | |
| FC_WARN("Changes to partial loaded document will not be saved: " << getFullName() << '.' | |
| << prop->getName()); | |
| } | |
| } | |
| signalEarlyChanged(*this, *prop); | |
| } | |
| /// get called by the container when a Property was changed | |
| void DocumentObject::onChanged(const Property* prop) | |
| { | |
| if (prop == &Label && _pDoc && _pDoc->containsObject(this) && oldLabel != Label.getStrValue()) { | |
| _pDoc->unregisterLabel(oldLabel); | |
| _pDoc->registerLabel(Label.getStrValue()); | |
| } | |
| if (isFreezed() && prop != &Visibility) { | |
| return; | |
| } | |
| if (GetApplication().isClosingAll()) { | |
| return; | |
| } | |
| if (!GetApplication().isRestoring() && !prop->testStatus(Property::PartialTrigger) | |
| && getDocument() && getDocument()->testStatus(Document::PartialDoc)) { | |
| static App::Document* warnedDoc; | |
| if (warnedDoc != getDocument()) { | |
| warnedDoc = getDocument(); | |
| FC_WARN("Changes to partial loaded document will not be saved: " << getFullName() << '.' | |
| << prop->getName()); | |
| } | |
| } | |
| // 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); | |
| } | |
| // set object touched if it is an input property | |
| if (!testStatus(ObjectStatus::NoTouch) && !(prop->getType() & Prop_Output) | |
| && !prop->testStatus(Property::Output)) { | |
| if (!StatusBits.test(ObjectStatus::Touch)) { | |
| FC_TRACE("touch '" << getFullName() << "' on change of '" << prop->getName() << "'"); | |
| 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); | |
| // Now signal the view provider | |
| if (_pDoc) { | |
| _pDoc->onChangedProperty(this, prop); | |
| } | |
| signalChanged(*this, *prop); | |
| } | |
| void DocumentObject::clearOutListCache() const | |
| { | |
| _outList.clear(); | |
| _outListMap.clear(); | |
| _outListCached = false; | |
| } | |
| PyObject* DocumentObject::getPyObject() | |
| { | |
| if (PythonObject.is(Py::_None())) { | |
| // ref counter is set to 1 | |
| PythonObject = Py::Object(new DocumentObjectPy(this), true); | |
| } | |
| return Py::new_reference_to(PythonObject); | |
| } | |
| DocumentObject* DocumentObject::getSubObject(const char* subname, | |
| PyObject** pyObj, | |
| Base::Matrix4D* mat, | |
| bool transform, | |
| int depth) const | |
| { | |
| DocumentObject* ret = nullptr; | |
| auto exts = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : exts) { | |
| if (ext->extensionGetSubObject(ret, subname, pyObj, mat, transform, depth)) { | |
| return ret; | |
| } | |
| } | |
| std::string name; | |
| const char* dot = nullptr; | |
| if (!subname || !(dot = strchr(subname, '.'))) { | |
| ret = const_cast<DocumentObject*>(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->getDagKey()] = 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 = freecad_cast<PropertyPlacement*>(getPropertyByName("Placement")); | |
| if (pla) { | |
| *mat *= pla->getValue().toMatrix(); | |
| } | |
| } | |
| if (ret && dot) { | |
| return ret->getSubObject(dot + 1, pyObj, mat, true, depth + 1); | |
| } | |
| return ret; | |
| } | |
| namespace | |
| { | |
| std::vector<DocumentObject*> | |
| getSubObjectListFlatten(const std::vector<App::DocumentObject*>& resNotFlatten, | |
| std::vector<int>* const subsizes, | |
| const App::DocumentObject* sobj, | |
| App::DocumentObject** container, | |
| bool& lastChild) | |
| { | |
| auto res {resNotFlatten}; | |
| auto linked = sobj->getLinkedObject(); | |
| if (*container) { | |
| auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(linked); | |
| if (grp != *container) { | |
| *container = nullptr; | |
| } | |
| else { | |
| if (lastChild && !res.empty()) { | |
| res.pop_back(); | |
| if (subsizes) { | |
| subsizes->pop_back(); | |
| } | |
| } | |
| lastChild = true; | |
| } | |
| } | |
| if (linked->getExtensionByType<App::GeoFeatureGroupExtension>(true)) { | |
| *container = linked; | |
| lastChild = false; | |
| } | |
| else if (linked != sobj || sobj->hasChildElement()) { | |
| // Check for Link or LinkGroup | |
| *container = nullptr; | |
| } | |
| else if (auto ext = sobj->getExtensionByType<LinkBaseExtension>(true)) { | |
| // check for Link array | |
| if (ext->getElementCountValue() != 0) { | |
| *container = nullptr; | |
| } | |
| } | |
| return res; | |
| } | |
| } // namespace | |
| std::vector<DocumentObject*> DocumentObject::getSubObjectList(const char* subname, | |
| std::vector<int>* const subsizes, | |
| bool flatten) const | |
| { | |
| std::vector<DocumentObject*> res; | |
| res.push_back(const_cast<DocumentObject*>(this)); | |
| if (subsizes) { | |
| subsizes->push_back(0); | |
| } | |
| if (!subname || (subname[0] == '\0')) { | |
| return res; | |
| } | |
| auto element = Data::findElementName(subname); | |
| std::string sub(subname, element - subname); | |
| App::DocumentObject* container = nullptr; | |
| bool lastChild = false; | |
| if (flatten) { | |
| auto linked = getLinkedObject(); | |
| if (linked->getExtensionByType<App::GeoFeatureGroupExtension>(true)) { | |
| container = const_cast<DocumentObject*>(this); | |
| } | |
| else if (auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(linked)) { | |
| container = grp; | |
| lastChild = true; | |
| } | |
| } | |
| for (auto pos = sub.find('.'); pos != std::string::npos; pos = sub.find('.', pos + 1)) { | |
| char subTail = sub[pos + 1]; | |
| sub[pos + 1] = '\0'; | |
| auto sobj = getSubObject(sub.c_str()); | |
| if (!sobj || !sobj->isAttachedToDocument()) { | |
| continue; | |
| } | |
| if (flatten) { | |
| res = getSubObjectListFlatten(res, subsizes, sobj, &container, lastChild); | |
| } | |
| res.push_back(sobj); | |
| if (subsizes) { | |
| subsizes->push_back((int)pos + 1); | |
| } | |
| sub[pos + 1] = subTail; | |
| } | |
| return res; | |
| } | |
| std::vector<std::string> DocumentObject::getSubObjects(int reason) const | |
| { | |
| std::vector<std::string> ret; | |
| auto exts = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : exts) { | |
| if (ext->extensionGetSubObjects(ret, reason)) { | |
| return ret; | |
| } | |
| } | |
| return ret; | |
| } | |
| std::vector<std::pair<App::DocumentObject*, std::string>> | |
| DocumentObject::getParents(int depth) const | |
| { | |
| std::vector<std::pair<App::DocumentObject*, std::string>> ret; | |
| if (!isAttachedToDocument() || !GetApplication().checkLinkDepth(depth, MessageOption::Throw)) { | |
| return ret; | |
| } | |
| std::string name(getNameInDocument()); | |
| name += "."; | |
| for (auto parent : getInList()) { | |
| if (!parent || !parent->isAttachedToDocument()) { | |
| 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; | |
| } | |
| App::DocumentObject* DocumentObject::getFirstParent() const | |
| { | |
| for (auto obj : getInList()) { | |
| if (obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), true)) { | |
| return obj; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| DocumentObject* DocumentObject::getLinkedObject(bool recursive, | |
| Base::Matrix4D* mat, | |
| bool transform, | |
| int depth) const | |
| { | |
| DocumentObject* ret = nullptr; | |
| auto exts = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : exts) { | |
| if (ext->extensionGetLinkedObject(ret, recursive, mat, transform, depth)) { | |
| return ret; | |
| } | |
| } | |
| if (transform && mat) { | |
| auto pla = freecad_cast<PropertyPlacement*>(getPropertyByName("Placement")); | |
| if (pla) { | |
| *mat *= pla->getValue().toMatrix(); | |
| } | |
| } | |
| return const_cast<DocumentObject*>(this); | |
| } | |
| void DocumentObject::Save(Base::Writer& writer) const | |
| { | |
| if (this->isFreezed()) { | |
| throw Base::AbortException("At least one object is frozen, unable to save."); | |
| } | |
| if (this->isAttachedToDocument()){ | |
| 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 | |
| */ | |
| void DocumentObject::setExpression(const ObjectIdentifier& path, std::shared_ptr<Expression> expr) | |
| { | |
| ExpressionEngine.setValue(path, std::move(expr)); | |
| } | |
| /** | |
| * @brief Clear the expression of the object identifier \a path in this document object. | |
| * @param path Target object identifier | |
| */ | |
| void DocumentObject::clearExpression(const ObjectIdentifier& path) | |
| { | |
| setExpression(path, std::shared_ptr<Expression>()); | |
| } | |
| /** | |
| * @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<PropertyExpressionEngine::ExpressionInfo>(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<ObjectIdentifier, ObjectIdentifier>& paths) | |
| { | |
| ExpressionEngine.renameObjectIdentifiers(paths); | |
| } | |
| void DocumentObject::onDocumentRestored() | |
| { | |
| // call all extensions | |
| auto vector = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : vector) { | |
| ext->onExtendedDocumentRestored(); | |
| } | |
| if (Visibility.testStatus(Property::Output)) { | |
| Visibility.setStatus(Property::NoModify, true); | |
| } | |
| } | |
| void DocumentObject::restoreFinished() | |
| { | |
| // some link type property cannot restore link information until other | |
| // objects has been restored. For example, PropertyExpressionEngine and | |
| // PropertySheet with expression containing label reference. | |
| // So on document load they are handled in Document::afterRestore, but if the user | |
| // use dumpContent and restoreContent then they need to be handled here. | |
| std::vector<App::Property*> props; | |
| getPropertyList(props); | |
| for (auto prop : props) { | |
| prop->afterRestore(); | |
| } | |
| } | |
| void DocumentObject::onUndoRedoFinished() | |
| {} | |
| void DocumentObject::onSettingDocument() | |
| { | |
| // call all extensions | |
| auto vector = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : vector) { | |
| ext->onExtendedSettingDocument(); | |
| } | |
| } | |
| void DocumentObject::setupObject() | |
| { | |
| // call all extensions | |
| auto vector = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : vector) { | |
| ext->onExtendedSetupObject(); | |
| } | |
| } | |
| void DocumentObject::unsetupObject() | |
| { | |
| // call all extensions | |
| auto vector = getExtensionsDerivedFromType<App::DocumentObjectExtension>(); | |
| for (auto ext : vector) { | |
| ext->onExtendedUnsetupObject(); | |
| } | |
| } | |
| void App::DocumentObject::_removeBackLink(DocumentObject* rmvObj) | |
| { | |
| // do not use erase-remove idom, as this erases ALL entries that match. we only want to remove a | |
| // single one. | |
| auto it = std::ranges::find(_inList, rmvObj); | |
| if (it != _inList.end()) { | |
| _inList.erase(it); | |
| } | |
| } | |
| void App::DocumentObject::_addBackLink(DocumentObject* newObj) | |
| { | |
| // 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); | |
| } | |
| int DocumentObject::setElementVisible(const char* element, bool visible) | |
| { | |
| for (auto ext : getExtensionsDerivedFromType<DocumentObjectExtension>()) { | |
| int ret = ext->extensionSetElementVisible(element, visible); | |
| if (ret >= 0) { | |
| return ret; | |
| } | |
| } | |
| return -1; | |
| } | |
| int DocumentObject::isElementVisible(const char* element) const | |
| { | |
| for (auto ext : getExtensionsDerivedFromType<DocumentObjectExtension>()) { | |
| int ret = ext->extensionIsElementVisible(element); | |
| if (ret >= 0) { | |
| return ret; | |
| } | |
| } | |
| return -1; | |
| } | |
| bool DocumentObject::hasChildElement() const | |
| { | |
| for (auto ext : getExtensionsDerivedFromType<DocumentObjectExtension>()) { | |
| 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<DocumentObject*>(this); | |
| if (parent) { | |
| *parent = nullptr; | |
| } | |
| if (subElement) { | |
| *subElement = nullptr; | |
| } | |
| auto obj = getSubObject(subname, pyObj, pmat, transform, depth); | |
| if (!obj || !subname || *subname == 0) { | |
| return self; | |
| } | |
| if (!parent && !subElement) { | |
| return obj; | |
| } | |
| // NOTE, the convention 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 = nullptr; | |
| if (Data::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::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 visibility 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; | |
| } | |
| DocumentObject* DocumentObject::resolveRelativeLink(std::string& subname, | |
| DocumentObject*& link, | |
| std::string& linkSub) const | |
| { | |
| if (!link || !link->isAttachedToDocument() || !isAttachedToDocument()) { | |
| return nullptr; | |
| } | |
| auto ret = const_cast<DocumentObject*>(this); | |
| if (link != ret) { | |
| auto sub = subname.c_str(); | |
| auto nextsub = sub; | |
| for (auto dot = strchr(nextsub, '.'); dot; nextsub = dot + 1, dot = strchr(nextsub, '.')) { | |
| std::string subcheck(sub, nextsub - sub); | |
| subcheck += link->getNameInDocument(); | |
| subcheck += '.'; | |
| if (getSubObject(subcheck.c_str()) == link) { | |
| ret = getSubObject(std::string(sub, dot + 1 - sub).c_str()); | |
| if (!ret) { | |
| return nullptr; | |
| } | |
| subname = std::string(dot + 1); | |
| break; | |
| } | |
| } | |
| return ret; | |
| } | |
| size_t pos = 0, linkPos = 0; | |
| std::string linkssub, ssub; | |
| do { | |
| linkPos = linkSub.find('.', linkPos); | |
| if (linkPos == std::string::npos) { | |
| link = nullptr; | |
| return nullptr; | |
| } | |
| ++linkPos; | |
| pos = subname.find('.', pos); | |
| if (pos == std::string::npos) { | |
| subname.clear(); | |
| ret = nullptr; | |
| break; | |
| } | |
| ++pos; | |
| } while (subname.compare(0, pos, linkSub, 0, linkPos) == 0); | |
| if (pos != std::string::npos) { | |
| ret = getSubObject(subname.substr(0, pos).c_str()); | |
| if (!ret) { | |
| link = nullptr; | |
| return nullptr; | |
| } | |
| subname = subname.substr(pos); | |
| } | |
| if (linkPos) { | |
| link = link->getSubObject(linkSub.substr(0, linkPos).c_str()); | |
| if (!link) { | |
| return nullptr; | |
| } | |
| linkSub = linkSub.substr(linkPos); | |
| } | |
| return ret; | |
| } | |
| bool DocumentObject::adjustRelativeLinks(const std::set<App::DocumentObject*>& inList, | |
| std::set<App::DocumentObject*>* visited) | |
| { | |
| if (visited) { | |
| visited->insert(this); | |
| } | |
| bool touched = false; | |
| std::vector<Property*> props; | |
| getPropertyList(props); | |
| for (auto prop : props) { | |
| auto linkProp = freecad_cast<PropertyLinkBase*>(prop); | |
| if (linkProp && linkProp->adjustLink(inList)) { | |
| touched = true; | |
| } | |
| } | |
| if (visited) { | |
| for (auto obj : getOutList()) { | |
| if (!visited->count(obj)) { | |
| if (obj->adjustRelativeLinks(inList, visited)) { | |
| touched = true; | |
| } | |
| } | |
| } | |
| } | |
| return touched; | |
| } | |
| std::string DocumentObject::getElementMapVersion(const App::Property* _prop, bool restored) const | |
| { | |
| auto prop = freecad_cast<const PropertyComplexGeoData*>(_prop); | |
| if (!prop) { | |
| return std::string(); | |
| } | |
| return prop->getElementMapVersion(restored); | |
| } | |
| bool DocumentObject::checkElementMapVersion(const App::Property* _prop, const char* ver) const | |
| { | |
| auto prop = freecad_cast<const PropertyComplexGeoData*>(_prop); | |
| if (!prop) { | |
| return false; | |
| } | |
| return prop->checkElementMapVersion(ver); | |
| } | |
| const std::string& DocumentObject::hiddenMarker() | |
| { | |
| static std::string marker("!hide"); | |
| return marker; | |
| } | |
| const char* DocumentObject::hasHiddenMarker(const char* subname) | |
| { | |
| if (!subname) { | |
| return nullptr; | |
| } | |
| const char* marker = strrchr(subname, '.'); | |
| if (!marker) { | |
| marker = subname; | |
| } | |
| else { | |
| ++marker; | |
| } | |
| return hiddenMarker() == marker ? marker : nullptr; | |
| } | |
| bool DocumentObject::redirectSubName(std::ostringstream&, DocumentObject*, DocumentObject*) const | |
| { | |
| return false; | |
| } | |
| void DocumentObject::onPropertyStatusChanged(const Property& prop, unsigned long oldStatus) | |
| { | |
| (void)oldStatus; | |
| if (!Document::isAnyRestoring() && isAttachedToDocument() && getDocument()) { | |
| getDocument()->signalChangePropertyEditor(*getDocument(), prop); | |
| } | |
| } | |
| Base::Placement DocumentObject::getPlacementOf(const std::string& sub, DocumentObject* targetObj) | |
| { | |
| Base::Placement plc; | |
| auto* propPlacement = freecad_cast<App::PropertyPlacement*>(getPropertyByName("Placement")); | |
| if (propPlacement) { | |
| // If the object has no placement (like a Group), plc stays identity so we can proceed. | |
| plc = propPlacement->getValue(); | |
| } | |
| std::vector<std::string> names = Base::Tools::splitSubName(sub); | |
| if (names.empty() || this == targetObj) { | |
| return plc; | |
| } | |
| DocumentObject* subObj = getDocument()->getObject(names.front().c_str()); | |
| if (!subObj) { | |
| return plc; | |
| } | |
| std::vector<std::string> newNames(names.begin() + 1, names.end()); | |
| std::string newSub = Base::Tools::joinList(newNames, "."); | |
| return plc * subObj->getPlacementOf(newSub, targetObj); | |
| } | |
| Base::Placement DocumentObject::getPlacement() const | |
| { | |
| Base::Placement plc; | |
| if (auto* prop = getPlacementProperty()) { | |
| plc = prop->getValue(); | |
| } | |
| return plc; | |
| } | |
| App::PropertyPlacement* DocumentObject::getPlacementProperty() const | |
| { | |
| if (auto linkExtension = getExtensionByType<App::LinkBaseExtension>(true)) { | |
| if (auto linkPlacementProp = linkExtension->getLinkPlacementProperty()) { | |
| return linkPlacementProp; | |
| } | |
| return linkExtension->getPlacementProperty(); | |
| } | |
| return getPropertyByName<App::PropertyPlacement>("Placement"); | |
| } | |