1672 lines
53 KiB
C++
1672 lines
53 KiB
C++
// 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 *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <stack>
|
|
#include <memory>
|
|
#include <map>
|
|
#include <set>
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
#include <Base/Console.h>
|
|
#include <Base/Matrix.h>
|
|
#include <Base/Placement.h>
|
|
#include <Base/Tools.h>
|
|
#include <Base/Writer.h>
|
|
|
|
#include "Expression.h"
|
|
#include "Application.h"
|
|
#include "ElementNamingUtils.h"
|
|
#include "Document.h"
|
|
#include "DocumentObject.h"
|
|
#include "DocumentObjectPy.h"
|
|
#include "DocumentObjectExtension.h"
|
|
#include "DocumentObjectGroup.h"
|
|
#include "GeoFeatureGroupExtension.h"
|
|
#include "Link.h"
|
|
#include "ObjectIdentifier.h"
|
|
#include "PropertyExpressionEngine.h"
|
|
#include "PropertyLinks.h"
|
|
|
|
|
|
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");
|
|
}
|
|
|
|
|