| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #include <bitset>
|
| | #include <stack>
|
| | #include <deque>
|
| | #include <iostream>
|
| | #include <utility>
|
| | #include <set>
|
| | #include <memory>
|
| | #include <string>
|
| | #include <map>
|
| | #include <vector>
|
| | #include <list>
|
| | #include <algorithm>
|
| | #include <filesystem>
|
| |
|
| | #include <boost/algorithm/string.hpp>
|
| | #include <boost/bimap.hpp>
|
| | #include <boost/graph/strong_components.hpp>
|
| |
|
| | #include <boost/regex.hpp>
|
| | #include <random>
|
| | #include <unordered_map>
|
| | #include <unordered_set>
|
| |
|
| | #include <QCryptographicHash>
|
| | #include <QCoreApplication>
|
| |
|
| | #include <FCConfig.h>
|
| |
|
| | #include <App/DocumentPy.h>
|
| | #include <Base/Interpreter.h>
|
| | #include <Base/Console.h>
|
| | #include <Base/Exception.h>
|
| | #include <Base/FileInfo.h>
|
| | #include <Base/TimeInfo.h>
|
| | #include <Base/Reader.h>
|
| | #include <Base/Writer.h>
|
| | #include <Base/Profiler.h>
|
| | #include <Base/Tools.h>
|
| | #include <Base/Uuid.h>
|
| | #include <Base/Sequencer.h>
|
| | #include <Base/Stream.h>
|
| | #include <Base/UnitsApi.h>
|
| |
|
| | #include "Document.h"
|
| | #include "private/DocumentP.h"
|
| | #include "Application.h"
|
| | #include "AutoTransaction.h"
|
| | #include "BackupPolicy.h"
|
| | #include "ExpressionParser.h"
|
| | #include "GeoFeature.h"
|
| | #include "License.h"
|
| | #include "Link.h"
|
| | #include "MergeDocuments.h"
|
| | #include "StringHasher.h"
|
| | #include "Transactions.h"
|
| |
|
| | #ifdef _MSC_VER
|
| | #include <zipios++/zipios-config.h>
|
| | #endif
|
| | #include <zipios++/zipfile.h>
|
| | #include <zipios++/zipinputstream.h>
|
| | #include <zipios++/zipoutputstream.h>
|
| | #include <zipios++/meta-iostreams.h>
|
| |
|
| |
|
| | FC_LOG_LEVEL_INIT("App", true, true, true)
|
| |
|
| | using Base::Console;
|
| | using Base::streq;
|
| | using Base::Writer;
|
| | using namespace App;
|
| | using namespace std;
|
| | using namespace boost;
|
| | using namespace zipios;
|
| |
|
| | #if FC_DEBUG
|
| | #define FC_LOGFEATUREUPDATE
|
| | #endif
|
| |
|
| | namespace fs = std::filesystem;
|
| |
|
| | namespace App
|
| | {
|
| |
|
| | static bool globalIsRestoring;
|
| | static bool globalIsRelabeling;
|
| |
|
| | DocumentP::DocumentP()
|
| | {
|
| | static std::random_device rd;
|
| | static std::mt19937 rgen(rd());
|
| | static std::uniform_int_distribution<> rdist(0, 5000);
|
| |
|
| |
|
| |
|
| | lastObjectId = rdist(rgen);
|
| | StatusBits.set((size_t)Document::Closable, true);
|
| | StatusBits.set((size_t)Document::KeepTrailingDigits, true);
|
| | StatusBits.set((size_t)Document::Restoring, false);
|
| | }
|
| |
|
| | }
|
| |
|
| | PROPERTY_SOURCE(App::Document, App::PropertyContainer)
|
| |
|
| | bool Document::testStatus(const Status pos) const
|
| | {
|
| | return d->StatusBits.test(static_cast<size_t>(pos));
|
| | }
|
| |
|
| | void Document::setStatus(const Status pos, const bool on)
|
| | {
|
| | d->StatusBits.set(static_cast<size_t>(pos), on);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | bool Document::checkOnCycle()
|
| | {
|
| | return false;
|
| | }
|
| |
|
| | bool Document::undo(const int id)
|
| | {
|
| | if (d->iUndoMode != 0) {
|
| | if (id != 0) {
|
| | const auto it = mUndoMap.find(id);
|
| | if (it == mUndoMap.end()) {
|
| | return false;
|
| | }
|
| | if (it->second != d->activeUndoTransaction) {
|
| | while (!mUndoTransactions.empty() && mUndoTransactions.back() != it->second) {
|
| | undo(0);
|
| | }
|
| | }
|
| | }
|
| |
|
| | if (d->activeUndoTransaction) {
|
| | _commitTransaction(true);
|
| | }
|
| | if (mUndoTransactions.empty()) {
|
| | return false;
|
| | }
|
| |
|
| | d->activeUndoTransaction = new Transaction(mUndoTransactions.back()->getID());
|
| | d->activeUndoTransaction->Name = mUndoTransactions.back()->Name;
|
| |
|
| | {
|
| | Base::FlagToggler<bool> flag(d->undoing);
|
| |
|
| | mUndoTransactions.back()->apply(*this, false);
|
| |
|
| |
|
| | mRedoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
|
| | mRedoTransactions.push_back(d->activeUndoTransaction);
|
| | d->activeUndoTransaction = nullptr;
|
| |
|
| | mUndoMap.erase(mUndoTransactions.back()->getID());
|
| | delete mUndoTransactions.back();
|
| | mUndoTransactions.pop_back();
|
| | }
|
| |
|
| | for (const auto& obj : d->objectArray) {
|
| | if (obj->testStatus(ObjectStatus::PendingTransactionUpdate)) {
|
| | obj->onUndoRedoFinished();
|
| | obj->setStatus(ObjectStatus::PendingTransactionUpdate, false);
|
| | }
|
| | }
|
| |
|
| | signalUndo(*this);
|
| |
|
| | return true;
|
| | }
|
| |
|
| | return false;
|
| | }
|
| |
|
| | bool Document::redo(const int id)
|
| | {
|
| | if (d->iUndoMode != 0) {
|
| | if (id != 0) {
|
| | const auto it = mRedoMap.find(id);
|
| | if (it == mRedoMap.end()) {
|
| | return false;
|
| | }
|
| | while (!mRedoTransactions.empty() && mRedoTransactions.back() != it->second) {
|
| | redo(0);
|
| | }
|
| | }
|
| |
|
| | if (d->activeUndoTransaction) {
|
| | _commitTransaction(true);
|
| | }
|
| |
|
| | assert(mRedoTransactions.size() != 0);
|
| |
|
| |
|
| | d->activeUndoTransaction = new Transaction(mRedoTransactions.back()->getID());
|
| | d->activeUndoTransaction->Name = mRedoTransactions.back()->Name;
|
| |
|
| |
|
| | {
|
| | Base::FlagToggler<bool> flag(d->undoing);
|
| | mRedoTransactions.back()->apply(*this, true);
|
| |
|
| | mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
|
| | mUndoTransactions.push_back(d->activeUndoTransaction);
|
| | d->activeUndoTransaction = nullptr;
|
| |
|
| | mRedoMap.erase(mRedoTransactions.back()->getID());
|
| | delete mRedoTransactions.back();
|
| | mRedoTransactions.pop_back();
|
| | }
|
| |
|
| | for (const auto& obj : d->objectArray) {
|
| | if (obj->testStatus(ObjectStatus::PendingTransactionUpdate)) {
|
| | obj->onUndoRedoFinished();
|
| | obj->setStatus(ObjectStatus::PendingTransactionUpdate, false);
|
| | }
|
| | }
|
| |
|
| | signalRedo(*this);
|
| | return true;
|
| | }
|
| |
|
| | return false;
|
| | }
|
| |
|
| | void Document::changePropertyOfObject(TransactionalObject* obj,
|
| | const Property* prop,
|
| | const std::function<void()>& changeFunc)
|
| | {
|
| | if (!prop || !obj || !obj->isAttachedToDocument()) {
|
| | return;
|
| | }
|
| | if ((d->iUndoMode != 0) && !isPerformingTransaction() && !d->activeUndoTransaction) {
|
| | if (!testStatus(Restoring) || testStatus(Importing)) {
|
| | int tid = 0;
|
| | const char* name = GetApplication().getActiveTransaction(&tid);
|
| | if (name && tid > 0) {
|
| | _openTransaction(name, tid);
|
| | }
|
| | }
|
| | }
|
| | if (d->activeUndoTransaction && !d->rollback) {
|
| | changeFunc();
|
| | }
|
| | }
|
| |
|
| | void Document::renamePropertyOfObject(TransactionalObject* obj,
|
| | const Property* prop, const char* oldName)
|
| | {
|
| | changePropertyOfObject(obj, prop, [this, obj, prop, oldName]() {
|
| | d->activeUndoTransaction->renameProperty(obj, prop, oldName);
|
| | });
|
| | }
|
| |
|
| | void Document::addOrRemovePropertyOfObject(TransactionalObject* obj,
|
| | const Property* prop, const bool add)
|
| | {
|
| | changePropertyOfObject(obj, prop, [this, obj, prop, add]() {
|
| | d->activeUndoTransaction->addOrRemoveProperty(obj, prop, add);
|
| | });
|
| | }
|
| |
|
| | bool Document::isPerformingTransaction() const
|
| | {
|
| | return d->undoing || d->rollback;
|
| | }
|
| |
|
| | std::vector<std::string> Document::getAvailableUndoNames() const
|
| | {
|
| | std::vector<std::string> vList;
|
| | if (d->activeUndoTransaction) {
|
| | vList.push_back(d->activeUndoTransaction->Name);
|
| | }
|
| | for (auto It = mUndoTransactions.rbegin();
|
| | It != mUndoTransactions.rend();
|
| | ++It) {
|
| | vList.push_back((*It)->Name);
|
| | }
|
| | return vList;
|
| | }
|
| |
|
| | std::vector<std::string> Document::getAvailableRedoNames() const
|
| | {
|
| | std::vector<std::string> vList;
|
| | for (auto It = mRedoTransactions.rbegin();
|
| | It != mRedoTransactions.rend();
|
| | ++It) {
|
| | vList.push_back((*It)->Name);
|
| | }
|
| | return vList;
|
| | }
|
| |
|
| | void Document::openTransaction(const char* name)
|
| | {
|
| | if (isPerformingTransaction() || d->committing) {
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | FC_WARN("Cannot open transaction while transacting");
|
| | }
|
| | return;
|
| | }
|
| |
|
| | GetApplication().setActiveTransaction(name ? name : "<empty>");
|
| | }
|
| |
|
| | int Document::_openTransaction(const char* name, int id)
|
| | {
|
| | if (isPerformingTransaction() || d->committing) {
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | FC_WARN("Cannot open transaction while transacting");
|
| | }
|
| | return 0;
|
| | }
|
| |
|
| | if (d->iUndoMode != 0) {
|
| |
|
| |
|
| |
|
| |
|
| | if (d->opentransaction) {
|
| | return 0;
|
| | }
|
| | Base::FlagToggler<> flag(d->opentransaction);
|
| |
|
| | if ((id != 0) && mUndoMap.find(id) != mUndoMap.end()) {
|
| | throw Base::RuntimeError("invalid transaction id");
|
| | }
|
| | if (d->activeUndoTransaction) {
|
| | _commitTransaction(true);
|
| | }
|
| | _clearRedos();
|
| |
|
| | d->activeUndoTransaction = new Transaction(id);
|
| | if (!name) {
|
| | name = "<empty>";
|
| | }
|
| | d->activeUndoTransaction->Name = name;
|
| | mUndoMap[d->activeUndoTransaction->getID()] = d->activeUndoTransaction;
|
| | id = d->activeUndoTransaction->getID();
|
| |
|
| | signalOpenTransaction(*this, name);
|
| |
|
| | auto& app = GetApplication();
|
| | auto activeDoc = app.getActiveDocument();
|
| | if (activeDoc && activeDoc != this && !activeDoc->hasPendingTransaction()) {
|
| | std::string aname("-> ");
|
| | aname += d->activeUndoTransaction->Name;
|
| | FC_LOG("auto transaction " << getName() << " -> " << activeDoc->getName());
|
| | activeDoc->_openTransaction(aname.c_str(), id);
|
| | }
|
| | return id;
|
| | }
|
| | return 0;
|
| | }
|
| |
|
| | void Document::renameTransaction(const char* name, const int id) const
|
| | {
|
| | if (name && d->activeUndoTransaction && d->activeUndoTransaction->getID() == id) {
|
| | if (boost::starts_with(d->activeUndoTransaction->Name, "-> ")) {
|
| | d->activeUndoTransaction->Name.resize(3);
|
| | }
|
| | else {
|
| | d->activeUndoTransaction->Name.clear();
|
| | }
|
| | d->activeUndoTransaction->Name += name;
|
| | }
|
| | }
|
| |
|
| | void Document::_checkTransaction(DocumentObject* pcDelObj, const Property* What, int line)
|
| | {
|
| |
|
| | if ((d->iUndoMode != 0) && !isPerformingTransaction()) {
|
| | if (!d->activeUndoTransaction) {
|
| | if (!testStatus(Restoring) || testStatus(Importing)) {
|
| | int tid = 0;
|
| | const char* name = GetApplication().getActiveTransaction(&tid);
|
| | if (name && tid > 0) {
|
| | bool ignore = false;
|
| | if (What && What->testStatus(Property::NoModify)) {
|
| | ignore = true;
|
| | }
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | if (What) {
|
| | FC_LOG((ignore ? "ignore" : "auto")
|
| | << " transaction (" << line << ") '" << What->getFullName());
|
| | }
|
| | else {
|
| | FC_LOG((ignore ? "ignore" : "auto") << " transaction (" << line << ") '"
|
| | << name << "' in " << getName());
|
| | }
|
| | }
|
| | if (!ignore) {
|
| | _openTransaction(name, tid);
|
| | }
|
| | return;
|
| | }
|
| | }
|
| | if (!pcDelObj) {
|
| | return;
|
| | }
|
| |
|
| |
|
| | std::list<Transaction*>::iterator it;
|
| | for (it = mUndoTransactions.begin(); it != mUndoTransactions.end(); ++it) {
|
| | if ((*it)->hasObject(pcDelObj)) {
|
| | _openTransaction("Delete");
|
| | break;
|
| | }
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | void Document::_clearRedos()
|
| | {
|
| | if (isPerformingTransaction() || d->committing) {
|
| | FC_ERR("Cannot clear redo while transacting");
|
| | return;
|
| | }
|
| |
|
| | mRedoMap.clear();
|
| | while (!mRedoTransactions.empty()) {
|
| | delete mRedoTransactions.back();
|
| | mRedoTransactions.pop_back();
|
| | }
|
| | }
|
| |
|
| | void Document::commitTransaction()
|
| | {
|
| | if (isPerformingTransaction() || d->committing) {
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | FC_WARN("Cannot commit transaction while transacting");
|
| | }
|
| | return;
|
| | }
|
| |
|
| | if (d->activeUndoTransaction) {
|
| | GetApplication().closeActiveTransaction(false, d->activeUndoTransaction->getID());
|
| | }
|
| | }
|
| |
|
| | void Document::_commitTransaction(const bool notify)
|
| | {
|
| | if (isPerformingTransaction()) {
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | FC_WARN("Cannot commit transaction while transacting");
|
| | }
|
| | return;
|
| | }
|
| | if (d->committing) {
|
| |
|
| | return;
|
| | }
|
| |
|
| | if (d->activeUndoTransaction) {
|
| | Base::FlagToggler<> flag(d->committing);
|
| | Application::TransactionSignaller signaller(false, true);
|
| | const int id = d->activeUndoTransaction->getID();
|
| | mUndoTransactions.push_back(d->activeUndoTransaction);
|
| | d->activeUndoTransaction = nullptr;
|
| |
|
| | if (mUndoTransactions.size() > d->UndoMaxStackSize) {
|
| | mUndoMap.erase(mUndoTransactions.front()->getID());
|
| | delete mUndoTransactions.front();
|
| | mUndoTransactions.pop_front();
|
| | }
|
| | signalCommitTransaction(*this);
|
| |
|
| |
|
| | if (notify) {
|
| | GetApplication().closeActiveTransaction(false, id);
|
| | }
|
| | }
|
| | }
|
| |
|
| | void Document::abortTransaction() const
|
| | {
|
| | if (isPerformingTransaction() || d->committing) {
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | FC_WARN("Cannot abort transaction while transacting");
|
| | }
|
| | return;
|
| | }
|
| | if (d->activeUndoTransaction) {
|
| | GetApplication().closeActiveTransaction(true, d->activeUndoTransaction->getID());
|
| | }
|
| | }
|
| |
|
| | void Document::_abortTransaction()
|
| | {
|
| | if (isPerformingTransaction() || d->committing) {
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | FC_WARN("Cannot abort transaction while transacting");
|
| | }
|
| | }
|
| |
|
| | if (d->activeUndoTransaction) {
|
| | Base::FlagToggler<bool> flag(d->rollback);
|
| | Application::TransactionSignaller signaller(true, true);
|
| |
|
| |
|
| | d->activeUndoTransaction->apply(*this, false);
|
| |
|
| |
|
| | mUndoMap.erase(d->activeUndoTransaction->getID());
|
| | delete d->activeUndoTransaction;
|
| | d->activeUndoTransaction = nullptr;
|
| | signalAbortTransaction(*this);
|
| | }
|
| | }
|
| |
|
| | bool Document::hasPendingTransaction() const
|
| | {
|
| | return d->activeUndoTransaction != nullptr;
|
| | }
|
| |
|
| | int Document::getTransactionID(const bool undo, unsigned pos) const
|
| | {
|
| | if (undo) {
|
| | if (d->activeUndoTransaction) {
|
| | if (pos == 0) {
|
| | return d->activeUndoTransaction->getID();
|
| | }
|
| | --pos;
|
| | }
|
| | if (pos >= mUndoTransactions.size()) {
|
| | return 0;
|
| | }
|
| | auto rit = mUndoTransactions.rbegin();
|
| | for (; pos != 0U; ++rit, --pos) {}
|
| | return (*rit)->getID();
|
| | }
|
| | if (pos >= mRedoTransactions.size()) {
|
| | return 0;
|
| | }
|
| | auto rit = mRedoTransactions.rbegin();
|
| | for (; pos != 0U; ++rit, --pos) {}
|
| | return (*rit)->getID();
|
| | }
|
| |
|
| | bool Document::isTransactionEmpty() const
|
| | {
|
| | return !d->activeUndoTransaction;
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | }
|
| |
|
| | void Document::clearDocument()
|
| | {
|
| | d->activeObject = nullptr;
|
| |
|
| | if (!d->objectArray.empty()) {
|
| | GetApplication().signalDeleteDocument(*this);
|
| | d->clearDocument();
|
| | GetApplication().signalNewDocument(*this, false);
|
| | }
|
| |
|
| | Base::FlagToggler<> flag(globalIsRestoring, false);
|
| |
|
| | setStatus(Document::PartialDoc, false);
|
| |
|
| | d->clearRecomputeLog();
|
| | d->objectLabelManager.clear();
|
| | d->objectArray.clear();
|
| | d->objectMap.clear();
|
| | d->objectNameManager.clear();
|
| | d->objectIdMap.clear();
|
| | d->lastObjectId = 0;
|
| | }
|
| |
|
| |
|
| | void Document::clearUndos()
|
| | {
|
| | if (isPerformingTransaction() || d->committing) {
|
| | FC_ERR("Cannot clear undos while transacting");
|
| | return;
|
| | }
|
| |
|
| | if (d->activeUndoTransaction) {
|
| | _commitTransaction(true);
|
| | }
|
| |
|
| | mUndoMap.clear();
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | while (!mUndoTransactions.empty()) {
|
| | delete mUndoTransactions.front();
|
| | mUndoTransactions.pop_front();
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | _clearRedos();
|
| | }
|
| |
|
| | int Document::getAvailableUndos(const int id) const
|
| | {
|
| | if (id != 0) {
|
| | const auto it = mUndoMap.find(id);
|
| | if (it == mUndoMap.end()) {
|
| | return 0;
|
| | }
|
| | int i = 0;
|
| | if (d->activeUndoTransaction) {
|
| | ++i;
|
| | if (d->activeUndoTransaction->getID() == id) {
|
| | return i;
|
| | }
|
| | }
|
| | auto rit = mUndoTransactions.rbegin();
|
| | for (; rit != mUndoTransactions.rend() && *rit != it->second; ++rit) {
|
| | ++i;
|
| | }
|
| | assert(rit != mUndoTransactions.rend());
|
| | return i + 1;
|
| | }
|
| | if (d->activeUndoTransaction) {
|
| | return static_cast<int>(mUndoTransactions.size() + 1);
|
| | }
|
| | return static_cast<int>(mUndoTransactions.size());
|
| | }
|
| |
|
| | int Document::getAvailableRedos(const int id) const
|
| | {
|
| | if (id != 0) {
|
| | const auto it = mRedoMap.find(id);
|
| | if (it == mRedoMap.end()) {
|
| | return 0;
|
| | }
|
| | int i = 0;
|
| | for (auto rit = mRedoTransactions.rbegin(); *rit != it->second; ++rit) {
|
| | ++i;
|
| | }
|
| | assert(i < static_cast<int>(mRedoTransactions.size()));
|
| | return i + 1;
|
| | }
|
| | return static_cast<int>(mRedoTransactions.size());
|
| | }
|
| |
|
| | void Document::setUndoMode(const int iMode)
|
| | {
|
| | if ((d->iUndoMode != 0) && (iMode == 0)) {
|
| | clearUndos();
|
| | }
|
| |
|
| | d->iUndoMode = iMode;
|
| | }
|
| |
|
| | int Document::getUndoMode() const
|
| | {
|
| | return d->iUndoMode;
|
| | }
|
| |
|
| | unsigned int Document::getUndoMemSize() const
|
| | {
|
| | return d->UndoMemSize;
|
| | }
|
| |
|
| | void Document::setUndoLimit(const unsigned int UndoMemSize)
|
| | {
|
| | d->UndoMemSize = UndoMemSize;
|
| | }
|
| |
|
| | void Document::setMaxUndoStackSize(const unsigned int UndoMaxStackSize)
|
| | {
|
| | d->UndoMaxStackSize = UndoMaxStackSize;
|
| | }
|
| |
|
| | unsigned int Document::getMaxUndoStackSize() const
|
| | {
|
| | return d->UndoMaxStackSize;
|
| | }
|
| |
|
| | void Document::onBeforeChange(const Property* prop)
|
| | {
|
| | if (prop == &Label) {
|
| | oldLabel = Label.getValue();
|
| | }
|
| | signalBeforeChange(*this, *prop);
|
| | }
|
| |
|
| | void Document::onChanged(const Property* prop)
|
| | {
|
| | signalChanged(*this, *prop);
|
| |
|
| |
|
| | if (prop == &Label) {
|
| | Base::FlagToggler<> flag(globalIsRelabeling);
|
| | GetApplication().signalRelabelDocument(*this);
|
| | }
|
| | else if (prop == &ShowHidden) {
|
| | GetApplication().signalShowHidden(*this);
|
| | }
|
| | else if (prop == &Uid) {
|
| | std::string new_dir =
|
| | getTransientDirectoryName(this->Uid.getValueStr(), this->FileName.getStrValue());
|
| | std::string old_dir = this->TransientDir.getStrValue();
|
| | Base::FileInfo TransDirNew(new_dir);
|
| | Base::FileInfo TransDirOld(old_dir);
|
| |
|
| | if (!TransDirNew.exists()) {
|
| | if (TransDirOld.exists()) {
|
| | if (!TransDirOld.renameFile(new_dir.c_str())) {
|
| | Base::Console().warning("Failed to rename '%s' to '%s'\n",
|
| | old_dir.c_str(),
|
| | new_dir.c_str());
|
| | }
|
| | else {
|
| | this->TransientDir.setValue(new_dir);
|
| | }
|
| | }
|
| | else {
|
| | if (!TransDirNew.createDirectories()) {
|
| | Base::Console().warning("Failed to create '%s'\n", new_dir.c_str());
|
| | }
|
| | else {
|
| | this->TransientDir.setValue(new_dir);
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | else if (TransDirNew.filePath() != TransDirOld.filePath()) {
|
| |
|
| | std::string uuid = this->Uid.getValueStr();
|
| | Base::Uuid id;
|
| | Base::Console().warning("Document with the UUID '%s' already exists, change to '%s'\n",
|
| | uuid.c_str(),
|
| | id.getValue().c_str());
|
| |
|
| | this->Uid.setValue(id);
|
| | }
|
| | }
|
| | else if (prop == &UseHasher) {
|
| | for (auto obj : d->objectArray) {
|
| | auto geofeature = freecad_cast<GeoFeature*>(obj);
|
| | if (geofeature && geofeature->getPropertyOfGeometry()) {
|
| | geofeature->enforceRecompute();
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | void Document::onBeforeChangeProperty(const TransactionalObject* Who, const Property* What)
|
| | {
|
| | if (Who->isDerivedFrom<DocumentObject>()) {
|
| | signalBeforeChangeObject(*static_cast<const DocumentObject*>(Who), *What);
|
| | }
|
| | if (!d->rollback && !globalIsRelabeling) {
|
| | _checkTransaction(nullptr, What, __LINE__);
|
| | if (d->activeUndoTransaction) {
|
| | d->activeUndoTransaction->addObjectChange(Who, What);
|
| | }
|
| | }
|
| | }
|
| |
|
| | void Document::onChangedProperty(const DocumentObject* Who, const Property* What)
|
| | {
|
| | signalChangedObject(*Who, *What);
|
| | }
|
| |
|
| | void Document::setTransactionMode(const int iMode)
|
| | {
|
| | d->iTransactionMode = iMode;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | Document::Document(const char* documentName)
|
| | : d(new DocumentP), myName(documentName)
|
| | {
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | setAutoCreated(false);
|
| | Base::PyGILStateLocker lock;
|
| | d->DocumentPythonObject = Py::Object(new DocumentPy(this), true);
|
| |
|
| | #ifdef FC_LOGUPDATECHAIN
|
| | Console().log("+App::Document: %p\n", this);
|
| | #endif
|
| | std::string CreationDateString = Base::Tools::currentDateTimeString();
|
| | std::string Author = GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetASCII("prefAuthor", "");
|
| | std::string AuthorComp =
|
| | GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetASCII("prefCompany", "");
|
| | ADD_PROPERTY_TYPE(Label, ("Unnamed"), 0, Prop_ReadOnly, "The name of the document");
|
| | ADD_PROPERTY_TYPE(FileName,
|
| | (""),
|
| | 0,
|
| | PropertyType(Prop_Transient | Prop_ReadOnly),
|
| | "The path to the file where the document is saved to");
|
| | ADD_PROPERTY_TYPE(CreatedBy, (Author.c_str()), 0, Prop_None, "The creator of the document");
|
| | ADD_PROPERTY_TYPE(CreationDate,
|
| | (CreationDateString.c_str()),
|
| | 0,
|
| | Prop_ReadOnly,
|
| | "Date of creation");
|
| | ADD_PROPERTY_TYPE(LastModifiedBy, (""), 0, Prop_None, 0);
|
| | ADD_PROPERTY_TYPE(LastModifiedDate, ("Unknown"), 0, Prop_ReadOnly, "Date of last modification");
|
| | ADD_PROPERTY_TYPE(Company,
|
| | (AuthorComp.c_str()),
|
| | 0,
|
| | Prop_None,
|
| | "Additional tag to save the name of the company");
|
| | ADD_PROPERTY_TYPE(UnitSystem, (""), 0, Prop_None, "Unit system to use in this project");
|
| |
|
| |
|
| | UnitSystem.setEnums(Base::UnitsApi::getDescriptions());
|
| |
|
| | ParameterGrp::handle hGrpu =
|
| | GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Units");
|
| | UnitSystem.setValue(hGrpu->GetInt("UserSchema", 0));
|
| | ADD_PROPERTY_TYPE(Comment, (""), 0, Prop_None, "Additional tag to save a comment");
|
| | ADD_PROPERTY_TYPE(Meta, (), 0, Prop_None, "Map with additional meta information");
|
| | ADD_PROPERTY_TYPE(Material, (), 0, Prop_None, "Map with material properties");
|
| |
|
| | Base::Uuid id;
|
| | ADD_PROPERTY_TYPE(Id, (""), 0, Prop_None, "ID of the document");
|
| | ADD_PROPERTY_TYPE(Uid, (id), 0, Prop_ReadOnly, "UUID of the document");
|
| |
|
| |
|
| | auto paramGrp {GetApplication().GetParameterGroupByPath(
|
| | "User parameter:BaseApp/Preferences/Document")};
|
| | auto index = static_cast<int>(paramGrp->GetInt("prefLicenseType", 0));
|
| | auto name = "";
|
| | std::string licenseUrl = "";
|
| | if (index >= 0 && index < countOfLicenses) {
|
| | name = licenseItems.at(index).at(posnOfFullName);
|
| | auto url = licenseItems.at(index).at(posnOfUrl);
|
| | licenseUrl = (paramGrp->GetASCII("prefLicenseUrl", url));
|
| | }
|
| | ADD_PROPERTY_TYPE(License, (name), 0, Prop_None, "License string of the Item");
|
| | ADD_PROPERTY_TYPE(LicenseURL,
|
| | (licenseUrl.c_str()),
|
| | 0,
|
| | Prop_None,
|
| | "URL to the license text/contract");
|
| | ADD_PROPERTY_TYPE(ShowHidden,
|
| | (false),
|
| | 0,
|
| | PropertyType(Prop_None),
|
| | "Whether to show hidden object items in the tree view");
|
| | ADD_PROPERTY_TYPE(UseHasher,
|
| | (true),
|
| | 0,
|
| | PropertyType(Prop_Hidden),
|
| | "Whether to use hasher on topological naming");
|
| |
|
| |
|
| | ADD_PROPERTY_TYPE(TransientDir,
|
| | (""),
|
| | 0,
|
| | PropertyType(Prop_Transient | Prop_ReadOnly),
|
| | "Transient directory, where the files live while the document is open");
|
| | ADD_PROPERTY_TYPE(Tip,
|
| | (nullptr),
|
| | 0,
|
| | PropertyType(Prop_Transient),
|
| | "Link of the tip object of the document");
|
| | ADD_PROPERTY_TYPE(TipName,
|
| | (""),
|
| | 0,
|
| | PropertyType(Prop_Hidden | Prop_ReadOnly),
|
| | "Link of the tip object of the document");
|
| | Uid.touch();
|
| | }
|
| |
|
| | Document::~Document()
|
| | {
|
| | #ifdef FC_LOGUPDATECHAIN
|
| | Console().log("-App::Document: %s %p\n", getName(), this);
|
| | #endif
|
| |
|
| | try {
|
| | clearUndos();
|
| | }
|
| | catch (const boost::exception&) {
|
| | }
|
| |
|
| | #ifdef FC_LOGUPDATECHAIN
|
| | Console().log("-Delete Features of %s \n", getName());
|
| | #endif
|
| |
|
| | d->clearDocument();
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | Base::PyGILStateLocker lock;
|
| | auto* doc = static_cast<Base::PyObjectBase*>(d->DocumentPythonObject.ptr());
|
| |
|
| | doc->setInvalid();
|
| |
|
| |
|
| | try {
|
| | const Base::FileInfo TransDir(TransientDir.getValue());
|
| | TransDir.deleteDirectoryRecursive();
|
| | }
|
| | catch (const Base::Exception& e) {
|
| | std::cerr << "Removing transient directory failed: " << e.what() << '\n';
|
| | }
|
| | delete d;
|
| | }
|
| |
|
| | std::string Document::getTransientDirectoryName(const std::string& uuid,
|
| | const std::string& filename) const
|
| | {
|
| |
|
| | std::stringstream out;
|
| | QCryptographicHash hash(QCryptographicHash::Sha1);
|
| | #if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
|
| | hash.addData(filename.c_str(), filename.size());
|
| | #else
|
| | hash.addData(QByteArrayView(filename.c_str(), filename.size()));
|
| | #endif
|
| | out << Application::getUserCachePath() << Application::getExecutableName() << "_Doc_"
|
| | << uuid << "_" << hash.result().toHex().left(6).constData() << "_"
|
| | << Application::applicationPid();
|
| | return out.str();
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | void Document::Save(Base::Writer& writer) const
|
| | {
|
| | d->hashers.clear();
|
| | addStringHasher(d->Hasher);
|
| |
|
| | writer.Stream() << R"(<Document SchemaVersion="4" ProgramVersion=")"
|
| | << Application::Config()["BuildVersionMajor"] << "."
|
| | << Application::Config()["BuildVersionMinor"] << "R"
|
| | << Application::Config()["BuildRevision"] << "\" FileVersion=\""
|
| | << writer.getFileVersion() << "\" StringHasher=\"1\">\n";
|
| |
|
| | writer.incInd();
|
| |
|
| | d->Hasher->setPersistenceFileName("StringHasher.Table");
|
| | for (const auto o : d->objectArray) {
|
| | o->beforeSave();
|
| | }
|
| | beforeSave();
|
| |
|
| | d->Hasher->Save(writer);
|
| |
|
| | writer.decInd();
|
| |
|
| | PropertyContainer::Save(writer);
|
| |
|
| |
|
| | writeObjects(d->objectArray, writer);
|
| | writer.Stream() << "</Document>" << '\n';
|
| | }
|
| |
|
| | void Document::Restore(Base::XMLReader& reader)
|
| | {
|
| | d->hashers.clear();
|
| | d->touchedObjs.clear();
|
| | addStringHasher(d->Hasher);
|
| | setStatus(Document::PartialDoc, false);
|
| |
|
| | reader.readElement("Document");
|
| | const long scheme = reader.getAttribute<long>("SchemaVersion");
|
| | reader.DocumentSchema = static_cast<int>(scheme);
|
| | if (reader.hasAttribute("ProgramVersion")) {
|
| | reader.ProgramVersion = reader.getAttribute<const char*>("ProgramVersion");
|
| | }
|
| | else {
|
| | reader.ProgramVersion = "pre-0.14";
|
| | }
|
| | if (reader.hasAttribute("FileVersion")) {
|
| | reader.FileVersion = static_cast<int>(reader.getAttribute<unsigned long>("FileVersion"));
|
| | }
|
| | else {
|
| | reader.FileVersion = 0;
|
| | }
|
| |
|
| | if (reader.hasAttribute("StringHasher")) {
|
| | d->Hasher->Restore(reader);
|
| | }
|
| | else {
|
| | d->Hasher->clear();
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | const std::string FilePath = FileName.getValue();
|
| | const std::string DocLabel = Label.getValue();
|
| |
|
| |
|
| |
|
| | PropertyContainer::Restore(reader);
|
| |
|
| |
|
| |
|
| | FileName.setValue(FilePath.c_str());
|
| | Label.setValue(DocLabel.c_str());
|
| |
|
| |
|
| | if (scheme == 2) {
|
| |
|
| | reader.readElement("Features");
|
| | for (auto i = 0; i < reader.getAttribute<long>("Count"); i++) {
|
| | reader.readElement("Feature");
|
| | string type = reader.getAttribute<const char*>("type");
|
| | string name = reader.getAttribute<const char*>("name");
|
| | try {
|
| | addObject(type.c_str(), name.c_str(), false);
|
| | }
|
| | catch (Base::Exception&) {
|
| | Base::Console().message("Cannot create object '%s'\n", name.c_str());
|
| | }
|
| | }
|
| | reader.readEndElement("Features");
|
| |
|
| |
|
| | reader.readElement("FeatureData");
|
| | for (auto i = 0; i < reader.getAttribute<long>("Count"); i++) {
|
| | reader.readElement("Feature");
|
| | string name = reader.getAttribute<const char*>("name");
|
| | DocumentObject* pObj = getObject(name.c_str());
|
| | if (pObj) {
|
| | pObj->setStatus(ObjectStatus::Restore, true);
|
| | pObj->Restore(reader);
|
| | pObj->setStatus(ObjectStatus::Restore, false);
|
| | }
|
| | reader.readEndElement("Feature");
|
| | }
|
| | reader.readEndElement("FeatureData");
|
| | }
|
| | else if (scheme >= 3) {
|
| |
|
| | readObjects(reader);
|
| |
|
| |
|
| |
|
| | Tip.setValue(getObject(TipName.getValue()));
|
| | }
|
| |
|
| | reader.readEndElement("Document");
|
| | }
|
| |
|
| | void DocumentP::checkStringHasher(const Base::XMLReader& reader)
|
| | {
|
| | if (reader.hasReadFailed("StringHasher.Table.txt")) {
|
| | Base::Console().error(QT_TRANSLATE_NOOP(
|
| | "Notifications",
|
| | "\nIt is recommended that the user right-click the root of "
|
| | "the document and select Mark to recompute.\n"
|
| | "The user should then click the Refresh button in the main toolbar.\n"));
|
| | }
|
| | }
|
| |
|
| | std::pair<bool, int> Document::addStringHasher(const StringHasherRef& hasher) const
|
| | {
|
| | if (!hasher) {
|
| | return std::make_pair(false, 0);
|
| | }
|
| | auto ret =
|
| | d->hashers.left.insert(HasherMap::left_map::value_type(hasher, static_cast<int>(d->hashers.size())));
|
| | if (ret.second) {
|
| | hasher->clearMarks();
|
| | }
|
| | return std::make_pair(ret.second, ret.first->second);
|
| | }
|
| |
|
| | StringHasherRef Document::getStringHasher(const int idx) const
|
| | {
|
| | StringHasherRef hasher;
|
| | if (idx < 0) {
|
| | if (UseHasher.getValue()) {
|
| | return d->Hasher;
|
| | }
|
| | return hasher;
|
| | }
|
| | const auto it = d->hashers.right.find(idx);
|
| | if (it == d->hashers.right.end()) {
|
| | hasher = new StringHasher;
|
| | d->hashers.right.insert(HasherMap::right_map::value_type(idx, hasher));
|
| | }
|
| | else {
|
| | hasher = it->second;
|
| | }
|
| | return hasher;
|
| | }
|
| |
|
| | struct DocExportStatus
|
| | {
|
| | Document::ExportStatus status;
|
| | std::set<const DocumentObject*> objs;
|
| | };
|
| |
|
| | static DocExportStatus exportStatus;
|
| |
|
| |
|
| | class DocumentExporting
|
| | {
|
| | public:
|
| | explicit DocumentExporting(const std::vector<DocumentObject*>& objs)
|
| | {
|
| | exportStatus.status = Document::Exporting;
|
| | exportStatus.objs.insert(objs.begin(), objs.end());
|
| | }
|
| |
|
| | ~DocumentExporting()
|
| | {
|
| | exportStatus.status = Document::NotExporting;
|
| | exportStatus.objs.clear();
|
| | }
|
| | };
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | Document::ExportStatus Document::isExporting(const DocumentObject* obj) const
|
| | {
|
| | if (exportStatus.status != Document::NotExporting
|
| | && ((obj == nullptr) || exportStatus.objs.find(obj) != exportStatus.objs.end())) {
|
| | return exportStatus.status;
|
| | }
|
| | return Document::NotExporting;
|
| | }
|
| | ExportInfo Document::exportInfo() const
|
| | {
|
| | return d->exportInfo;
|
| | }
|
| | void Document::setExportInfo(const ExportInfo& info)
|
| | {
|
| | d->exportInfo = info;
|
| | }
|
| |
|
| | void Document::exportObjects(const std::vector<DocumentObject*>& obj, std::ostream& out)
|
| | {
|
| |
|
| | DocumentExporting exporting(obj);
|
| | d->hashers.clear();
|
| |
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | for (auto o : obj) {
|
| | if (o && o->isAttachedToDocument()) {
|
| | FC_LOG("exporting " << o->getFullName());
|
| | if (!o->getPropertyByName("_ObjectUUID")) {
|
| | auto prop = static_cast<PropertyUUID*>(
|
| | o->addDynamicProperty("App::PropertyUUID",
|
| | "_ObjectUUID",
|
| | nullptr,
|
| | nullptr,
|
| | Prop_Output | Prop_Hidden));
|
| | prop->setValue(Base::Uuid::createUuid());
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | Base::ZipWriter writer(out);
|
| | writer.putNextEntry("Document.xml");
|
| | writer.Stream() << "<?xml version='1.0' encoding='utf-8'?>" << '\n';
|
| | writer.Stream() << R"(<Document SchemaVersion="4" ProgramVersion=")"
|
| | << Application::Config()["BuildVersionMajor"] << "."
|
| | << Application::Config()["BuildVersionMinor"] << "R"
|
| | << Application::Config()["BuildRevision"] << R"(" FileVersion="1">)"
|
| | << '\n';
|
| |
|
| | writer.Stream() << "<Properties Count=\"0\">" << '\n';
|
| | writer.Stream() << "</Properties>" << '\n';
|
| |
|
| |
|
| | writeObjects(obj, writer);
|
| | writer.Stream() << "</Document>" << '\n';
|
| |
|
| |
|
| | signalExportObjects(obj, writer);
|
| |
|
| |
|
| | writer.writeFiles();
|
| | d->hashers.clear();
|
| | }
|
| |
|
| | constexpr auto fcAttrDependencies {"Dependencies"};
|
| | constexpr auto fcElementObjectDeps {"ObjectDeps"};
|
| | constexpr auto fcAttrDepCount {"Count"};
|
| | constexpr auto fcAttrDepObjName {"Name"};
|
| | constexpr auto fcAttrDepAllowPartial {"AllowPartial"};
|
| | constexpr auto fcElementObjectDep {"Dep"};
|
| |
|
| | void Document::writeObjects(const std::vector<DocumentObject*>& obj,
|
| | Base::Writer& writer) const
|
| | {
|
| |
|
| | writer.incInd();
|
| | writer.Stream() << writer.ind() << "<Objects Count=\"" << obj.size();
|
| | if (isExporting(nullptr) == 0U) {
|
| | writer.Stream() << "\" " << fcAttrDependencies << "=\"1";
|
| | }
|
| | writer.Stream() << "\">" << '\n';
|
| |
|
| | writer.incInd();
|
| |
|
| | if (isExporting(nullptr) == 0U) {
|
| | for (const auto o : obj) {
|
| | const auto& outList =
|
| | o->getOutList(DocumentObject::OutListNoHidden | DocumentObject::OutListNoXLinked);
|
| | writer.Stream() << writer.ind()
|
| | << "<" << fcElementObjectDeps << " " << fcAttrDepObjName << "=\""
|
| | << o->getNameInDocument() << "\" " << fcAttrDepCount << "=\""
|
| | << outList.size();
|
| | if (outList.empty()) {
|
| | writer.Stream() << "\"/>" << '\n';
|
| | continue;
|
| | }
|
| | const int partial = o->canLoadPartial();
|
| | if (partial > 0) {
|
| | writer.Stream() << "\" " << fcAttrDepAllowPartial << "=\"" << partial;
|
| | }
|
| | writer.Stream() << "\">" << '\n';
|
| | writer.incInd();
|
| | for (const auto dep : outList) {
|
| | const auto name = dep ? dep->getNameInDocument() : "";
|
| | writer.Stream() << writer.ind()
|
| | << "<" << fcElementObjectDep << " " << fcAttrDepObjName << "=\""
|
| | << (name ? name : "") << "\"/>" << '\n';
|
| | }
|
| | writer.decInd();
|
| | writer.Stream() << writer.ind() << "</" << fcElementObjectDeps << ">" << '\n';
|
| | }
|
| | }
|
| |
|
| | std::vector<DocumentObject*>::const_iterator it;
|
| | for (it = obj.begin(); it != obj.end(); ++it) {
|
| | writer.Stream() << writer.ind() << "<Object "
|
| | << "type=\"" << (*it)->getTypeId().getName() << "\" "
|
| | << "name=\"" << (*it)->getExportName() << "\" "
|
| | << "id=\"" << (*it)->getID() << "\" ";
|
| |
|
| |
|
| | std::string viewType = (*it)->getViewProviderNameStored();
|
| | if (viewType != (*it)->getViewProviderName()) {
|
| | writer.Stream() << "ViewType=\"" << viewType << "\" ";
|
| | }
|
| |
|
| |
|
| | if ((*it)->testStatus(ObjectStatus::Touch)) {
|
| | writer.Stream() << "Touched=\"1\" ";
|
| | }
|
| | if ((*it)->testStatus(ObjectStatus::Error)) {
|
| | writer.Stream() << "Invalid=\"1\" ";
|
| | const auto desc = getErrorDescription(*it);
|
| | if (desc) {
|
| | writer.Stream() << "Error=\"" << Property::encodeAttribute(desc) << "\" ";
|
| | }
|
| | }
|
| | writer.Stream() << "/>" << '\n';
|
| | }
|
| |
|
| | writer.decInd();
|
| | writer.Stream() << writer.ind() << "</Objects>" << '\n';
|
| |
|
| |
|
| | writer.Stream() << writer.ind() << "<ObjectData Count=\"" << obj.size() << "\">" << '\n';
|
| |
|
| | writer.incInd();
|
| | for (it = obj.begin(); it != obj.end(); ++it) {
|
| | writer.Stream() << writer.ind() << "<Object name=\"" << (*it)->getExportName() << "\"";
|
| | if ((*it)->hasExtensions()) {
|
| | writer.Stream() << " Extensions=\"True\"";
|
| | }
|
| |
|
| | writer.Stream() << ">" << '\n';
|
| | (*it)->Save(writer);
|
| | writer.Stream() << writer.ind() << "</Object>" << '\n';
|
| | }
|
| |
|
| | writer.decInd();
|
| | writer.Stream() << writer.ind() << "</ObjectData>" << '\n';
|
| | writer.decInd();
|
| | }
|
| |
|
| | struct DepInfo
|
| | {
|
| | std::unordered_set<std::string> deps;
|
| | int canLoadPartial = 0;
|
| | };
|
| |
|
| | static void loadDeps(const std::string& name,
|
| | std::unordered_map<std::string, bool>& objs,
|
| | const std::unordered_map<std::string, DepInfo>& deps)
|
| | {
|
| | const auto it = deps.find(name);
|
| | if (it == deps.end()) {
|
| | objs.emplace(name, true);
|
| | return;
|
| | }
|
| | if (it->second.canLoadPartial != 0) {
|
| | if (it->second.canLoadPartial == 1) {
|
| |
|
| |
|
| |
|
| | for (auto& dep : it->second.deps) {
|
| | objs.emplace(dep, false);
|
| | }
|
| | objs.emplace(name, true);
|
| | }
|
| | else {
|
| | objs.emplace(name, false);
|
| | }
|
| | return;
|
| | }
|
| | objs[name] = true;
|
| |
|
| | for (auto& dep : it->second.deps) {
|
| | if (auto found = objs.find(dep); found != objs.end() && found->second) {
|
| | continue;
|
| | }
|
| | loadDeps(dep, objs, deps);
|
| | }
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::readObjects(Base::XMLReader& reader)
|
| | {
|
| | d->touchedObjs.clear();
|
| | bool keepDigits = testStatus(Document::KeepTrailingDigits);
|
| | setStatus(Document::KeepTrailingDigits, !reader.doNameMapping());
|
| | std::vector<DocumentObject*> objs;
|
| |
|
| |
|
| |
|
| | reader.readElement("Objects");
|
| | int Cnt = static_cast<int>(reader.getAttribute<long>("Count"));
|
| |
|
| | if (!reader.hasAttribute(fcAttrDependencies)) {
|
| | d->partialLoadObjects.clear();
|
| | }
|
| | else if (!d->partialLoadObjects.empty()) {
|
| | std::unordered_map<std::string, DepInfo> deps;
|
| | for (int i = 0; i < Cnt; i++) {
|
| | reader.readElement(fcElementObjectDeps);
|
| | int dcount = static_cast<int>(reader.getAttribute<long>(fcAttrDepCount));
|
| | if (dcount == 0) {
|
| | continue;
|
| | }
|
| | auto& info = deps[reader.getAttribute<const char*>(fcAttrDepObjName)];
|
| | if (reader.hasAttribute(fcAttrDepAllowPartial)) {
|
| | info.canLoadPartial =
|
| | static_cast<int>(reader.getAttribute<long>(fcAttrDepAllowPartial));
|
| | }
|
| | for (int j = 0; j < dcount; ++j) {
|
| | reader.readElement(fcElementObjectDep);
|
| | const char* name = reader.getAttribute<const char*>(fcAttrDepObjName);
|
| | if (!Base::Tools::isNullOrEmpty(name)) {
|
| | info.deps.insert(name);
|
| | }
|
| | }
|
| | reader.readEndElement(fcElementObjectDeps);
|
| | }
|
| | std::vector<std::string> strings;
|
| | strings.reserve(d->partialLoadObjects.size());
|
| | for (auto& v : d->partialLoadObjects) {
|
| | strings.emplace_back(v.first.c_str());
|
| | }
|
| | for (auto& name : strings) {
|
| | loadDeps(name, d->partialLoadObjects, deps);
|
| | }
|
| | if (Cnt > static_cast<int>(d->partialLoadObjects.size())) {
|
| | setStatus(Document::PartialDoc, true);
|
| | }
|
| | else {
|
| | for (auto& v : d->partialLoadObjects) {
|
| | if (!v.second) {
|
| | setStatus(Document::PartialDoc, true);
|
| | break;
|
| | }
|
| | }
|
| | if (!testStatus(Document::PartialDoc)) {
|
| | d->partialLoadObjects.clear();
|
| | }
|
| | }
|
| | }
|
| |
|
| | long lastId = 0;
|
| | for (int i = 0; i < Cnt; i++) {
|
| | reader.readElement("Object");
|
| | std::string type = reader.getAttribute<const char*>("type");
|
| | std::string name = reader.getAttribute<const char*>("name");
|
| | std::string viewType =
|
| | reader.hasAttribute("ViewType") ? reader.getAttribute<const char*>("ViewType") : "";
|
| |
|
| | bool partial = false;
|
| | if (!d->partialLoadObjects.empty()) {
|
| | auto it = d->partialLoadObjects.find(name);
|
| | if (it == d->partialLoadObjects.end()) {
|
| | continue;
|
| | }
|
| | partial = !it->second;
|
| | }
|
| |
|
| | if (!testStatus(Status::Importing) && reader.hasAttribute("id")) {
|
| |
|
| |
|
| | d->lastObjectId = reader.getAttribute<long>("id") - 1;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | auto pos = name.find('@');
|
| | std::string _obj_name;
|
| | const char* obj_name {nullptr};
|
| | if (pos != std::string::npos) {
|
| | _obj_name = name.substr(0, pos);
|
| | obj_name = _obj_name.c_str();
|
| | }
|
| | else {
|
| | obj_name = name.c_str();
|
| | }
|
| |
|
| | try {
|
| |
|
| |
|
| |
|
| |
|
| | DocumentObject* obj =
|
| | addObject(type.c_str(), obj_name, false, viewType.c_str(), partial);
|
| | if (obj) {
|
| | if (lastId < obj->_Id) {
|
| | lastId = obj->_Id;
|
| | }
|
| | objs.push_back(obj);
|
| |
|
| |
|
| | reader.addName(name.c_str(), obj->getNameInDocument());
|
| |
|
| |
|
| | if (reader.hasAttribute("Touched")) {
|
| | if (reader.getAttribute<long>("Touched") != 0) {
|
| | d->touchedObjs.insert(obj);
|
| | }
|
| | }
|
| | if (reader.hasAttribute("Invalid")) {
|
| | obj->setStatus(ObjectStatus::Error,
|
| | reader.getAttribute<bool>("Invalid"));
|
| | if (obj->isError() && reader.hasAttribute("Error")) {
|
| | d->addRecomputeLog(reader.getAttribute<const char*>("Error"), obj);
|
| | }
|
| | }
|
| | }
|
| | }
|
| | catch (const Base::Exception& e) {
|
| | Base::Console().error("Cannot create object '%s': (%s)\n", name.c_str(), e.what());
|
| | }
|
| | }
|
| | if (!testStatus(Status::Importing)) {
|
| | d->lastObjectId = lastId;
|
| | }
|
| |
|
| | reader.readEndElement("Objects");
|
| | setStatus(Document::KeepTrailingDigits, keepDigits);
|
| |
|
| |
|
| | reader.clearPartialRestoreDocumentObject();
|
| | reader.readElement("ObjectData");
|
| | Cnt = static_cast<int>(reader.getAttribute<long>("Count"));
|
| | for (int i = 0; i < Cnt; i++) {
|
| | reader.readElement("Object");
|
| | std::string name = reader.getName(reader.getAttribute<const char*>("name"));
|
| | if (DocumentObject* pObj = getObject(name.c_str()); pObj
|
| | && !pObj->testStatus(
|
| | PartialObject)) {
|
| | pObj->setStatus(ObjectStatus::Restore, true);
|
| | try {
|
| | FC_TRACE("restoring " << pObj->getFullName());
|
| | pObj->Restore(reader);
|
| | }
|
| |
|
| |
|
| | catch (const Base::UnicodeError& e) {
|
| | e.reportException();
|
| | }
|
| | catch (const Base::ValueError& e) {
|
| | e.reportException();
|
| | }
|
| | catch (const Base::IndexError& e) {
|
| | e.reportException();
|
| | }
|
| | catch (const Base::RuntimeError& e) {
|
| | e.reportException();
|
| | }
|
| | catch (const Base::XMLAttributeError& e) {
|
| | e.reportException();
|
| | }
|
| |
|
| | pObj->setStatus(ObjectStatus::Restore, false);
|
| |
|
| | if (reader.testStatus(Base::XMLReader::ReaderStatus::PartialRestoreInDocumentObject)) {
|
| | Base::Console().error("Object \"%s\" was subject to a partial restore. As a result "
|
| | "geometry may have changed or be incomplete.\n",
|
| | name.c_str());
|
| | reader.clearPartialRestoreDocumentObject();
|
| | }
|
| | }
|
| | reader.readEndElement("Object");
|
| | }
|
| | reader.readEndElement("ObjectData");
|
| |
|
| | return objs;
|
| | }
|
| |
|
| | void Document::addRecomputeObject(DocumentObject* obj)
|
| | {
|
| | if (testStatus(Status::Restoring) && obj) {
|
| | setStatus(Status::RecomputeOnRestore, true);
|
| | d->touchedObjs.insert(obj);
|
| | obj->touch();
|
| | }
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::importObjects(Base::XMLReader& reader)
|
| | {
|
| | d->hashers.clear();
|
| | Base::FlagToggler<> flag(globalIsRestoring, false);
|
| | Base::ObjectStatusLocker<Status, Document> restoreBit(Status::Restoring, this);
|
| | Base::ObjectStatusLocker<Status, Document> restoreBit2(Status::Importing, this);
|
| | ExpressionParser::ExpressionImporter expImporter(reader);
|
| | reader.readElement("Document");
|
| | const long scheme = reader.getAttribute<long>("SchemaVersion");
|
| | reader.DocumentSchema = static_cast<int>(scheme);
|
| | if (reader.hasAttribute("ProgramVersion")) {
|
| | reader.ProgramVersion = reader.getAttribute<const char*>("ProgramVersion");
|
| | }
|
| | else {
|
| | reader.ProgramVersion = "pre-0.14";
|
| | }
|
| | if (reader.hasAttribute("FileVersion")) {
|
| | reader.FileVersion = static_cast<int>(reader.getAttribute<unsigned long>("FileVersion"));
|
| | }
|
| | else {
|
| | reader.FileVersion = 0;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> objs = readObjects(reader);
|
| | for (const auto o : objs) {
|
| | if (o && o->isAttachedToDocument()) {
|
| | o->setStatus(ObjImporting, true);
|
| | FC_LOG("importing " << o->getFullName());
|
| | if (const auto propUUID =
|
| | freecad_cast<PropertyUUID*>(o->getPropertyByName("_ObjectUUID"))) {
|
| | auto propSource =
|
| | freecad_cast<PropertyUUID*>(o->getPropertyByName("_SourceUUID"));
|
| | if (!propSource) {
|
| | propSource = static_cast<PropertyUUID*>(
|
| | o->addDynamicProperty("App::PropertyUUID",
|
| | "_SourceUUID",
|
| | nullptr,
|
| | nullptr,
|
| | Prop_Output | Prop_Hidden));
|
| | }
|
| | if (propSource) {
|
| | propSource->setValue(propUUID->getValue());
|
| | }
|
| | propUUID->setValue(Base::Uuid::createUuid());
|
| | }
|
| | }
|
| | }
|
| |
|
| | reader.readEndElement("Document");
|
| |
|
| | signalImportObjects(objs, reader);
|
| | afterRestore(objs, true);
|
| |
|
| | signalFinishImportObjects(objs);
|
| |
|
| | for (const auto o : objs) {
|
| | if (o && o->isAttachedToDocument()) {
|
| | o->setStatus(ObjImporting, false);
|
| | }
|
| | }
|
| |
|
| | d->hashers.clear();
|
| | return objs;
|
| | }
|
| |
|
| | unsigned int Document::getMemSize() const
|
| | {
|
| | unsigned int size = 0;
|
| |
|
| |
|
| | for (const auto & it : d->objectArray) {
|
| | size += it->getMemSize();
|
| | }
|
| |
|
| | size += d->Hasher->getMemSize();
|
| |
|
| |
|
| | size += PropertyContainer::getMemSize();
|
| |
|
| |
|
| | size += getUndoMemSize();
|
| |
|
| | return size;
|
| | }
|
| |
|
| | static std::string checkFileName(const char* file)
|
| | {
|
| | std::string fn(file);
|
| |
|
| |
|
| |
|
| | if (GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetBool("CheckExtension", true)) {
|
| | const char* ext = strrchr(file, '.');
|
| | if ((ext == nullptr) || !boost::iequals(ext + 1, "fcstd")) {
|
| | if (ext && ext[1] == 0) {
|
| | fn += "FCStd";
|
| | }
|
| | else {
|
| | fn += ".FCStd";
|
| | }
|
| | }
|
| | }
|
| | return fn;
|
| | }
|
| |
|
| | bool Document::saveAs(const char* _file)
|
| | {
|
| | const std::string file = checkFileName(_file);
|
| | const Base::FileInfo fi(file.c_str());
|
| | if (this->FileName.getStrValue() != file) {
|
| | this->FileName.setValue(file);
|
| | this->Label.setValue(fi.fileNamePure());
|
| | this->Uid.touch();
|
| | }
|
| |
|
| | return save();
|
| | }
|
| |
|
| | bool Document::saveCopy(const char* file) const
|
| | {
|
| | const std::string checked = checkFileName(file);
|
| | return this->FileName.getStrValue() != checked ? saveToFile(checked.c_str()) : false;
|
| | }
|
| |
|
| |
|
| | bool Document::save()
|
| | {
|
| | if (testStatus(Document::PartialDoc)) {
|
| | FC_ERR("Partial loaded document '" << Label.getValue() << "' cannot be saved");
|
| |
|
| |
|
| |
|
| | return true;
|
| | }
|
| |
|
| | if (*(FileName.getValue()) != '\0') {
|
| |
|
| | if (Tip.getValue()) {
|
| | TipName.setValue(Tip.getValue()->getNameInDocument());
|
| | }
|
| |
|
| | const std::string LastModifiedDateString = Base::Tools::currentDateTimeString();
|
| | LastModifiedDate.setValue(LastModifiedDateString.c_str());
|
| |
|
| | const bool saveAuthor =
|
| | GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetBool("prefSetAuthorOnSave", false);
|
| | if (saveAuthor) {
|
| | const std::string Author =
|
| | GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetASCII("prefAuthor", "");
|
| | LastModifiedBy.setValue(Author.c_str());
|
| | }
|
| |
|
| | return saveToFile(FileName.getValue());
|
| | }
|
| |
|
| | return false;
|
| | }
|
| |
|
| | bool Document::saveToFile(const char* filename) const
|
| | {
|
| | signalStartSave(*this, filename);
|
| |
|
| | auto hGrp = GetApplication().GetParameterGroupByPath(
|
| | "User parameter:BaseApp/Preferences/Document");
|
| | int compression = static_cast<int>(hGrp->GetInt("CompressionLevel", 7));
|
| | compression = Base::clamp<int>(compression, Z_NO_COMPRESSION, Z_BEST_COMPRESSION);
|
| |
|
| | bool policy = GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetBool("BackupPolicy", true);
|
| |
|
| | auto canonical_path = [](const char* filename) {
|
| | try {
|
| | #ifdef FC_OS_WIN32
|
| | QString utf8Name = QString::fromUtf8(filename);
|
| | auto realpath = fs::weakly_canonical(fs::absolute(fs::path(utf8Name.toStdWString())));
|
| | std::string nativePath = QString::fromStdWString(realpath.native()).toStdString();
|
| | #else
|
| | auto realpath = fs::weakly_canonical(fs::absolute(fs::path(filename)));
|
| | std::string nativePath = realpath.native();
|
| | #endif
|
| |
|
| | auto parentPath = realpath.parent_path();
|
| | fs::create_directories(parentPath);
|
| |
|
| | return nativePath;
|
| | }
|
| | catch (const std::exception&) {
|
| | #ifdef FC_OS_WIN32
|
| | QString utf8Name = QString::fromUtf8(filename);
|
| | auto parentPath = fs::absolute(fs::path(utf8Name.toStdWString())).parent_path();
|
| | #else
|
| | auto parentPath = fs::absolute(fs::path(filename)).parent_path();
|
| | #endif
|
| | fs::create_directories(parentPath);
|
| |
|
| | return std::string(filename);
|
| | }
|
| | };
|
| |
|
| |
|
| | std::string nativePath = canonical_path(filename);
|
| |
|
| |
|
| | Base::FileInfo originalFileInfo(nativePath);
|
| | if (originalFileInfo.exists() && !originalFileInfo.isWritable()) {
|
| | throw Base::FileException("Unable to save document because file is marked as read-only or write permission is not available.", originalFileInfo);
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | std::string uuid = Base::Uuid::createUuid();
|
| | std::string fn = nativePath;
|
| | if (policy) {
|
| | fn += ".";
|
| | fn += uuid;
|
| | }
|
| |
|
| |
|
| |
|
| | {
|
| | Base::FileInfo tmp(fn);
|
| | Base::ofstream file(tmp, std::ios::out | std::ios::binary);
|
| |
|
| | Base::ZipWriter writer(file);
|
| | if (!file.is_open()) {
|
| | throw Base::FileException("Failed to open file", tmp);
|
| | }
|
| |
|
| | writer.setComment("FreeCAD Document");
|
| | writer.setLevel(compression);
|
| | writer.putNextEntry("Document.xml");
|
| |
|
| | if (hGrp->GetBool("SaveBinaryBrep", false)) {
|
| | writer.setMode("BinaryBrep");
|
| | }
|
| |
|
| | writer.Stream() << "<?xml version='1.0' encoding='utf-8'?>" << '\n'
|
| | << "<!--" << '\n'
|
| | << " FreeCAD Document, see https://www.freecad.org for more information..."
|
| | << '\n'
|
| | << "-->" << '\n';
|
| | Document::Save(writer);
|
| |
|
| |
|
| | signalSaveDocument(writer);
|
| |
|
| |
|
| | writer.writeFiles();
|
| | if (writer.hasErrors()) {
|
| |
|
| | std::stringstream message;
|
| | message << "Failed to write all data to file ";
|
| | message << writer.getErrors().front();
|
| | throw Base::FileException(message.str().c_str(), tmp);
|
| | }
|
| |
|
| | GetApplication().signalSaveDocument(*this);
|
| | }
|
| |
|
| | if (policy) {
|
| |
|
| | int count_bak = static_cast<int>(GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetInt("CountBackupFiles", 1));
|
| | bool backup = GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetBool("CreateBackupFiles", true);
|
| | if (!backup) {
|
| | count_bak = -1;
|
| | }
|
| | bool useFCBakExtension =
|
| | GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetBool("UseFCBakExtension", true);
|
| | std::string saveBackupDateFormat =
|
| | GetApplication()
|
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
| | ->GetASCII("SaveBackupDateFormat", "%Y%m%d-%H%M%S");
|
| |
|
| | BackupPolicy backupPolicy;
|
| | if (useFCBakExtension) {
|
| | backupPolicy.setPolicy(BackupPolicy::TimeStamp);
|
| | backupPolicy.useBackupExtension(useFCBakExtension);
|
| | backupPolicy.setDateFormat(saveBackupDateFormat);
|
| | }
|
| | else {
|
| | backupPolicy.setPolicy(BackupPolicy::Standard);
|
| | }
|
| | backupPolicy.setNumberOfFiles(count_bak);
|
| | backupPolicy.apply(fn, nativePath);
|
| | }
|
| |
|
| | signalFinishSave(*this, filename);
|
| |
|
| | return true;
|
| | }
|
| |
|
| | void Document::registerLabel(const std::string& newLabel)
|
| | {
|
| | if (!newLabel.empty()) {
|
| | d->objectLabelManager.addExactName(newLabel);
|
| | }
|
| | }
|
| |
|
| | void Document::unregisterLabel(const std::string& oldLabel)
|
| | {
|
| | if (!oldLabel.empty()) {
|
| | d->objectLabelManager.removeExactName(oldLabel);
|
| | }
|
| | }
|
| |
|
| | bool Document::containsLabel(const std::string& label)
|
| | {
|
| | return d->objectLabelManager.containsName(label);
|
| | }
|
| |
|
| | std::string Document::makeUniqueLabel(const std::string& modelLabel)
|
| | {
|
| | if (modelLabel.empty()) {
|
| | return {};
|
| | }
|
| |
|
| | return d->objectLabelManager.makeUniqueName(modelLabel, 3);
|
| | }
|
| |
|
| | bool Document::isAnyRestoring()
|
| | {
|
| | return globalIsRestoring;
|
| | }
|
| |
|
| |
|
| | void Document::restore(const char* filename,
|
| | bool delaySignal,
|
| | const std::vector<std::string>& objNames)
|
| | {
|
| | clearUndos();
|
| | d->activeObject = nullptr;
|
| |
|
| | bool signal = false;
|
| | Document* activeDoc = GetApplication().getActiveDocument();
|
| | if (!d->objectArray.empty()) {
|
| | signal = true;
|
| | GetApplication().signalDeleteDocument(*this);
|
| | d->clearDocument();
|
| | }
|
| |
|
| | Base::FlagToggler<> flag(globalIsRestoring, false);
|
| |
|
| | setStatus(Document::PartialDoc, false);
|
| |
|
| | d->clearRecomputeLog();
|
| | d->objectLabelManager.clear();
|
| | d->objectArray.clear();
|
| | d->objectNameManager.clear();
|
| | d->objectMap.clear();
|
| | d->objectIdMap.clear();
|
| | d->lastObjectId = 0;
|
| |
|
| | if (signal) {
|
| | GetApplication().signalNewDocument(*this, true);
|
| | if (activeDoc == this) {
|
| | GetApplication().setActiveDocument(this);
|
| | }
|
| | }
|
| |
|
| | if (!filename) {
|
| | filename = FileName.getValue();
|
| | }
|
| | Base::FileInfo fi(filename);
|
| | Base::ifstream file(fi, std::ios::in | std::ios::binary);
|
| | std::streambuf* buf = file.rdbuf();
|
| | std::streamoff size = buf->pubseekoff(0, std::ios::end, std::ios::in);
|
| | buf->pubseekoff(0, std::ios::beg, std::ios::in);
|
| | if (size < 22) {
|
| | throw Base::FileException("Invalid project file", filename);
|
| | }
|
| |
|
| | zipios::ZipInputStream zipstream(file);
|
| | Base::XMLReader reader(filename, zipstream);
|
| |
|
| | if (!reader.isValid()) {
|
| | throw Base::FileException("Error reading compression file", filename);
|
| | }
|
| |
|
| | GetApplication().signalStartRestoreDocument(*this);
|
| | setStatus(Document::Restoring, true);
|
| |
|
| | d->partialLoadObjects.clear();
|
| | for (auto& name : objNames) {
|
| | d->partialLoadObjects.emplace(name, true);
|
| | }
|
| | try {
|
| | Document::Restore(reader);
|
| | }
|
| | catch (const Base::Exception& e) {
|
| | Base::Console().error("Invalid Document.xml: %s\n", e.what());
|
| | setStatus(Document::RestoreError, true);
|
| | }
|
| |
|
| | d->partialLoadObjects.clear();
|
| | d->programVersion = reader.ProgramVersion;
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | signalRestoreDocument(reader);
|
| | reader.readFiles(zipstream);
|
| |
|
| | DocumentP::checkStringHasher(reader);
|
| |
|
| | if (reader.testStatus(Base::XMLReader::ReaderStatus::PartialRestore)) {
|
| | setStatus(Document::PartialRestore, true);
|
| | Base::Console().error("There were errors while loading the file. Some data might have been "
|
| | "modified or not recovered at all. Look above for more specific "
|
| | "information about the objects involved.\n");
|
| | }
|
| |
|
| | if (!delaySignal) {
|
| | afterRestore(true);
|
| | }
|
| | }
|
| |
|
| | bool Document::afterRestore(const bool checkPartial)
|
| | {
|
| | Base::FlagToggler<> flag(globalIsRestoring, false);
|
| | if (!afterRestore(d->objectArray, checkPartial)) {
|
| | FC_WARN("Reload partial document " << getName());
|
| | GetApplication().signalPendingReloadDocument(*this);
|
| | return false;
|
| | }
|
| | GetApplication().signalFinishRestoreDocument(*this);
|
| | setStatus(Document::Restoring, false);
|
| | return true;
|
| | }
|
| |
|
| | bool Document::afterRestore(const std::vector<DocumentObject*>& objArray, bool checkPartial)
|
| | {
|
| | checkPartial = checkPartial && testStatus(Document::PartialDoc);
|
| | if (checkPartial && !d->touchedObjs.empty()) {
|
| | return false;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | std::map<DocumentObject*, std::vector<Property*>> propMap;
|
| | for (auto obj : objArray) {
|
| | auto& props = propMap[obj];
|
| | obj->getPropertyList(props);
|
| | for (auto prop : props) {
|
| | try {
|
| | prop->afterRestore();
|
| | }
|
| | catch (const Base::Exception& e) {
|
| | FC_ERR("Failed to restore " << obj->getFullName() << '.' << prop->getName() << ": "
|
| | << e.what());
|
| | }
|
| | }
|
| | }
|
| |
|
| | if (checkPartial && !d->touchedObjs.empty()) {
|
| |
|
| | return false;
|
| | }
|
| |
|
| | std::set<DocumentObject*> objSet(objArray.begin(), objArray.end());
|
| | auto objs = getDependencyList(objArray.empty() ? d->objectArray : objArray, DepSort);
|
| | for (auto obj : objs) {
|
| | if (objSet.find(obj) == objSet.end()) {
|
| | continue;
|
| | }
|
| | try {
|
| | for (auto prop : propMap[obj]) {
|
| | prop->onContainerRestored();
|
| | }
|
| | bool touched = false;
|
| | auto returnCode =
|
| | obj->ExpressionEngine.execute(PropertyExpressionEngine::ExecuteOnRestore, &touched);
|
| | if (returnCode != DocumentObject::StdReturn) {
|
| | FC_ERR("Expression engine failed to restore " << obj->getFullName() << ": "
|
| | << returnCode->Why);
|
| | d->addRecomputeLog(returnCode);
|
| | }
|
| | obj->onDocumentRestored();
|
| | if (touched) {
|
| | d->touchedObjs.insert(obj);
|
| | }
|
| | }
|
| | catch (const Base::Exception& e) {
|
| | d->addRecomputeLog(e.what(), obj);
|
| | FC_ERR("Failed to restore " << obj->getFullName() << ": " << e.what());
|
| | }
|
| | catch (std::exception& e) {
|
| | d->addRecomputeLog(e.what(), obj);
|
| | FC_ERR("Failed to restore " << obj->getFullName() << ": " << e.what());
|
| | }
|
| | catch (...) {
|
| |
|
| |
|
| |
|
| |
|
| | if (PyErr_Occurred()) {
|
| | Base::Console().error("Python error during object restore:\n");
|
| | PyErr_Print();
|
| | PyErr_Clear();
|
| | }
|
| |
|
| | d->addRecomputeLog("Unknown exception on restore", obj);
|
| | FC_ERR("Failed to restore " << obj->getFullName() << ": " << "unknown exception");
|
| | }
|
| | if (obj->isValid()) {
|
| | auto& props = propMap[obj];
|
| | props.clear();
|
| |
|
| | obj->getPropertyList(props);
|
| | for (auto prop : props) {
|
| | auto link = freecad_cast<PropertyLinkBase*>(prop);
|
| | int res {0};
|
| | std::string errMsg;
|
| | if (link && ((res = link->checkRestore(&errMsg)) != 0)) {
|
| | d->touchedObjs.insert(obj);
|
| | if (res == 1 || checkPartial) {
|
| | FC_WARN(obj->getFullName() << '.' << prop->getName() << ": " << errMsg);
|
| | setStatus(Document::LinkStampChanged, true);
|
| | if (checkPartial) {
|
| | return false;
|
| | }
|
| | }
|
| | else {
|
| | FC_ERR(obj->getFullName() << '.' << prop->getName() << ": " << errMsg);
|
| | d->addRecomputeLog(errMsg, obj);
|
| | setStatus(Document::PartialRestore, true);
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | if (checkPartial && !d->touchedObjs.empty()) {
|
| |
|
| | return false;
|
| | }
|
| | if (!d->touchedObjs.contains(obj)) {
|
| | obj->purgeTouched();
|
| | }
|
| |
|
| | signalFinishRestoreObject(*obj);
|
| | }
|
| |
|
| | d->touchedObjs.clear();
|
| | return true;
|
| | }
|
| |
|
| | bool Document::isSaved() const
|
| | {
|
| | const std::string name = FileName.getValue();
|
| | return !name.empty();
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | const char* Document::getName() const
|
| | {
|
| |
|
| | return myName.c_str();
|
| | }
|
| |
|
| | std::string Document::getFullName() const
|
| | {
|
| | return myName;
|
| | }
|
| |
|
| | void Document::setAutoCreated(bool value) {
|
| | autoCreated = value;
|
| | }
|
| |
|
| | bool Document::isAutoCreated() const {
|
| | return autoCreated;
|
| | }
|
| |
|
| | const char* Document::getProgramVersion() const
|
| | {
|
| | return d->programVersion.c_str();
|
| | }
|
| |
|
| | const char* Document::getFileName() const
|
| | {
|
| | return testStatus(TempDoc) ? TransientDir.getValue() : FileName.getValue();
|
| | }
|
| |
|
| |
|
| | void Document::purgeTouched()
|
| | {
|
| | for (const auto It : d->objectArray) {
|
| | It->purgeTouched();
|
| | }
|
| | }
|
| |
|
| | bool Document::isTouched() const
|
| | {
|
| | for (const auto It : d->objectArray) {
|
| | if (It->isTouched()) {
|
| | return true;
|
| | }
|
| | }
|
| | return false;
|
| | }
|
| |
|
| | vector<DocumentObject*> Document::getTouched() const
|
| | {
|
| | vector<DocumentObject*> result;
|
| |
|
| | for (auto It : d->objectArray) {
|
| | if (It->isTouched()) {
|
| | result.push_back(It);
|
| | }
|
| | }
|
| |
|
| | return result;
|
| | }
|
| |
|
| | void Document::setClosable(const bool c)
|
| | {
|
| | setStatus(Document::Closable, c);
|
| | }
|
| |
|
| | bool Document::isClosable() const
|
| | {
|
| | return testStatus(Document::Closable);
|
| | }
|
| |
|
| | int Document::countObjects() const
|
| | {
|
| | return static_cast<int>(d->objectArray.size());
|
| | }
|
| |
|
| | void Document::getLinksTo(std::set<DocumentObject*>& links,
|
| | const DocumentObject* obj,
|
| | const int options,
|
| | const int maxCount,
|
| | const std::vector<DocumentObject*>& objs) const
|
| | {
|
| | std::map<const DocumentObject*, std::vector<DocumentObject*>> linkMap;
|
| |
|
| | for (auto o : !objs.empty() ? objs : d->objectArray) {
|
| | if (o == obj) {
|
| | continue;
|
| | }
|
| | auto linked = o;
|
| | if ((options & GetLinkArrayElement) != 0) {
|
| | linked = o->getLinkedObject(false);
|
| | }
|
| | else {
|
| | const auto ext = o->getExtensionByType<LinkBaseExtension>(true);
|
| | linked =
|
| | ext ? ext->getTrueLinkedObject(false, nullptr, 0, true) : o->getLinkedObject(false);
|
| | }
|
| |
|
| | if (linked && linked != o) {
|
| | if ((options & GetLinkRecursive) != 0) {
|
| | linkMap[linked].push_back(o);
|
| | }
|
| | else if (linked == obj || !obj) {
|
| | if (((options & GetLinkExternal) != 0) && linked->getDocument() == o->getDocument()) {
|
| | continue;
|
| | }
|
| | if ((options & GetLinkedObject) != 0) {
|
| | links.insert(linked);
|
| | }
|
| | else {
|
| | links.insert(o);
|
| | }
|
| | if ((maxCount != 0) && maxCount <= static_cast<int>(links.size())) {
|
| | return;
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | if ((options & GetLinkRecursive) == 0) {
|
| | return;
|
| | }
|
| |
|
| | std::vector<const DocumentObject*> current(1, obj);
|
| | for (int depth = 0; !current.empty(); ++depth) {
|
| | if (GetApplication().checkLinkDepth(depth, MessageOption::Error) == 0) {
|
| | break;
|
| | }
|
| | std::vector<const DocumentObject*> next;
|
| | for (const DocumentObject* o : current) {
|
| | auto iter = linkMap.find(o);
|
| | if (iter == linkMap.end()) {
|
| | continue;
|
| | }
|
| | for (DocumentObject* link : iter->second) {
|
| | if (links.insert(link).second) {
|
| | if ((maxCount != 0) && maxCount <= static_cast<int>(links.size())) {
|
| | return;
|
| | }
|
| | next.push_back(link);
|
| | }
|
| | }
|
| | }
|
| | current = std::move(next);
|
| | }
|
| | }
|
| |
|
| | bool Document::hasLinksTo(const DocumentObject* obj) const
|
| | {
|
| | std::set<DocumentObject*> links;
|
| | getLinksTo(links, obj, 0, 1);
|
| | return !links.empty();
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::getInList(const DocumentObject* me) const
|
| | {
|
| |
|
| | std::vector<DocumentObject*> result;
|
| |
|
| | for (const auto& [name, object] : d->objectMap) {
|
| |
|
| | std::vector<DocumentObject*> OutList = object->getOutList();
|
| | for (const auto obj : OutList) {
|
| | if (obj && obj == me) {
|
| |
|
| | result.push_back(object);
|
| | }
|
| | }
|
| | }
|
| | return result;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | static void buildDependencyList(const std::vector<DocumentObject*>& objectArray,
|
| | const int options,
|
| | std::vector<DocumentObject*>* depObjs,
|
| | DependencyList* depList,
|
| | std::map<DocumentObject*, Vertex>* objectMap,
|
| | bool* touchCheck = nullptr)
|
| | {
|
| | std::map<DocumentObject*, std::vector<DocumentObject*>> outLists;
|
| | std::deque<DocumentObject*> objs;
|
| |
|
| | if (objectMap) {
|
| | objectMap->clear();
|
| | }
|
| | if (depList) {
|
| | depList->clear();
|
| | }
|
| |
|
| | const int op = ((options & Document::DepNoXLinked) != 0) ? DocumentObject::OutListNoXLinked : 0;
|
| | for (auto obj : objectArray) {
|
| | objs.push_back(obj);
|
| | while (!objs.empty()) {
|
| | auto objF = objs.front();
|
| | objs.pop_front();
|
| | if (!objF || !objF->isAttachedToDocument()) {
|
| | continue;
|
| | }
|
| |
|
| | auto it = outLists.find(objF);
|
| | if (it != outLists.end()) {
|
| | continue;
|
| | }
|
| |
|
| | if (touchCheck) {
|
| | if (objF->isTouched() || (objF->mustExecute() != 0)) {
|
| |
|
| | *touchCheck = true;
|
| | return;
|
| | }
|
| | }
|
| | if (depObjs) {
|
| | depObjs->push_back(objF);
|
| | }
|
| | if (objectMap && depList) {
|
| | (*objectMap)[objF] = add_vertex(*depList);
|
| | }
|
| |
|
| | auto& outList = outLists[objF];
|
| | outList = objF->getOutList(op);
|
| | objs.insert(objs.end(), outList.begin(), outList.end());
|
| | }
|
| | }
|
| |
|
| | if (objectMap && depList) {
|
| | for (const auto& [key, objects] : outLists) {
|
| | for (auto obj : objects) {
|
| | if (obj && obj->isAttachedToDocument()) {
|
| | add_edge((*objectMap)[key], (*objectMap)[obj], *depList);
|
| | }
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | std::vector<DocumentObject*>
|
| | Document::getDependencyList(const std::vector<DocumentObject*>& objs, int options)
|
| | {
|
| | std::vector<DocumentObject*> ret;
|
| | if ((options & DepSort) == 0) {
|
| | buildDependencyList(objs, options, &ret, nullptr, nullptr);
|
| | return ret;
|
| | }
|
| |
|
| | DependencyList depList;
|
| | std::map<DocumentObject*, Vertex> objectMap;
|
| | std::map<Vertex, DocumentObject*> vertexMap;
|
| |
|
| | buildDependencyList(objs, options, nullptr, &depList, &objectMap);
|
| |
|
| | for (auto& v : objectMap) {
|
| | vertexMap[v.second] = v.first;
|
| | }
|
| |
|
| | std::list<Vertex> make_order;
|
| | try {
|
| | boost::topological_sort(depList, std::front_inserter(make_order));
|
| | }
|
| | catch (const std::exception& e) {
|
| | if ((options & DepNoCycle) != 0) {
|
| |
|
| |
|
| |
|
| | std::vector<int> c(vertexMap.size());
|
| | std::map<int, std::vector<Vertex>> components;
|
| | boost::strong_components(
|
| | depList,
|
| | boost::make_iterator_property_map(c.begin(),
|
| | boost::get(boost::vertex_index, depList),
|
| | c[0]));
|
| | for (size_t i = 0; i < c.size(); ++i) {
|
| | components[c[i]].push_back(i);
|
| | }
|
| |
|
| | FC_ERR("Dependency cycles: ");
|
| | std::ostringstream ss;
|
| | ss << '\n';
|
| | for (auto& [key, vertexes] : components) {
|
| | if (vertexes.size() == 1) {
|
| |
|
| |
|
| | auto it = vertexMap.find(vertexes[0]);
|
| | if (it == vertexMap.end()) {
|
| | continue;
|
| | }
|
| |
|
| | for (auto obj : it->second->getOutList()) {
|
| | if (obj == it->second) {
|
| | ss << '\n' << it->second->getFullName() << '\n';
|
| | break;
|
| | }
|
| | }
|
| | continue;
|
| | }
|
| |
|
| | for (size_t i = 0; i < vertexes.size(); ++i) {
|
| | auto it = vertexMap.find(vertexes[i]);
|
| | if (it == vertexMap.end()) {
|
| | continue;
|
| | }
|
| | if (i % 6 == 0) {
|
| | ss << '\n';
|
| | }
|
| | ss << it->second->getFullName() << ", ";
|
| | }
|
| | ss << '\n';
|
| | }
|
| | FC_ERR(ss.str());
|
| | FC_THROWM(Base::RuntimeError, e.what());
|
| | }
|
| | FC_ERR(e.what());
|
| | ret = DocumentP::partialTopologicalSort(objs);
|
| | std::reverse(ret.begin(), ret.end());
|
| | return ret;
|
| | }
|
| |
|
| | for (auto i = make_order.rbegin(); i != make_order.rend(); ++i) {
|
| | ret.push_back(vertexMap[*i]);
|
| | }
|
| | return ret;
|
| | }
|
| |
|
| | std::vector<Document*> Document::getDependentDocuments(const bool sort)
|
| | {
|
| | return getDependentDocuments({this}, sort);
|
| | }
|
| |
|
| | std::vector<Document*> Document::getDependentDocuments(std::vector<Document*> docs,
|
| | const bool sort)
|
| | {
|
| | DependencyList depList;
|
| | std::map<Document*, Vertex> docMap;
|
| | std::map<Vertex, Document*> vertexMap;
|
| |
|
| | std::vector<Document*> ret;
|
| | if (docs.empty()) {
|
| | return ret;
|
| | }
|
| |
|
| | auto outLists = PropertyXLink::getDocumentOutList();
|
| | std::set<Document*> docSet;
|
| | docSet.insert(docs.begin(), docs.end());
|
| | if (sort) {
|
| | for (auto doc : docs) {
|
| | docMap[doc] = add_vertex(depList);
|
| | }
|
| | }
|
| | while (!docs.empty()) {
|
| | auto doc = docs.back();
|
| | docs.pop_back();
|
| |
|
| | auto it = outLists.find(doc);
|
| | if (it == outLists.end()) {
|
| | continue;
|
| | }
|
| |
|
| | const auto& vertex = docMap[doc];
|
| | for (auto depDoc : it->second) {
|
| | if (docSet.insert(depDoc).second) {
|
| | docs.push_back(depDoc);
|
| | if (sort) {
|
| | docMap[depDoc] = add_vertex(depList);
|
| | }
|
| | }
|
| | add_edge(vertex, docMap[depDoc], depList);
|
| | }
|
| | }
|
| |
|
| | if (!sort) {
|
| | ret.insert(ret.end(), docSet.begin(), docSet.end());
|
| | return ret;
|
| | }
|
| |
|
| | std::list<Vertex> make_order;
|
| | try {
|
| | boost::topological_sort(depList, std::front_inserter(make_order));
|
| | }
|
| | catch (const std::exception& e) {
|
| | std::string msg("Document::getDependentDocuments: ");
|
| | msg += e.what();
|
| | throw Base::RuntimeError(msg);
|
| | }
|
| |
|
| | for (auto& v : docMap) {
|
| | vertexMap[v.second] = v.first;
|
| | }
|
| | for (auto rIt = make_order.rbegin(); rIt != make_order.rend(); ++rIt) {
|
| | ret.push_back(vertexMap[*rIt]);
|
| | }
|
| | return ret;
|
| | }
|
| |
|
| | void Document::_rebuildDependencyList(const std::vector<DocumentObject*>& objs)
|
| | {
|
| | (void)objs;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | void Document::renameObjectIdentifiers(
|
| | const std::map<ObjectIdentifier, ObjectIdentifier>& paths,
|
| | const std::function<bool(const DocumentObject*)>& selector)
|
| | {
|
| | std::map<ObjectIdentifier, ObjectIdentifier> extendedPaths;
|
| |
|
| | auto it = paths.begin();
|
| | while (it != paths.end()) {
|
| | extendedPaths[it->first.canonicalPath()] = it->second.canonicalPath();
|
| | ++it;
|
| | }
|
| |
|
| | for (const auto object : d->objectArray) {
|
| | if (selector(object)) {
|
| | object->renameObjectIdentifiers(extendedPaths);
|
| | }
|
| | }
|
| | }
|
| |
|
| | void Document::setPreRecomputeHook(const PreRecomputeHook& hook)
|
| | {
|
| | d->_preRecomputeHook = hook;
|
| | }
|
| |
|
| | int Document::recompute(const std::vector<DocumentObject*>& objs,
|
| | bool force,
|
| | bool* hasError,
|
| | int options)
|
| | {
|
| | ZoneScoped;
|
| |
|
| | if (d->undoing || d->rollback) {
|
| | if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
|
| | FC_WARN("Ignore document recompute on undo/redo");
|
| | }
|
| | return 0;
|
| | }
|
| |
|
| | int objectCount = 0;
|
| | if (testStatus(Document::PartialDoc)) {
|
| | if (mustExecute()) {
|
| | FC_WARN("Please reload partial document '" << Label.getValue()
|
| | << "' for recomputation.");
|
| | }
|
| | return 0;
|
| | }
|
| | if (testStatus(Document::Recomputing)) {
|
| |
|
| | FC_ERR("Recursive calling of recompute for document " << getName());
|
| | return 0;
|
| | }
|
| |
|
| |
|
| | if (!force && testStatus(Document::SkipRecompute)) {
|
| | signalSkipRecompute(*this, objs);
|
| | return 0;
|
| | }
|
| |
|
| |
|
| | d->clearRecomputeLog();
|
| |
|
| | FC_TIME_INIT(t);
|
| |
|
| | Base::ObjectStatusLocker<Document::Status, Document> exe(Document::Recomputing, this);
|
| |
|
| |
|
| |
|
| |
|
| | if (d->_preRecomputeHook) {
|
| | d->_preRecomputeHook();
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| |
|
| | auto topoSortedObjects =
|
| | getDependencyList(objs.empty() ? d->objectArray : objs, DepSort | options);
|
| |
|
| | for (auto obj : topoSortedObjects) {
|
| | obj->setStatus(ObjectStatus::PendingRecompute, true);
|
| | }
|
| |
|
| | ParameterGrp::handle hGrp =
|
| | GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document");
|
| | bool canAbort = hGrp->GetBool("CanAbortRecompute", true);
|
| |
|
| | FC_TIME_INIT(t2);
|
| |
|
| | try {
|
| | std::set<DocumentObject*> filter;
|
| | size_t idx = 0;
|
| |
|
| | for (int passes = 0; passes < 2 && idx < topoSortedObjects.size(); ++passes) {
|
| | std::unique_ptr<Base::SequencerLauncher> seq;
|
| | if (canAbort) {
|
| | seq = std::make_unique<Base::SequencerLauncher>("Recompute...",
|
| | topoSortedObjects.size());
|
| | }
|
| | FC_LOG("Recompute pass " << passes);
|
| | for (; idx < topoSortedObjects.size(); ++idx) {
|
| | auto obj = topoSortedObjects[idx];
|
| | if (!obj->isAttachedToDocument() || filter.find(obj) != filter.end()) {
|
| | continue;
|
| | }
|
| |
|
| | bool doRecompute = false;
|
| | if (obj->mustRecompute()) {
|
| | doRecompute = true;
|
| | ++objectCount;
|
| | int res = _recomputeFeature(obj);
|
| | if (res != 0) {
|
| | if (hasError) {
|
| | *hasError = true;
|
| | }
|
| | if (res < 0) {
|
| | passes = 2;
|
| | break;
|
| | }
|
| |
|
| |
|
| | obj->getInListEx(filter, true);
|
| | filter.insert(obj);
|
| | continue;
|
| | }
|
| | }
|
| | if (obj->isTouched() || doRecompute) {
|
| | signalRecomputedObject(*obj);
|
| | obj->purgeTouched();
|
| |
|
| | for (auto inObjIt : obj->getInList()) {
|
| | inObjIt->enforceRecompute();
|
| | }
|
| | }
|
| | if (seq) {
|
| | seq->next(true);
|
| | }
|
| | }
|
| |
|
| | for (size_t i = 0; i < topoSortedObjects.size(); ++i) {
|
| | auto obj = topoSortedObjects[i];
|
| | obj->setStatus(ObjectStatus::Recompute2, false);
|
| | if (!filter.contains(obj) && obj->isTouched()) {
|
| | if (passes > 0) {
|
| | FC_ERR(obj->getFullName() << " still touched after recompute");
|
| | }
|
| | else {
|
| | FC_LOG(obj->getFullName() << " still touched after recompute");
|
| | if (idx >= topoSortedObjects.size()) {
|
| |
|
| | idx = i;
|
| | }
|
| | obj->setStatus(ObjectStatus::Recompute2, true);
|
| | }
|
| | }
|
| | }
|
| | }
|
| | }
|
| | catch (Base::Exception& e) {
|
| | e.reportException();
|
| | }
|
| |
|
| | FC_TIME_LOG(t2, "Recompute");
|
| |
|
| | for (auto obj : topoSortedObjects) {
|
| | if (!obj->isAttachedToDocument()) {
|
| | continue;
|
| | }
|
| | obj->setStatus(ObjectStatus::PendingRecompute, false);
|
| | obj->setStatus(ObjectStatus::Recompute2, false);
|
| | }
|
| |
|
| | signalRecomputed(*this, topoSortedObjects);
|
| |
|
| | FC_TIME_LOG(t, "Recompute total");
|
| |
|
| | if (!d->_RecomputeLog.empty()) {
|
| | if (!testStatus(Status::IgnoreErrorOnRecompute)) {
|
| | for (auto it : topoSortedObjects) {
|
| | if (it->isError()) {
|
| | const char* text = getErrorDescription(it);
|
| | if (text) {
|
| | Base::Console().error("%s: %s\n", it->Label.getValue(), text);
|
| | }
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| | for (auto doc : GetApplication().getDocuments()) {
|
| | decltype(doc->d->pendingRemove) objects;
|
| | objects.swap(doc->d->pendingRemove);
|
| | for (auto& o : objects) {
|
| | try {
|
| | if (auto obj = o.getObject()) {
|
| | obj->getDocument()->removeObject(obj->getNameInDocument());
|
| | }
|
| | }
|
| | catch (Base::Exception& e) {
|
| | e.reportException();
|
| | FC_ERR("error when removing object " << o.getDocumentName() << '#'
|
| | << o.getObjectName());
|
| | }
|
| | }
|
| | }
|
| | return objectCount;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | std::vector<DocumentObject*>
|
| | DocumentP::partialTopologicalSort(const std::vector<DocumentObject*>& objects)
|
| | {
|
| | vector<DocumentObject*> ret;
|
| | ret.reserve(objects.size());
|
| |
|
| | map<DocumentObject*, std::pair<int, int>> countMap;
|
| |
|
| | for (auto objectIt : objects) {
|
| |
|
| | auto in = objectIt->getInList();
|
| | std::sort(in.begin(), in.end());
|
| | in.erase(std::unique(in.begin(), in.end()), in.end());
|
| |
|
| |
|
| | auto out = objectIt->getOutList();
|
| | std::sort(out.begin(), out.end());
|
| | out.erase(std::unique(out.begin(), out.end()), out.end());
|
| |
|
| | countMap[objectIt] = std::make_pair(in.size(), out.size());
|
| | }
|
| |
|
| | std::list<DocumentObject*> degIn;
|
| | std::list<DocumentObject*> degOut;
|
| |
|
| | bool removeVertex = true;
|
| | while (removeVertex) {
|
| | removeVertex = false;
|
| |
|
| |
|
| | auto degInIt = find_if(countMap.begin(),
|
| | countMap.end(),
|
| | [](pair<DocumentObject*, pair<int, int>> vertex) -> bool {
|
| | return vertex.second.first == 0;
|
| | });
|
| |
|
| | if (degInIt != countMap.end()) {
|
| | removeVertex = true;
|
| | degIn.push_back(degInIt->first);
|
| | degInIt->second.first = degInIt->second.first - 1;
|
| |
|
| |
|
| | auto out = degInIt->first->getOutList();
|
| | std::sort(out.begin(), out.end());
|
| | out.erase(std::unique(out.begin(), out.end()), out.end());
|
| |
|
| | for (auto outListIt : out) {
|
| | auto outListMapIt = countMap.find(outListIt);
|
| | if (outListMapIt != countMap.end()) {
|
| | outListMapIt->second.first = outListMapIt->second.first - 1;
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| | for (auto& [obj, pair] : countMap) {
|
| | if (pair.first < 0) {
|
| | pair.second = -1;
|
| | }
|
| | }
|
| |
|
| | removeVertex = degIn.size() != objects.size();
|
| | while (removeVertex) {
|
| | removeVertex = false;
|
| |
|
| | auto degOutIt = find_if(countMap.begin(),
|
| | countMap.end(),
|
| | [](pair<DocumentObject*, pair<int, int>> vertex) -> bool {
|
| | return vertex.second.second == 0;
|
| | });
|
| |
|
| | if (degOutIt != countMap.end()) {
|
| | removeVertex = true;
|
| | degOut.push_front(degOutIt->first);
|
| | degOutIt->second.second = degOutIt->second.second - 1;
|
| |
|
| |
|
| | auto in = degOutIt->first->getInList();
|
| | std::sort(in.begin(), in.end());
|
| | in.erase(std::unique(in.begin(), in.end()), in.end());
|
| |
|
| | for (auto inListIt : in) {
|
| | auto inListMapIt = countMap.find(inListIt);
|
| | if (inListMapIt != countMap.end()) {
|
| | inListMapIt->second.second = inListMapIt->second.second - 1;
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | for (auto countIt : countMap) {
|
| | if (countIt.second.first > 0 && countIt.second.second > 0) {
|
| | degIn.push_back(countIt.first);
|
| | }
|
| | }
|
| |
|
| | ret.insert(ret.end(), degIn.begin(), degIn.end());
|
| | ret.insert(ret.end(), degOut.begin(), degOut.end());
|
| |
|
| | return ret;
|
| | }
|
| |
|
| | std::vector<DocumentObject*>
|
| | DocumentP::topologicalSort(const std::vector<DocumentObject*>& objects) const
|
| | {
|
| |
|
| |
|
| | vector<DocumentObject*> ret;
|
| | ret.reserve(objects.size());
|
| | map<DocumentObject*, int> countMap;
|
| |
|
| | for (auto objectIt : objects) {
|
| |
|
| |
|
| | if (!objectIt->isAttachedToDocument()) {
|
| | continue;
|
| | }
|
| |
|
| | auto in = objectIt->getInList();
|
| | std::sort(in.begin(), in.end());
|
| | in.erase(std::unique(in.begin(), in.end()), in.end());
|
| |
|
| | countMap[objectIt] = in.size();
|
| | }
|
| |
|
| | auto rootObjeIt = find_if(countMap.begin(),
|
| | countMap.end(),
|
| | [](pair<DocumentObject*, int> count) -> bool {
|
| | return count.second == 0;
|
| | });
|
| |
|
| | if (rootObjeIt == countMap.end()) {
|
| | cerr << "Document::topologicalSort: cyclic dependency detected (no root object)" << '\n';
|
| | return ret;
|
| | }
|
| |
|
| | while (rootObjeIt != countMap.end()) {
|
| | rootObjeIt->second = rootObjeIt->second - 1;
|
| |
|
| |
|
| | auto out = rootObjeIt->first->getOutList();
|
| | std::sort(out.begin(), out.end());
|
| | out.erase(std::unique(out.begin(), out.end()), out.end());
|
| |
|
| | for (auto outListIt : out) {
|
| | auto outListMapIt = countMap.find(outListIt);
|
| | if (outListMapIt != countMap.end()) {
|
| | outListMapIt->second = outListMapIt->second - 1;
|
| | }
|
| | }
|
| | ret.push_back(rootObjeIt->first);
|
| |
|
| | rootObjeIt = find_if(countMap.begin(),
|
| | countMap.end(),
|
| | [](pair<DocumentObject*, int> count) -> bool {
|
| | return count.second == 0;
|
| | });
|
| | }
|
| |
|
| | return ret;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::topologicalSort() const
|
| | {
|
| | return d->topologicalSort(d->objectArray);
|
| | }
|
| |
|
| | const char* Document::getErrorDescription(const DocumentObject* Obj) const
|
| | {
|
| | return d->findRecomputeLog(Obj);
|
| | }
|
| |
|
| |
|
| | int Document::_recomputeFeature(DocumentObject* Feat)
|
| | {
|
| | FC_LOG("Recomputing " << Feat->getFullName());
|
| |
|
| | DocumentObjectExecReturn* returnCode = nullptr;
|
| | try {
|
| | returnCode = Feat->ExpressionEngine.execute(PropertyExpressionEngine::ExecuteNonOutput);
|
| | if (returnCode == DocumentObject::StdReturn) {
|
| | returnCode = Feat->recompute();
|
| | if (returnCode == DocumentObject::StdReturn) {
|
| | returnCode =
|
| | Feat->ExpressionEngine.execute(PropertyExpressionEngine::ExecuteOutput);
|
| | }
|
| | }
|
| | }
|
| | catch (Base::AbortException& e) {
|
| | e.reportException();
|
| | FC_LOG("Failed to recompute " << Feat->getFullName() << ": " << e.what());
|
| | d->addRecomputeLog("User abort", Feat);
|
| | return -1;
|
| | }
|
| | catch (const Base::MemoryException& e) {
|
| | FC_ERR("Memory exception in " << Feat->getFullName() << " thrown: " << e.what());
|
| | d->addRecomputeLog("Out of memory exception", Feat);
|
| | return 1;
|
| | }
|
| | catch (Base::Exception& e) {
|
| | e.reportException();
|
| | FC_LOG("Failed to recompute " << Feat->getFullName() << ": " << e.what());
|
| | d->addRecomputeLog(e.what(), Feat);
|
| | return 1;
|
| | }
|
| | catch (std::exception& e) {
|
| | FC_ERR("Exception in " << Feat->getFullName() << " thrown: " << e.what());
|
| | d->addRecomputeLog(e.what(), Feat);
|
| | return 1;
|
| | }
|
| | #ifndef FC_DEBUG
|
| | catch (...) {
|
| | FC_ERR("Unknown exception in " << Feat->getFullName() << " thrown");
|
| | d->addRecomputeLog("Unknown exception!", Feat);
|
| | return 1;
|
| | }
|
| | #endif
|
| |
|
| | if (returnCode == DocumentObject::StdReturn) {
|
| | Feat->resetError();
|
| | }
|
| | else {
|
| | returnCode->Which = Feat;
|
| | d->addRecomputeLog(returnCode);
|
| | FC_LOG("Failed to recompute " << Feat->getFullName() << ": " << returnCode->Why);
|
| | return 1;
|
| | }
|
| | return 0;
|
| | }
|
| |
|
| | bool Document::recomputeFeature(DocumentObject* feature, bool recursive)
|
| | {
|
| |
|
| | d->clearRecomputeLog(feature);
|
| |
|
| |
|
| | if (!feature->isAttachedToDocument()) {
|
| | return false;
|
| | }
|
| |
|
| | if (recursive) {
|
| | bool hasError = false;
|
| | recompute({feature}, true, &hasError);
|
| | return !hasError;
|
| | }
|
| | _recomputeFeature(feature);
|
| | signalRecomputedObject(*feature);
|
| | return feature->isValid();
|
| | }
|
| |
|
| | DocumentObject* Document::addObject(const char* sType,
|
| | const char* pObjectName,
|
| | const bool isNew,
|
| | const char* viewType,
|
| | const bool isPartial)
|
| | {
|
| | const Base::Type type =
|
| | Base::Type::getTypeIfDerivedFrom(sType, DocumentObject::getClassTypeId(), true);
|
| | if (type.isBad()) {
|
| | std::stringstream str;
|
| | str << "Document::addObject: '" << sType << "' is not a document object type";
|
| | throw Base::TypeError(str.str());
|
| | }
|
| |
|
| | void* typeInstance = type.createInstance();
|
| | if (!typeInstance) {
|
| | return nullptr;
|
| | }
|
| |
|
| | auto* pcObject = static_cast<DocumentObject*>(typeInstance);
|
| | pcObject->setDocument(this);
|
| |
|
| | _addObject(pcObject,
|
| | pObjectName,
|
| | AddObjectOption::SetNewStatus
|
| | | (isPartial ? AddObjectOption::SetPartialStatus : AddObjectOption::UnsetPartialStatus)
|
| | | (isNew ? AddObjectOption::DoSetup : AddObjectOption::None)
|
| | | AddObjectOption::ActivateObject,
|
| | viewType);
|
| |
|
| |
|
| | return pcObject;
|
| | }
|
| |
|
| | std::vector<DocumentObject*>
|
| | Document::addObjects(const char* sType, const std::vector<std::string>& objectNames, bool isNew)
|
| | {
|
| | Base::Type type =
|
| | Base::Type::getTypeIfDerivedFrom(sType, DocumentObject::getClassTypeId(), true);
|
| | if (type.isBad()) {
|
| | std::stringstream str;
|
| | str << "'" << sType << "' is not a document object type";
|
| | throw Base::TypeError(str.str());
|
| | }
|
| |
|
| | std::vector<DocumentObject*> objects;
|
| | objects.resize(objectNames.size());
|
| | std::generate(objects.begin(), objects.end(), [&] {
|
| | return static_cast<DocumentObject*>(type.createInstance());
|
| | });
|
| |
|
| | if (!objects.empty() && !objects[0]) {
|
| | objects.clear();
|
| | return objects;
|
| | }
|
| |
|
| | for (auto it = objects.begin(); it != objects.end(); ++it) {
|
| | size_t index = std::distance(objects.begin(), it);
|
| | DocumentObject* pcObject = *it;
|
| | pcObject->setDocument(this);
|
| |
|
| |
|
| | bool isLast = index == (objects.size() - 1);
|
| | _addObject(pcObject,
|
| | objectNames[index].c_str(),
|
| | AddObjectOption::SetNewStatus
|
| | | (isNew ? AddObjectOption::DoSetup : AddObjectOption::None)
|
| | | (isLast ? AddObjectOption::ActivateObject : AddObjectOption::None));
|
| | }
|
| |
|
| | return objects;
|
| | }
|
| |
|
| | void Document::addObject(DocumentObject* pcObject, const char* pObjectName)
|
| | {
|
| | if (pcObject->getDocument()) {
|
| | throw Base::RuntimeError("Document object is already added to a document");
|
| | }
|
| |
|
| | pcObject->setDocument(this);
|
| |
|
| | _addObject(pcObject, pObjectName, AddObjectOption::SetNewStatus | AddObjectOption::ActivateObject);
|
| | }
|
| |
|
| | void Document::_addObject(DocumentObject* pcObject, const char* pObjectName, AddObjectOptions options, const char* viewType)
|
| | {
|
| |
|
| | string ObjectName;
|
| | if (!Base::Tools::isNullOrEmpty(pObjectName)) {
|
| | ObjectName = getUniqueObjectName(pObjectName);
|
| | }
|
| | else {
|
| | ObjectName = getUniqueObjectName(pcObject->getTypeId().getName());
|
| | }
|
| |
|
| |
|
| | d->objectMap[ObjectName] = pcObject;
|
| | d->objectNameManager.addExactName(ObjectName);
|
| |
|
| |
|
| | pcObject->pcNameInDocument = &(d->objectMap.find(ObjectName)->first);
|
| |
|
| | registerLabel(pcObject->Label.getStrValue());
|
| |
|
| |
|
| | if (pcObject->_Id == 0) {
|
| | pcObject->_Id = ++d->lastObjectId;
|
| | }
|
| | d->objectIdMap[pcObject->_Id] = pcObject;
|
| | d->objectArray.push_back(pcObject);
|
| |
|
| |
|
| | if (!d->rollback) {
|
| |
|
| | _checkTransaction(nullptr, nullptr, __LINE__);
|
| | if (d->activeUndoTransaction) {
|
| | d->activeUndoTransaction->addObjectDel(pcObject);
|
| | }
|
| | }
|
| |
|
| |
|
| | if (options.testFlag(AddObjectOption::SetNewStatus) && !d->StatusBits.test(Restoring)) {
|
| | pcObject->Label.setValue(ObjectName);
|
| | }
|
| |
|
| |
|
| | if (!isPerformingTransaction() && options.testFlag(AddObjectOption::DoSetup)) {
|
| | pcObject->setupObject();
|
| | }
|
| |
|
| | if (options.testFlag(AddObjectOption::SetNewStatus)) {
|
| | pcObject->setStatus(ObjectStatus::New, true);
|
| | }
|
| | if (options.testFlag(AddObjectOption::SetPartialStatus) || options.testFlag(AddObjectOption::UnsetPartialStatus)) {
|
| | pcObject->setStatus(ObjectStatus::PartialObject, options.testFlag(AddObjectOption::SetPartialStatus));
|
| | }
|
| |
|
| | if (Base::Tools::isNullOrEmpty(viewType)) {
|
| | viewType = pcObject->getViewProviderNameOverride();
|
| | }
|
| | pcObject->_pcViewProviderName = viewType ? viewType : "";
|
| |
|
| | signalNewObject(*pcObject);
|
| |
|
| |
|
| | if (!d->rollback && d->activeUndoTransaction) {
|
| | signalTransactionAppend(*pcObject, d->activeUndoTransaction);
|
| | }
|
| |
|
| | if (options.testFlag(AddObjectOption::ActivateObject)) {
|
| | d->activeObject = pcObject;
|
| | signalActivatedObject(*pcObject);
|
| | }
|
| | }
|
| |
|
| | bool Document::containsObject(const DocumentObject* pcObject) const
|
| | {
|
| |
|
| |
|
| |
|
| | auto found = d->objectIdMap.find(pcObject->getID());
|
| | return found != d->objectIdMap.end() && found->second == pcObject;
|
| | }
|
| |
|
| |
|
| | void Document::removeObject(const char* sName)
|
| | {
|
| | auto pos = d->objectMap.find(sName);
|
| | if (pos == d->objectMap.end()){
|
| | FC_MSG("Object " << sName << " already deleted in document " << getName());
|
| | return;
|
| | }
|
| |
|
| | if (pos->second->testStatus(ObjectStatus::PendingRecompute)) {
|
| |
|
| | FC_MSG("pending remove of " << sName << " after recomputing document " << getName());
|
| | d->pendingRemove.emplace_back(pos->second);
|
| | return;
|
| | }
|
| |
|
| | _removeObject(pos->second, RemoveObjectOption::MayRemoveWhileRecomputing | RemoveObjectOption::MayDestroyOutOfTransaction);
|
| | }
|
| | void Document::_removeObject(DocumentObject* pcObject, RemoveObjectOptions options)
|
| | {
|
| | if (!options.testFlag(RemoveObjectOption::MayRemoveWhileRecomputing) && testStatus(Document::Recomputing)) {
|
| | FC_ERR("Cannot delete " << pcObject->getFullName() << " while recomputing");
|
| | return;
|
| | }
|
| |
|
| | TransactionLocker tlock;
|
| |
|
| | _checkTransaction(pcObject, nullptr, __LINE__);
|
| |
|
| | auto pos = d->objectMap.find(pcObject->getNameInDocument());
|
| | if (pos == d->objectMap.end()) {
|
| | FC_ERR("Internal error, could not find " << pcObject->getFullName() << " to remove");
|
| | }
|
| |
|
| | if (options.testFlag(RemoveObjectOption::PreserveChildrenVisibility)
|
| | && !d->rollback && d->activeUndoTransaction && pcObject->hasChildElement()) {
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | for (auto& sub : pcObject->getSubObjects()) {
|
| | if (sub.empty()) {
|
| | continue;
|
| | }
|
| | if (sub[sub.size() - 1] != '.') {
|
| | sub += '.';
|
| | }
|
| | auto sobj = pcObject->getSubObject(sub.c_str());
|
| | if (sobj && sobj->getDocument() == this && !sobj->Visibility.getValue()) {
|
| | d->activeUndoTransaction->addObjectChange(sobj, &sobj->Visibility);
|
| | }
|
| | }
|
| | }
|
| |
|
| | if (d->activeObject == pcObject) {
|
| | d->activeObject = nullptr;
|
| | }
|
| |
|
| |
|
| | pcObject->setStatus(ObjectStatus::Remove, true);
|
| | if (!d->undoing && !d->rollback) {
|
| | pcObject->unsetupObject();
|
| | }
|
| | signalDeletedObject(*pcObject);
|
| | signalTransactionRemove(*pcObject, d->rollback ? nullptr : d->activeUndoTransaction);
|
| | breakDependency(pcObject, true);
|
| |
|
| |
|
| |
|
| | if (Tip.getValue() == pcObject) {
|
| | Tip.setValue(nullptr);
|
| | TipName.setValue("");
|
| | }
|
| |
|
| |
|
| | pcObject->setStatus(ObjectStatus::Remove, false);
|
| | d->objectIdMap.erase(pcObject->_Id);
|
| | d->objectNameManager.removeExactName(pos->first);
|
| | unregisterLabel(pcObject->Label.getStrValue());
|
| |
|
| |
|
| | if (!d->rollback && d->activeUndoTransaction) {
|
| | d->activeUndoTransaction->addObjectNew(pcObject);
|
| | }
|
| |
|
| | std::unique_ptr<DocumentObject> tobedestroyed;
|
| | if ((options.testFlag(RemoveObjectOption::MayDestroyOutOfTransaction) && !d->rollback && !d->activeUndoTransaction)
|
| | || (options.testFlag(RemoveObjectOption::DestroyOnRollback) && d->rollback)) {
|
| |
|
| | std::unique_ptr<DocumentObject> delobj(pos->second);
|
| | tobedestroyed.swap(delobj);
|
| | tobedestroyed->setStatus(ObjectStatus::Destroy, true);
|
| | }
|
| |
|
| | for (auto it = d->objectArray.begin();
|
| | it != d->objectArray.end();
|
| | ++it) {
|
| | if (*it == pcObject) {
|
| | d->objectArray.erase(it);
|
| | break;
|
| | }
|
| | }
|
| |
|
| |
|
| | if (tobedestroyed) {
|
| | tobedestroyed->pcNameInDocument = nullptr;
|
| | }
|
| |
|
| |
|
| |
|
| | d->objectMap.erase(pos);
|
| | }
|
| |
|
| | void Document::breakDependency(DocumentObject* pcObject, const bool clear)
|
| | {
|
| |
|
| | PropertyLinkBase::breakLinks(pcObject, d->objectArray, clear);
|
| | }
|
| |
|
| | std::vector<DocumentObject*>
|
| | Document::copyObject(const std::vector<DocumentObject*>& objs, bool recursive, bool returnAll)
|
| | {
|
| | std::vector<DocumentObject*> deps;
|
| | if (!recursive) {
|
| | deps = objs;
|
| | }
|
| | else {
|
| | deps = getDependencyList(objs, DepNoXLinked | DepSort);
|
| | }
|
| |
|
| | if (!testStatus(TempDoc) && !isSaved() && PropertyXLink::hasXLink(deps)) {
|
| | throw Base::RuntimeError(
|
| | "Document must be saved at least once before link to external objects");
|
| | }
|
| |
|
| | MergeDocuments md(this);
|
| |
|
| | md.setVerbose(recursive);
|
| |
|
| | unsigned int memsize = 1000;
|
| | for (auto it : deps) {
|
| | memsize += it->getMemSize();
|
| | }
|
| |
|
| |
|
| | bool use_buffer = (memsize < 0xA00000);
|
| | QByteArray res;
|
| | try {
|
| | res.reserve(memsize);
|
| | }
|
| | catch (const Base::MemoryException&) {
|
| | use_buffer = false;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> imported;
|
| | if (use_buffer) {
|
| | Base::ByteArrayOStreambuf obuf(res);
|
| | std::ostream ostr(&obuf);
|
| | exportObjects(deps, ostr);
|
| |
|
| | Base::ByteArrayIStreambuf ibuf(res);
|
| | std::istream istr(nullptr);
|
| | istr.rdbuf(&ibuf);
|
| | imported = md.importObjects(istr);
|
| | }
|
| | else {
|
| | static Base::FileInfo fi(Application::getTempFileName());
|
| | Base::ofstream ostr(fi, std::ios::out | std::ios::binary);
|
| | exportObjects(deps, ostr);
|
| | ostr.close();
|
| |
|
| | Base::ifstream istr(fi, std::ios::in | std::ios::binary);
|
| | imported = md.importObjects(istr);
|
| | }
|
| |
|
| | if (returnAll || imported.size() != deps.size()) {
|
| | return imported;
|
| | }
|
| |
|
| | std::unordered_map<DocumentObject*, size_t> indices;
|
| | size_t i = 0;
|
| | for (auto o : deps) {
|
| | indices[o] = i++;
|
| | }
|
| | std::vector<DocumentObject*> result;
|
| | result.reserve(objs.size());
|
| | for (auto o : objs) {
|
| | result.push_back(imported[indices[o]]);
|
| | }
|
| | return result;
|
| | }
|
| |
|
| | std::vector<DocumentObject*>
|
| | Document::importLinks(const std::vector<DocumentObject*>& objs)
|
| | {
|
| | std::set<DocumentObject*> links;
|
| | getLinksTo(links, nullptr, GetLinkExternal, 0, objs);
|
| |
|
| | std::vector<DocumentObject*> vecObjs;
|
| | vecObjs.insert(vecObjs.end(), links.begin(), links.end());
|
| | std::vector<DocumentObject*> depObjs = getDependencyList(vecObjs);
|
| | if (depObjs.empty()) {
|
| | FC_ERR("nothing to import");
|
| | return depObjs;
|
| | }
|
| |
|
| | for (auto it = depObjs.begin(); it != depObjs.end();) {
|
| | auto obj = *it;
|
| | if (obj->getDocument() == this) {
|
| | it = depObjs.erase(it);
|
| | continue;
|
| | }
|
| | ++it;
|
| | if (obj->testStatus(PartialObject)) {
|
| | throw Base::RuntimeError(
|
| | "Cannot import partial loaded object. Please reload the current document");
|
| | }
|
| | }
|
| |
|
| | Base::FileInfo fi(Application::getTempFileName());
|
| | {
|
| |
|
| | Base::ofstream str(fi, std::ios::out | std::ios::binary);
|
| | MergeDocuments mimeView(this);
|
| | exportObjects(depObjs, str);
|
| | str.close();
|
| | }
|
| | Base::ifstream str(fi, std::ios::in | std::ios::binary);
|
| | MergeDocuments mimeView(this);
|
| | depObjs = mimeView.importObjects(str);
|
| | str.close();
|
| | fi.deleteFile();
|
| |
|
| | const auto& nameMap = mimeView.getNameMap();
|
| |
|
| |
|
| | std::map<Property*, std::unique_ptr<Property>> propMap;
|
| | std::vector<Property*> propList;
|
| | for (auto obj : links) {
|
| | propList.clear();
|
| | obj->getPropertyList(propList);
|
| | for (auto prop : propList) {
|
| | auto linkProp = freecad_cast<PropertyLinkBase*>(prop);
|
| | if (linkProp && !prop->testStatus(Property::Immutable) && !obj->isReadOnly(prop)) {
|
| | auto copy = linkProp->CopyOnImportExternal(nameMap);
|
| | if (copy) {
|
| | propMap[linkProp].reset(copy);
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | for (auto& v : propMap) {
|
| | v.first->Paste(*v.second);
|
| | }
|
| |
|
| | return depObjs;
|
| | }
|
| |
|
| | DocumentObject* Document::moveObject(DocumentObject* obj, const bool recursive)
|
| | {
|
| | if (!obj) {
|
| | return nullptr;
|
| | }
|
| | Document* that = obj->getDocument();
|
| | if (that == this) {
|
| | return nullptr;
|
| | }
|
| |
|
| |
|
| |
|
| | if (!recursive && (d->iUndoMode == 0) && (that->d->iUndoMode == 0) && !that->d->rollback) {
|
| |
|
| | that->breakDependency(obj, false);
|
| | const std::string objname = getUniqueObjectName(obj->getNameInDocument());
|
| | that->_removeObject(obj);
|
| | this->_addObject(obj, objname.c_str());
|
| | obj->setDocument(this);
|
| | return obj;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> deps;
|
| | if (recursive) {
|
| | deps = getDependencyList({obj}, DepNoXLinked | DepSort);
|
| | }
|
| | else {
|
| | deps.push_back(obj);
|
| | }
|
| |
|
| | const auto objs = copyObject(deps, false);
|
| | if (objs.empty()) {
|
| | return nullptr;
|
| | }
|
| |
|
| |
|
| | std::vector<int> ids;
|
| | ids.reserve(deps.size());
|
| | for (const auto o : deps) {
|
| | ids.push_back(static_cast<int>(o->getID()));
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| | for (auto iter = ids.rbegin(); iter != ids.rend(); ++iter) {
|
| | const auto o = that->getObjectByID(*iter);
|
| | if (!o) {
|
| | continue;
|
| | }
|
| | if (iter == ids.rbegin() || o->getInList().empty()) {
|
| | that->removeObject(o->getNameInDocument());
|
| | }
|
| | }
|
| | return objs.back();
|
| | }
|
| |
|
| | DocumentObject* Document::getActiveObject() const
|
| | {
|
| | return d->activeObject;
|
| | }
|
| |
|
| | DocumentObject* Document::getObject(const char* Name) const
|
| | {
|
| | const auto pos = d->objectMap.find(Name);
|
| |
|
| | return pos != d->objectMap.end() ?pos->second:nullptr;
|
| | }
|
| |
|
| | DocumentObject* Document::getObjectByID(const long id) const
|
| | {
|
| | const auto it = d->objectIdMap.find(id);
|
| |
|
| | return it != d->objectIdMap.end() ?it->second:nullptr;
|
| | }
|
| |
|
| |
|
| |
|
| | bool Document::isIn(const DocumentObject* pFeat) const
|
| | {
|
| | for (const auto& [key, object] : d->objectMap) {
|
| | if (object == pFeat) {
|
| | return true;
|
| | }
|
| | }
|
| |
|
| | return false;
|
| | }
|
| |
|
| | const char* Document::getObjectName(const DocumentObject* pFeat) const
|
| | {
|
| | for (const auto& [key, object] : d->objectMap) {
|
| | if (object == pFeat) {
|
| | return key.c_str();
|
| | }
|
| | }
|
| |
|
| | return nullptr;
|
| | }
|
| |
|
| | std::string Document::getUniqueObjectName(const char* proposedName) const
|
| | {
|
| | if (!proposedName || *proposedName == '\0') {
|
| | return {};
|
| | }
|
| | std::string cleanName = Base::Tools::getIdentifier(proposedName);
|
| |
|
| | if (!d->objectNameManager.containsName(cleanName)) {
|
| |
|
| | return cleanName;
|
| | }
|
| | return d->objectNameManager.makeUniqueName(cleanName, 3);
|
| | }
|
| |
|
| | bool
|
| | Document::haveSameBaseName(const std::string& name, const std::string& label)
|
| | {
|
| |
|
| |
|
| |
|
| | return d->objectNameManager.haveSameBaseName(name, label);
|
| | }
|
| |
|
| | std::string Document::getStandardObjectLabel(const char* modelName, int digitCount) const
|
| | {
|
| | return d->objectLabelManager.makeUniqueName(modelName, digitCount);
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::getDependingObjects() const
|
| | {
|
| | return getDependencyList(d->objectArray);
|
| | }
|
| |
|
| | const std::vector<DocumentObject*>& Document::getObjects() const
|
| | {
|
| | return d->objectArray;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::getObjectsOfType(const Base::Type& typeId) const
|
| | {
|
| | std::vector<DocumentObject*> Objects;
|
| | for (auto it : d->objectArray) {
|
| | if (it->isDerivedFrom(typeId)) {
|
| | Objects.push_back(it);
|
| | }
|
| | }
|
| | return Objects;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::getObjectsOfType(const std::vector<Base::Type>& types) const
|
| | {
|
| | std::vector<DocumentObject*> Objects;
|
| | for (auto it : d->objectArray) {
|
| | for (auto& typeId : types) {
|
| | if (it->isDerivedFrom(typeId)) {
|
| | Objects.push_back(it);
|
| | break;
|
| | }
|
| | }
|
| | }
|
| | return Objects;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::getObjectsWithExtension(const Base::Type& typeId,
|
| | const bool derived) const
|
| | {
|
| |
|
| | std::vector<DocumentObject*> Objects;
|
| | for (auto it : d->objectArray) {
|
| | if (it->hasExtension(typeId, derived)) {
|
| | Objects.push_back(it);
|
| | }
|
| | }
|
| | return Objects;
|
| | }
|
| |
|
| |
|
| | std::vector<DocumentObject*>
|
| | Document::findObjects(const Base::Type& typeId, const char* objname, const char* label) const
|
| | {
|
| | boost::cmatch what;
|
| | boost::regex rx_name;
|
| | boost::regex rx_label;
|
| |
|
| | if (objname) {
|
| | rx_name.set_expression(objname);
|
| | }
|
| |
|
| | if (label) {
|
| | rx_label.set_expression(label);
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Objects;
|
| | DocumentObject* found = nullptr;
|
| | for (const auto it : d->objectArray) {
|
| | if (it->isDerivedFrom(typeId)) {
|
| | found = it;
|
| |
|
| | if (!rx_name.empty() && !boost::regex_search(it->getNameInDocument(), what, rx_name)) {
|
| | found = nullptr;
|
| | }
|
| |
|
| | if (!rx_label.empty() && !boost::regex_search(it->Label.getValue(), what, rx_label)) {
|
| | found = nullptr;
|
| | }
|
| |
|
| | if (found) {
|
| | Objects.push_back(found);
|
| | }
|
| | }
|
| | }
|
| | return Objects;
|
| | }
|
| |
|
| | int Document::countObjectsOfType(const Base::Type& typeId) const
|
| | {
|
| | return std::count_if(d->objectMap.begin(), d->objectMap.end(), [&](const auto& it) {
|
| | return it.second->isDerivedFrom(typeId);
|
| | });
|
| | }
|
| |
|
| | int Document::countObjectsOfType(const char* typeName) const
|
| | {
|
| | const Base::Type type = Base::Type::fromName(typeName);
|
| | return type.isBad() ? 0 : countObjectsOfType(type);
|
| | }
|
| |
|
| | PyObject* Document::getPyObject()
|
| | {
|
| | return Py::new_reference_to(d->DocumentPythonObject);
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::getRootObjects() const
|
| | {
|
| | std::vector<DocumentObject*> ret;
|
| |
|
| | for (auto objectIt : d->objectArray) {
|
| | if (objectIt->getInList().empty()) {
|
| | ret.push_back(objectIt);
|
| | }
|
| | }
|
| |
|
| | return ret;
|
| | }
|
| |
|
| | std::vector<DocumentObject*> Document::getRootObjectsIgnoreLinks() const
|
| | {
|
| | std::vector<DocumentObject*> ret;
|
| |
|
| | for (const auto &objectIt : d->objectArray) {
|
| | auto list = objectIt->getInList();
|
| | bool noParents = list.empty();
|
| |
|
| | if (!noParents) {
|
| |
|
| |
|
| |
|
| |
|
| | noParents = std::all_of(list.cbegin(), list.cend(), [](DocumentObject* obj) {
|
| | return obj->isDerivedFrom<Link>();
|
| | });
|
| | }
|
| |
|
| | if (noParents) {
|
| | ret.push_back(objectIt);
|
| | }
|
| | }
|
| |
|
| | return ret;
|
| | }
|
| |
|
| | void DocumentP::findAllPathsAt(const std::vector<Node>& all_nodes,
|
| | const size_t id,
|
| | std::vector<Path>& all_paths,
|
| | Path tmp)
|
| | {
|
| | if (std::ranges::find(tmp, id) != tmp.end()) {
|
| | tmp.push_back(id);
|
| | all_paths.push_back(std::move(tmp));
|
| | return;
|
| | }
|
| |
|
| | tmp.push_back(id);
|
| | if (all_nodes[id].empty()) {
|
| | all_paths.push_back(std::move(tmp));
|
| | return;
|
| | }
|
| |
|
| | for (size_t i = 0; i < all_nodes[id].size(); i++) {
|
| | const Path& tmp2(tmp);
|
| | findAllPathsAt(all_nodes, all_nodes[id][i], all_paths, tmp2);
|
| | }
|
| | }
|
| |
|
| | std::vector<std::list<DocumentObject*>>
|
| | Document::getPathsByOutList(const DocumentObject* from, const DocumentObject* to) const
|
| | {
|
| | std::map<const DocumentObject*, size_t> indexMap;
|
| | for (size_t i = 0; i < d->objectArray.size(); ++i) {
|
| | indexMap[d->objectArray[i]] = i;
|
| | }
|
| |
|
| | std::vector<Node> all_nodes(d->objectArray.size());
|
| | for (size_t i = 0; i < d->objectArray.size(); ++i) {
|
| | const DocumentObject* obj = d->objectArray[i];
|
| | std::vector<DocumentObject*> outList = obj->getOutList();
|
| | for (const auto it : outList) {
|
| | all_nodes[i].push_back(indexMap[it]);
|
| | }
|
| | }
|
| |
|
| | std::vector<std::list<DocumentObject*>> array;
|
| | if (from == to) {
|
| | return array;
|
| | }
|
| |
|
| | size_t index_from = indexMap[from];
|
| | size_t index_to = indexMap[to];
|
| | std::vector<Path> all_paths;
|
| | DocumentP::findAllPathsAt(all_nodes, index_from, all_paths, Path());
|
| |
|
| | for (const Path& it : all_paths) {
|
| | auto jt = std::ranges::find(it, index_to);
|
| | if (jt != it.end()) {
|
| | array.push_back({});
|
| | auto& path = array.back();
|
| | for (auto kt = it.begin(); kt != jt; ++kt) {
|
| | path.push_back(d->objectArray[*kt]);
|
| | }
|
| |
|
| | path.push_back(d->objectArray[*jt]);
|
| | }
|
| | }
|
| |
|
| |
|
| | std::sort(array.begin(), array.end());
|
| | array.erase(std::unique(array.begin(), array.end()), array.end());
|
| |
|
| | return array;
|
| | }
|
| |
|
| | bool Document::mustExecute() const
|
| | {
|
| | if (PropertyXLink::hasXLink(this)) {
|
| | bool touched = false;
|
| | buildDependencyList(d->objectArray, 0, nullptr, nullptr, nullptr, &touched);
|
| | return touched;
|
| | }
|
| |
|
| | for (const auto It : d->objectArray) {
|
| | if (It->isTouched() || It->mustExecute() == 1) {
|
| | return true;
|
| | }
|
| | }
|
| | return false;
|
| | }
|
| |
|