App: API changes for document recompute/save/restore/import/export

This patch adds support of recomputation with external linked object,
as well as external document auto loading and partial loading.

Application:

* Modified new/openDocument()/signalNewDocument to choose whether to
  signal GUI for creating a view for the document. This makes it possible
  to suppress view creation when opening external documents.

* New API openDocuments() which does the actual job of loading the
  document together with any external dependencies. There are afew
  extra arguments to allow setting FileName property differently from
  the actual file path, which are required when auto loading
  dependencies during document recovery (with future patch to
  Gui::DocumentRecovery)

* openDocumentPrivate() is an internal helper for opening individual
  document.

* New signalStart/FinishOpenDocument to be signaled before and after
  opening a document. There may be multiple depending documents actually
  opened in between these two signals.

* New signalBeforeRecomputeDocument signaled before recompute a
  document.

* New API addPendingDocument() for use by external capable link
  properties' to queue up external documents.

* isRestoring/isClosingAll(), for convenience status reporting.

Document:

* signalFinishImport/RestoreObjects, new signal triggered after imported
  or restored all input objects

* signalBeforeRecompute, signaled before start recomputing this document

* Modified signalRecomputed with additional recomputed objects, this is
  to make it more efficient for Gui::TreeWidget to check recomputation
  result.

* signalSkipRecompute, signal to inform which objects are skipped
  during recomputation because of their owner document SkipRecompute
  setting.

* restore/save/read/writeObjects() modified to suport partial
  loading. See [here](https://git.io/fj6PY) for more information.

* afterRestore(), internal function called to finish restore. The
  function is separated from restore() because there is quite a few
  critical steps needed to fully restore a document with external
  linking. See [here](https://git.io/fj6P4) for more information.

* DocumentP::_RecomputeLog is modified to store more accurate object
  recomputation error, including those happened during restore/import.

* isExporting(), new API for checking if an object is exporting.
  External linking properties will use this function to decide how to
  export.

* copyObject(), modified to support external linking objects, and
  accepts multiple input objects.

* moveObject(), modified to support arbitary object moves. The original
  implementation may cause crash if undo/redo is enabled. Furthermore,
  because the original information fakes the object's deletion to break
  its dependency, it does not work for objects that may auto delete their
  children when being deleted. The new implementation copy the object,
  and than paste it to the other document. It then deletes the input
  objects from the original document. In case of recursive move, it only
  deletes the depending object if it has an empty in list.

* importLinks(), new API to import any external object linked by the
  input objects into this document. It will auto correct all link
  references after importing.

* getDependencyList/_rebuildDependencyList(), these two APIs are unified
  and implemented by an internal function _buildDependencyList() with a
  new algorithm to handle external objects. The returned dependency list
  will now include objects from external documents. In case of cyclic
  dependencies, getDpendencyList() will report the actual objects
  involved in dependency loops.

* mustExecute(), new API to check if there are any object requires
  recomputation. This function will call _buildDependencyList() and
  check for external objects as well.

* addRecomputeObject(), new API for marking changes during document
  restore. It only marks the object but does not actually recompute
  them for performance reason. One use case is for geo feature to
  request for recomputation to generate geometry topological names.

* recompute(), support partial, external, and inverse dependency
  recomputation. Improve error handling during recomputation.
  See [here](https://git.io/fj6PO) for more information.

* _recomputeFeature(), suppoert user abort.

* getDependentDocuments/getInList(), new API to obtain an optional
  dependency sorted list of depending documents.

DocumentObject:

* Add various ObjectStatus flags

* isExporting/getExportName(), return a safe name for exporting, in the
  form of <ObjName>@<DocName>, which is guarrenteed to be unique.
  Various link property will save linked object using this name if the
  the linked object is exported together with the owner object, see
  [PropertyLinkBase::restoreLabelReference()](https://git.io/fj6XO)
  for more information.

* recomputeFeature(), add option to recompute this object together with
  all its dependent objects.

* canLoadPartial(), new API for [partial document loading](https://git.io/fj6PY).

MergeDocuments:

* Move object name mapping logic to various link properties. See

Base::Sequencer:

* Add new API checkAbort() for checking user abort.
This commit is contained in:
Zheng, Lei
2019-07-05 08:39:54 +08:00
committed by wmayer
parent b19647862b
commit 94c228973d
16 changed files with 1866 additions and 503 deletions

View File

@@ -221,8 +221,9 @@ init_freecad_module(void)
#endif
Application::Application(std::map<std::string,std::string> &mConfig)
: _mConfig(mConfig), _pActiveDoc(0), _objCount(-1)
, _activeTransactionID(0), _activeTransactionGuard(0), _activeTransactionTmpName(false)
: _mConfig(mConfig), _pActiveDoc(0), _isRestoring(false),_allowPartial(false)
, _isClosingAll(false), _objCount(-1), _activeTransactionID(0)
, _activeTransactionGuard(0), _activeTransactionTmpName(false)
{
//_hApp = new ApplicationOCC;
mpcPramManager["System parameter"] = _pcSysParamMngr;
@@ -355,6 +356,11 @@ Application::~Application()
/// get called by the document when the name is changing
void Application::renameDocument(const char *OldName, const char *NewName)
{
#if 1
(void)OldName;
(void)NewName;
throw Base::RuntimeError("Renaming document internal name is no longer allowed!");
#else
std::map<std::string,Document*>::iterator pos;
pos = DocMap.find(OldName);
@@ -368,9 +374,10 @@ void Application::renameDocument(const char *OldName, const char *NewName)
else {
throw Base::RuntimeError("Application::renameDocument(): no document with this name to rename!");
}
#endif
}
Document* Application::newDocument(const char * Name, const char * UserName)
Document* Application::newDocument(const char * Name, const char * UserName, bool createView)
{
// get a valid name anyway!
if (!Name || Name[0] == '\0')
@@ -415,6 +422,7 @@ Document* Application::newDocument(const char * Name, const char * UserName)
_pActiveDoc->signalRedo.connect(boost::bind(&App::Application::slotRedoDocument, this, _1));
_pActiveDoc->signalRecomputedObject.connect(boost::bind(&App::Application::slotRecomputedObject, this, _1));
_pActiveDoc->signalRecomputed.connect(boost::bind(&App::Application::slotRecomputed, this, _1));
_pActiveDoc->signalBeforeRecompute.connect(boost::bind(&App::Application::slotBeforeRecompute, this, _1));
_pActiveDoc->signalOpenTransaction.connect(boost::bind(&App::Application::slotOpenTransaction, this, _1, _2));
_pActiveDoc->signalCommitTransaction.connect(boost::bind(&App::Application::slotCommitTransaction, this, _1));
_pActiveDoc->signalAbortTransaction.connect(boost::bind(&App::Application::slotAbortTransaction, this, _1));
@@ -430,7 +438,7 @@ Document* Application::newDocument(const char * Name, const char * UserName)
Py::Module("FreeCAD").setAttr(std::string("ActiveDocument"),active);
}
signalNewDocument(*_pActiveDoc);
signalNewDocument(*_pActiveDoc,createView);
// set the UserName after notifying all observers
_pActiveDoc->Label.setValue(userName);
@@ -466,6 +474,7 @@ bool Application::closeDocument(const char* name)
void Application::closeAllDocuments(void)
{
Base::FlagToggler<bool> flag(_isClosingAll);
std::map<std::string,Document*>::iterator pos;
while((pos = DocMap.begin()) != DocMap.end())
closeDocument(pos->first.c_str());
@@ -524,7 +533,185 @@ std::string Application::getUniqueDocumentName(const char *Name) const
}
}
Document* Application::openDocument(const char * FileName)
int Application::addPendingDocument(const char *FileName, const char *objName, bool allowPartial)
{
if(!_isRestoring)
return 0;
if(allowPartial && _allowPartial)
return -1;
assert(FileName && FileName[0]);
assert(objName && objName[0]);
auto ret = _pendingDocMap.emplace(FileName,std::set<std::string>());
ret.first->second.emplace(objName);
if(ret.second) {
_pendingDocs.push_back(ret.first->first.c_str());
return 1;
}
return -1;
}
bool Application::isRestoring() const {
return _isRestoring || Document::isAnyRestoring();
}
bool Application::isClosingAll() const {
return _isClosingAll;
}
struct DocTiming {
FC_DURATION_DECLARE(d1);
FC_DURATION_DECLARE(d2);
DocTiming() {
FC_DURATION_INIT(d1);
FC_DURATION_INIT(d2);
}
};
class DocOpenGuard {
public:
bool &flag;
boost::signals2::signal<void ()> &signal;
DocOpenGuard(bool &f, boost::signals2::signal<void ()> &s)
:flag(f),signal(s)
{
flag = true;
}
~DocOpenGuard() {
if(flag) {
flag = false;
signal();
}
}
};
Document* Application::openDocument(const char * FileName, bool createView) {
std::vector<std::string> filenames(1,FileName);
auto docs = openDocuments(filenames,0,0,0,createView);
if(docs.size())
return docs.front();
return 0;
}
std::vector<Document*> Application::openDocuments(
const std::vector<std::string> &filenames,
const std::vector<std::string> *pathes,
const std::vector<std::string> *labels,
std::vector<std::string> *errs,
bool createView)
{
std::vector<Document*> res(filenames.size(),nullptr);
if(filenames.empty())
return res;
if(errs)
errs->resize(filenames.size());
DocOpenGuard guard(_isRestoring,signalFinishOpenDocument);
_pendingDocs.clear();
_pendingDocsReopen.clear();
_pendingDocMap.clear();
signalStartOpenDocument();
ParameterGrp::handle hGrp = GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document");
_allowPartial = !hGrp->GetBool("NoPartialLoading",false);
for(auto &name : filenames)
_pendingDocs.push_back(name.c_str());
std::deque<std::pair<Document *, DocTiming> > newDocs;
FC_TIME_INIT(t);
for(std::size_t count=0;;++count) {
const char *name = _pendingDocs.front();
_pendingDocs.pop_front();
bool isMainDoc = count<filenames.size();
try {
_objCount = -1;
std::set<std::string> objNames;
if(_allowPartial) {
auto it = _pendingDocMap.find(name);
if(it!=_pendingDocMap.end())
objNames.swap(it->second);
}
FC_TIME_INIT(t1);
DocTiming timing;
const char *path = name;
const char *label = 0;
if(isMainDoc) {
if(pathes && pathes->size()>count)
path = (*pathes)[count].c_str();
if(labels && labels->size()>count)
label = (*labels)[count].c_str();
}
auto doc = openDocumentPrivate(path,name,label,isMainDoc,createView,objNames);
FC_DURATION_PLUS(timing.d1,t1);
if(doc)
newDocs.emplace_front(doc,timing);
if(isMainDoc)
res[count] = doc;
_objCount = -1;
}catch(const Base::Exception &e) {
if(!errs && isMainDoc)
throw;
if(errs && isMainDoc)
(*errs)[count] = e.what();
else
Console().Error("Exception opening file: %s [%s]\n", name, e.what());
}catch(const std::exception &e) {
if(!errs && isMainDoc)
throw;
if(errs && isMainDoc)
(*errs)[count] = e.what();
else
Console().Error("Exception opening file: %s [%s]\n", name, e.what());
}catch(...) {
if(errs) {
if(isMainDoc)
(*errs)[count] = "unknown error";
} else {
_pendingDocs.clear();
_pendingDocsReopen.clear();
_pendingDocMap.clear();
throw;
}
}
if(_pendingDocs.empty()) {
if(_pendingDocsReopen.empty())
break;
_allowPartial = false;
_pendingDocs.swap(_pendingDocsReopen);
}
}
_pendingDocs.clear();
_pendingDocsReopen.clear();
_pendingDocMap.clear();
Base::SequencerLauncher seq("Postprocessing...", newDocs.size());
for(auto &v : newDocs) {
FC_TIME_INIT(t1);
v.first->afterRestore(true);
FC_DURATION_PLUS(v.second.d2,t1);
seq.next();
}
setActiveDocument(newDocs.back().first);
for(auto &v : newDocs) {
FC_DURATION_LOG(v.second.d1, v.first->getName() << " restore");
FC_DURATION_LOG(v.second.d2, v.first->getName() << " postprocess");
}
FC_TIME_LOG(t,"total");
_isRestoring = false;
signalFinishOpenDocument();
return res;
}
Document* Application::openDocumentPrivate(const char * FileName,
const char *propFileName, const char *label,
bool isMainDoc, bool createView,
const std::set<std::string> &objNames)
{
FileInfo File(FileName);
@@ -539,23 +726,64 @@ Document* Application::openDocument(const char * FileName)
for (std::map<std::string,Document*>::iterator it = DocMap.begin(); it != DocMap.end(); ++it) {
// get unique path separators
std::string fi = FileInfo(it->second->FileName.getValue()).filePath();
if (filepath == fi) {
std::stringstream str;
str << "The project '" << FileName << "' is already open!";
throw Base::FileSystemError(str.str().c_str());
if (filepath != fi)
continue;
if(it->second->testStatus(App::Document::PartialDoc)
|| it->second->testStatus(App::Document::PartialRestore)) {
// Here means a document is already partially loaded, but the document
// is requested again, either partial or not. We must check if the
// document contains the required object
if(isMainDoc) {
// Main document must be open fully, so close and reopen
closeDocument(it->first.c_str());
break;
}
if(_allowPartial) {
bool reopen = false;
for(auto &name : objNames) {
auto obj = it->second->getObject(name.c_str());
if(!obj || obj->testStatus(App::PartialObject)) {
reopen = true;
break;
}
}
if(!reopen)
return 0;
}
auto &names = _pendingDocMap[FileName];
names.clear();
_pendingDocsReopen.push_back(FileName);
return 0;
}
if(!isMainDoc)
return 0;
std::stringstream str;
str << "The project '" << FileName << "' is already open!";
throw Base::FileSystemError(str.str().c_str());
}
std::string name;
if(propFileName != FileName) {
FileInfo fi(propFileName);
name = fi.fileNamePure();
}else
name = File.fileNamePure();
// Use the same name for the internal and user name.
// The file name is UTF-8 encoded which means that the internal name will be modified
// to only contain valid ASCII characters but the user name will be kept.
Document* newDoc = newDocument(File.fileNamePure().c_str(), File.fileNamePure().c_str());
if(!label)
label = name.c_str();
Document* newDoc = newDocument(name.c_str(),label,isMainDoc && createView);
newDoc->FileName.setValue(File.filePath());
newDoc->FileName.setValue(propFileName==FileName?File.filePath():propFileName);
try {
// read the document
newDoc->restore();
newDoc->restore(File.filePath().c_str(),true,objNames);
return newDoc;
}
// if the project file itself is corrupt then
@@ -1235,6 +1463,11 @@ void Application::slotRecomputed(const Document& doc)
this->signalRecomputed(doc);
}
void Application::slotBeforeRecompute(const Document& doc)
{
this->signalBeforeRecomputeDocument(doc);
}
void Application::slotOpenTransaction(const Document& d, string s)
{
this->signalOpenTransaction(d, s);

View File

@@ -86,13 +86,35 @@ public:
* The second name is a UTF8 name of any kind. It's that name normally shown to
* the user and stored in the App::Document::Name property.
*/
App::Document* newDocument(const char * Name=0l, const char * UserName=0l);
App::Document* newDocument(const char * Name=0l, const char * UserName=0l, bool createView=true);
/// Closes the document \a name and removes it from the application.
bool closeDocument(const char* name);
/// find a unique document name
std::string getUniqueDocumentName(const char *Name) const;
/// Open an existing document from a file
App::Document* openDocument(const char * FileName=0l);
App::Document* openDocument(const char * FileName=0l, bool createView=true);
/** Open multiple documents
*
* @param filenames: input file names
* @param pathes: optional input file path in case it is different from
* filenames (mainly used during recovery).
* @param labels: optional label assign to document (mainly used during recovery).
* @param errs: optional output error message corresponding to each input
* file name. If errs is given, this function will catch all
* Base::Exception and save the error message inside. Otherwise, it will
* throw on exception when opening the input files.
* @param createView: whether to signal Gui module to create view on restore.
*
* @return Return opened document object corresponding to each input file
* name, which maybe NULL if failed.
*
* This function will also open any external referenced files.
*/
std::vector<Document*> openDocuments(const std::vector<std::string> &filenames,
const std::vector<std::string> *pathes=0,
const std::vector<std::string> *labels=0,
std::vector<std::string> *errs=0,
bool createView = true);
/// Retrieve the active document
App::Document* getActiveDocument(void) const;
/// Retrieve a named document
@@ -106,6 +128,14 @@ public:
void setActiveDocument(const char *Name);
/// close all documents (without saving)
void closeAllDocuments(void);
/// Add pending document to open together with the current opening document
int addPendingDocument(const char *FileName, const char *objName, bool allowPartial);
/// Indicate whether the application is opening (restoring) some document
bool isRestoring() const;
/// Indicate the application is closing all document
bool isClosingAll() const;
//@}
/** @name Application-wide trandaction setting */
//@{
/** Setup a pending application-wide active transaction
@@ -142,7 +172,7 @@ public:
/** @name Signals of the Application */
//@{
/// signal on new Document
boost::signals2::signal<void (const Document&)> signalNewDocument;
boost::signals2::signal<void (const Document&, bool)> signalNewDocument;
/// signal on document getting deleted
boost::signals2::signal<void (const Document&)> signalDeleteDocument;
/// signal on already deleted Document
@@ -177,6 +207,10 @@ public:
boost::signals2::signal<void (bool)> signalCloseTransaction;
/// signal on show hidden items
boost::signals2::signal<void (const Document&)> signalShowHidden;
/// signal on start opening document(s)
boost::signals2::signal<void ()> signalStartOpenDocument;
/// signal on finished opening document(s)
boost::signals2::signal<void ()> signalFinishOpenDocument;
//@}
@@ -202,6 +236,8 @@ public:
boost::signals2::signal<void (const App::DocumentObject&)> signalRelabelObject;
/// signal on activated Object
boost::signals2::signal<void (const App::DocumentObject&)> signalActivatedObject;
/// signal before recomputed document
boost::signals2::signal<void (const App::Document&)> signalBeforeRecomputeDocument;
/// signal on recomputed document
boost::signals2::signal<void (const App::Document&)> signalRecomputed;
/// signal on recomputed document object
@@ -377,6 +413,7 @@ protected:
void slotRedoDocument(const App::Document&);
void slotRecomputedObject(const App::DocumentObject&);
void slotRecomputed(const App::Document&);
void slotBeforeRecompute(const App::Document&);
void slotOpenTransaction(const App::Document&, std::string);
void slotCommitTransaction(const App::Document&);
void slotAbortTransaction(const App::Document&);
@@ -385,6 +422,10 @@ protected:
void slotChangePropertyEditor(const App::Document&, const App::Property &);
//@}
/// open single document only
App::Document* openDocumentPrivate(const char * FileName, const char *propFileName,
const char *label, bool isMainDoc, bool createView, const std::set<std::string> &objNames);
/// Helper class for App::Document to signal on close/abort transaction
class AppExport TransactionSignaller {
public:
@@ -439,6 +480,7 @@ private:
static PyObject* sListDocuments (PyObject *self,PyObject *args);
static PyObject* sAddDocObserver (PyObject *self,PyObject *args);
static PyObject* sRemoveDocObserver (PyObject *self,PyObject *args);
static PyObject *sIsRestoring (PyObject *self,PyObject *args);
static PyObject *sSetLogLevel (PyObject *self,PyObject *args);
static PyObject *sGetLogLevel (PyObject *self,PyObject *args);
@@ -446,9 +488,12 @@ private:
static PyObject *sCheckLinkDepth (PyObject *self,PyObject *args);
static PyObject *sGetLinksTo (PyObject *self,PyObject *args);
static PyObject *sGetDependentObjects(PyObject *self,PyObject *args);
static PyObject *sSetActiveTransaction (PyObject *self,PyObject *args);
static PyObject *sGetActiveTransaction (PyObject *self,PyObject *args);
static PyObject *sCloseActiveTransaction(PyObject *self,PyObject *args);
static PyObject *sCheckAbort(PyObject *self,PyObject *args);
static PyMethodDef Methods[];
friend class ApplicationObserver;
@@ -497,6 +542,13 @@ private:
std::map<std::string,std::string> &_mConfig;
App::Document* _pActiveDoc;
std::deque<const char *> _pendingDocs;
std::deque<const char *> _pendingDocsReopen;
std::map<std::string,std::set<std::string> > _pendingDocMap;
bool _isRestoring;
bool _allowPartial;
bool _isClosingAll;
// for estimate max link depth
int _objCount;

View File

@@ -45,6 +45,7 @@
#include <Base/Factory.h>
#include <Base/FileInfo.h>
#include <Base/UnitsApi.h>
#include <Base/Sequencer.h>
//using Base::GetConsole;
using namespace Base;
@@ -132,8 +133,8 @@ PyMethodDef Application::Methods[] = {
"Get a document by its name or raise an exception\n"
"if there is no document with the given name."},
{"listDocuments", (PyCFunction) Application::sListDocuments, METH_VARARGS,
"listDocuments() -> list\n\n"
"Return a list of names of all documents."},
"listDocuments(sort=False) -> list\n\n"
"Return a list of names of all documents, optionally sort in dependency order."},
{"addDocumentObserver", (PyCFunction) Application::sAddDocObserver, METH_VARARGS,
"addDocumentObserver() -> None\n\n"
"Add an observer to get notified about changes on documents."},
@@ -151,6 +152,12 @@ PyMethodDef Application::Methods[] = {
"getLinksTo(obj,options=0,maxCount=0) -- return the objects linked to 'obj'\n\n"
"options: 1: recursive, 2: check link array. Options can combine.\n"
"maxCount: to limit the number of links returned\n"},
{"getDependentObjects", (PyCFunction) Application::sGetDependentObjects, METH_VARARGS,
"getDependentObjects(obj|[obj,...], options=0)\n"
"Return a list of dependent objects including the given objects.\n\n"
"options: can have the following bit flags,\n"
" 1: to sort the list in topological order.\n"
" 2: to exclude dependency of Link type object."},
{"setActiveTransaction", (PyCFunction) Application::sSetActiveTransaction, METH_VARARGS,
"setActiveTransaction(name, persist=False) -- setup active transaction with the given name\n\n"
"name: the transaction name\n"
@@ -164,6 +171,14 @@ PyMethodDef Application::Methods[] = {
"getActiveTransaction() -> (name,id) return the current active transaction name and ID"},
{"closeActiveTransaction", (PyCFunction) Application::sCloseActiveTransaction, METH_VARARGS,
"closeActiveTransaction(abort=False) -- commit or abort current active transaction"},
{"isRestoring", (PyCFunction) Application::sIsRestoring, METH_VARARGS,
"isRestoring() -> Bool -- Test if the application is opening some document"},
{"checkAbort", (PyCFunction) Application::sCheckAbort, METH_VARARGS,
"checkAbort() -- check for user abort in length operation.\n\n"
"This only works if there is an active sequencer (or ProgressIndicator in Python).\n"
"There is an active sequencer during document restore and recomputation. User may\n"
"abort the operation by pressing the ESC key. Once detected, this function will\n"
"trigger a BaseExceptionFreeCADAbort exception."},
{NULL, NULL, 0, NULL} /* Sentinel */
};
@@ -213,6 +228,12 @@ PyObject* Application::sLoadFile(PyObject * /*self*/, PyObject *args)
}
}
PyObject* Application::sIsRestoring(PyObject * /*self*/, PyObject *args) {
if (!PyArg_ParseTuple(args, ""))
return NULL;
return Py::new_reference_to(Py::Boolean(GetApplication().isRestoring()));
}
PyObject* Application::sOpenDocument(PyObject * /*self*/, PyObject *args)
{
char* Name;
@@ -645,22 +666,26 @@ PyObject* Application::sGetHomePath(PyObject * /*self*/, PyObject *args)
PyObject* Application::sListDocuments(PyObject * /*self*/, PyObject *args)
{
if (!PyArg_ParseTuple(args, "")) // convert args: Python->C
PyObject *sort = Py_False;
if (!PyArg_ParseTuple(args, "|O",&sort)) // convert args: Python->C
return NULL; // NULL triggers exception
PY_TRY {
PyObject *pDict = PyDict_New();
PyObject *pKey;
Base::PyObjectBase* pValue;
for (std::map<std::string,Document*>::const_iterator It = GetApplication().DocMap.begin();
It != GetApplication().DocMap.end();++It) {
std::vector<Document*> docs = GetApplication().getDocuments();;
if(PyObject_IsTrue(sort))
docs = Document::getDependentDocuments(docs,true);
for (auto doc : docs) {
#if PY_MAJOR_VERSION >= 3
pKey = PyUnicode_FromString(It->first.c_str());
pKey = PyUnicode_FromString(doc->getName());
#else
pKey = PyString_FromString(It->first.c_str());
pKey = PyString_FromString(doc->getName());
#endif
// GetPyObject() increments
pValue = static_cast<Base::PyObjectBase*>(It->second->getPyObject());
pValue = static_cast<Base::PyObjectBase*>(doc->getPyObject());
PyDict_SetItem(pDict, pKey, pValue);
// now we can decrement again as PyDict_SetItem also has incremented
pValue->DecRef();
@@ -819,6 +844,41 @@ PyObject *Application::sGetLinksTo(PyObject * /*self*/, PyObject *args)
}PY_CATCH;
}
PyObject *Application::sGetDependentObjects(PyObject * /*self*/, PyObject *args)
{
PyObject *obj;
int options = 0;
if (!PyArg_ParseTuple(args, "O|i", &obj,&options))
return 0;
std::vector<App::DocumentObject*> objs;
if(PySequence_Check(obj)) {
Py::Sequence seq(obj);
for(size_t i=0;i<seq.size();++i) {
if(!PyObject_TypeCheck(seq[i].ptr(),&DocumentObjectPy::Type)) {
PyErr_SetString(PyExc_TypeError, "Expect element in sequence to be of type document object");
return 0;
}
objs.push_back(static_cast<DocumentObjectPy*>(seq[i].ptr())->getDocumentObjectPtr());
}
}else if(!PyObject_TypeCheck(obj,&DocumentObjectPy::Type)) {
PyErr_SetString(PyExc_TypeError,
"Expect first argument to be either a document object or sequence of document objects");
return 0;
}else
objs.push_back(static_cast<DocumentObjectPy*>(obj)->getDocumentObjectPtr());
PY_TRY {
auto ret = App::Document::getDependencyList(objs,options);
Py::Tuple tuple(ret.size());
for(size_t i=0;i<ret.size();++i)
tuple.setItem(i,Py::Object(ret[i]->getPyObject(),true));
return Py::new_reference_to(tuple);
} PY_CATCH;
}
PyObject *Application::sSetActiveTransaction(PyObject * /*self*/, PyObject *args)
{
char *name;
@@ -862,3 +922,13 @@ PyObject *Application::sCloseActiveTransaction(PyObject * /*self*/, PyObject *ar
} PY_CATCH;
}
PyObject *Application::sCheckAbort(PyObject * /*self*/, PyObject *args)
{
if (!PyArg_ParseTuple(args, ""))
return 0;
PY_TRY {
Base::Sequencer().checkAbort();
Py_Return;
}PY_CATCH
}

File diff suppressed because it is too large Load Diff

View File

@@ -70,6 +70,9 @@ public:
Restoring = 3,
Recomputing = 4,
PartialRestore = 5,
Importing = 6,
PartialDoc = 7,
AllowPartialRecompute = 8, // allow recomputing editing object if SkipRecompute is set
};
/** @name Properties */
@@ -157,11 +160,13 @@ public:
Base::XMLReader&)> signalImportObjects;
boost::signals2::signal<void (const std::vector<App::DocumentObject*>&, Base::Reader&,
const std::map<std::string, std::string>&)> signalImportViewObjects;
boost::signals2::signal<void (const std::vector<App::DocumentObject*>&)> signalFinishImportObjects;
//signal starting a save action to a file
boost::signals2::signal<void (const App::Document&, const std::string&)> signalStartSave;
//signal finishing a save action to a file
boost::signals2::signal<void (const App::Document&, const std::string&)> signalFinishSave;
boost::signals2::signal<void (const App::Document&)> signalRecomputed;
boost::signals2::signal<void (const App::Document&)> signalBeforeRecompute;
boost::signals2::signal<void (const App::Document&, const std::vector<App::DocumentObject*>&)> signalRecomputed;
boost::signals2::signal<void (const App::DocumentObject&)> signalRecomputedObject;
//signal a new opened transaction
boost::signals2::signal<void (const App::Document&, std::string)> signalOpenTransaction;
@@ -169,6 +174,8 @@ public:
boost::signals2::signal<void (const App::Document&)> signalCommitTransaction;
// signal an aborted transaction
boost::signals2::signal<void (const App::Document&)> signalAbortTransaction;
boost::signals2::signal<void (const App::Document&, const std::vector<App::DocumentObject*>&)> signalSkipRecompute;
boost::signals2::signal<void (const App::DocumentObject&)> signalFinishRestoreObject;
boost::signals2::signal<void (const App::Document&,const App::Property&)> signalChangePropertyEditor;
//@}
@@ -181,10 +188,32 @@ public:
bool saveAs(const char* file);
bool saveCopy(const char* file) const;
/// Restore the document from the file in Property Path
void restore (void);
void restore (const char *filename=0,
bool delaySignal=false, const std::set<std::string> &objNames={});
void afterRestore(bool checkPartial=false);
bool afterRestore(const std::vector<App::DocumentObject *> &, bool checkPartial=false);
enum ExportStatus {
NotExporting,
Exporting,
};
ExportStatus isExporting(const App::DocumentObject *obj) const;
void exportObjects(const std::vector<App::DocumentObject*>&, std::ostream&);
void exportGraphviz(std::ostream&) const;
std::vector<App::DocumentObject*> importObjects(Base::XMLReader& reader);
/** Import any externally linked objects
*
* @param objs: input list of objects. Only objects belonging to this document will
* be checked for external links. And all found external linked object will be imported
* to this document. Link type properties of those input objects will be automatically
* reassigned to the imported objects. Note that the link properties of other objects
* in the document but not included in the input list, will not be affected even if they
* point to some object beining imported. To import all objects, simply pass in all objects
* of this document.
*
* @return the list of imported objects
*/
std::vector<App::DocumentObject*> importLinks(
const std::vector<App::DocumentObject*> &objs = {});
/// Opens the document from its file name
//void open (void);
/// Is the document already saved to a file?
@@ -230,12 +259,15 @@ public:
void addObject(DocumentObject*, const char* pObjectName=0);
/** Copy an object from another document to this document
* If \a recursive is true then all objects this object depends on
* are copied as well. By default \a recursive is false.
* Returns the copy of the object or 0 if the creation failed.
/** Copy objects from another document to this document
*
* @param recursive: if true, then all objects this object depends on are
* copied as well. By default \a recursive is false.
*
* @return Returns the list of objects copied.
*/
DocumentObject* copyObject(DocumentObject* obj, bool recursive=false);
std::vector<DocumentObject*> copyObject(
const std::vector<DocumentObject*> &objs, bool recursive=false);
/** Move an object from another document to this document
* If \a recursive is true then all objects this object depends on
* are moved as well. By default \a recursive is false.
@@ -257,6 +289,8 @@ public:
std::string getUniqueObjectName(const char *Name) const;
/// Returns a name of the form prefix_number. d specifies the number of digits.
std::string getStandardObjectName(const char *Name, int d) const;
/// Returns a list of document's objects including the dependencies
std::vector<DocumentObject*> getDependingObjects() const;
/// Returns a list of all Objects
const std::vector<DocumentObject*> &getObjects() const;
std::vector<DocumentObject*> getObjectsOfType(const Base::Type& typeId) const;
@@ -277,18 +311,23 @@ public:
void purgeTouched();
/// check if there is any touched object in this document
bool isTouched(void) const;
/// check if there is any object must execute in this document
bool mustExecute(void) const;
/// returns all touched objects
std::vector<App::DocumentObject *> getTouched(void) const;
/// set the document to be closable, this is on by default.
void setClosable(bool);
/// check whether the document can be closed
bool isClosable() const;
/// Recompute all touched features and return the number of recalculated features
int recompute();
/** Recompute touched features and return the number of recalculated features
*
* @param objs: specify a sub set of objects to recompute. If empty, then
* all object in this document is checked for recompute
*/
int recompute(const std::vector<App::DocumentObject*> &objs={},
bool force=false,bool *hasError=0, int options=0);
/// Recompute only one feature
void recomputeFeature(DocumentObject* Feat);
/// get the error log from the recompute run
const std::vector<App::DocumentObjectExecReturn*> &getRecomputeLog(void)const{return _RecomputeLog;}
bool recomputeFeature(DocumentObject* Feat,bool recursive=false);
/// get the text of the error of a specified object
const char* getErrorDescription(const App::DocumentObject*) const;
/// return the status bits
@@ -379,11 +418,31 @@ public:
bool checkOnCycle(void);
/// get a list of all objects linking to the given object
std::vector<App::DocumentObject*> getInList(const DocumentObject* me) const;
/// Get a complete list of all objects the given objects depend on. The list
/// also contains the given objects!
/// deprecated! Use In- and OutList mimic in the DocumentObject instead!
std::vector<App::DocumentObject*> getDependencyList
(const std::vector<App::DocumentObject*>&) const;
/// Option bit flags used by getDepenencyList()
enum DependencyOption {
/// Return topological sorted list
DepSort = 1,
/// Do no include object linked by PropertyXLink, as it can handle external link
DepNoXLinked = 2,
/// Raise exception on cycles
DepNoCycle = 4,
};
/** Get a complete list of all objects the given objects depend on.
*
* This function is defined as static because it accpets objects from
* different documents, and the returned list will contain dependent
* objects from all relavent documents
*
* @param objs: input objects to query for dependency.
* @param options: See DependencyOption
*/
static std::vector<App::DocumentObject*> getDependencyList(
const std::vector<App::DocumentObject*> &objs, int options=0);
std::vector<App::Document*> getDependentDocuments(bool sort=true);
static std::vector<App::Document*> getDependentDocuments(std::vector<App::Document*> docs, bool sort);
// set Changed
//void setChanged(DocumentObject* change);
/// get a list of topological sorted objects (https://en.wikipedia.org/wiki/Topological_sorting)
@@ -411,6 +470,11 @@ public:
/// Check if there is any link to the given object
bool hasLinksTo(const DocumentObject *obj) const;
/// Called by objects during restore to ask for recompute
void addRecomputeObject(DocumentObject *obj);
const std::string &getOldLabel() const {return oldLabel;}
/// Function called to signal that an object identifier has been renamed
void renameObjectIdentifiers(const std::map<App::ObjectIdentifier, App::ObjectIdentifier> & paths, const std::function<bool(const App::DocumentObject*)> &selector = [](const App::DocumentObject *) { return true; });
@@ -418,6 +482,9 @@ public:
virtual std::string getFullName() const override;
/// Indicate if there is any document restoring/importing
static bool isAnyRestoring();
friend class Application;
/// because of transaction handling
friend class TransactionalObject;
@@ -448,12 +515,14 @@ protected:
/// callback from the Document objects after property was changed
void onChangedProperty(const DocumentObject *Who, const Property *What);
/// helper which Recompute only this feature
/// @return True if the recompute process of the Document shall be stopped, False if it shall be continued.
bool _recomputeFeature(DocumentObject* Feat);
/// @return 0 if succeed, 1 if failed, -1 if aborted by user.
int _recomputeFeature(DocumentObject* Feat);
void _clearRedos();
/// refresh the internal dependency graph
void _rebuildDependencyList(void);
void _rebuildDependencyList(
const std::vector<App::DocumentObject*> &objs = std::vector<App::DocumentObject*>());
std::string getTransientDirectoryName(const std::string& uuid, const std::string& filename) const;
/** Open a new command Undo/Redo, an UTF-8 name can be specified

View File

@@ -123,11 +123,11 @@ DocumentObjectExecReturn *DocumentObject::execute(void)
return StdReturn;
}
bool DocumentObject::recomputeFeature()
bool DocumentObject::recomputeFeature(bool recursive)
{
Document* doc = this->getDocument();
if (doc)
doc->recomputeFeature(this);
return doc->recomputeFeature(this,recursive);
return isValid();
}
@@ -227,6 +227,26 @@ const char *DocumentObject::getNameInDocument() const
return pcNameInDocument->c_str();
}
int DocumentObject::isExporting() const {
if(!getDocument() || !getNameInDocument())
return 0;
return getDocument()->isExporting(this);
}
std::string DocumentObject::getExportName(bool forced) const {
if(!pcNameInDocument)
return std::string();
if(!forced && !isExporting())
return *pcNameInDocument;
// '@' is an invalid character for an internal name, which ensures the
// following returned name will be unique in any document. Saving external
// object like that shall only happens in Document::exportObjects(). We
// shall strip out this '@' and the following document name during restoring.
return *pcNameInDocument + '@' + getDocument()->getName();
}
bool DocumentObject::isAttachedToDocument() const
{
return (pcNameInDocument != 0);
@@ -667,6 +687,19 @@ void DocumentObject::onChanged(const Property* prop)
if(GetApplication().isClosingAll())
return;
if(!GetApplication().isRestoring() &&
prop && !prop->testStatus(Property::PartialTrigger) &&
getDocument() &&
getDocument()->testStatus(Document::PartialDoc))
{
static App::Document *warnedDoc;
if(warnedDoc != getDocument()) {
warnedDoc = getDocument();
FC_WARN("Changes to partial loaded document will not be saved: "
<< getFullName() << '.' << prop->getName());
}
}
// Delay signaling view provider until the document object has handled the
// change
// if (_pDoc)
@@ -753,7 +786,7 @@ DocumentObject *DocumentObject::getSubObject(const char *subname,
// objects (think of the claimed children of a Fusion). But I do think we
// should change that.
if(transform && mat) {
auto pla = dynamic_cast<PropertyPlacement*>(getPropertyByName("Placement"));
auto pla = Base::freecad_dynamic_cast<PropertyPlacement>(getPropertyByName("Placement"));
if(pla)
*mat *= pla->getValue().toMatrix();
}

View File

@@ -53,7 +53,14 @@ enum ObjectStatus {
PythonCall = 6,
Destroy = 7,
Enforce = 8,
Expand = 16
Recompute2 = 9, // set when the object is being recomputed in the second pass
PartialObject = 10,
PendingRecompute = 11, // set by Document, indicating the object is in recomputation queue
PendingRemove = 12, // set by Document, indicating the object is in pending for remove after recompute
ObjImporting = 13, // Mark the object as importing
NoTouch = 14, // no touch on any property change
GeoExcluded = 15, // mark as a member but not claimed by GeoFeatureGroup
Expand = 16,
};
/** Return object for feature execution
@@ -114,6 +121,8 @@ public:
const char *getNameInDocument(void) const;
/// Return the object ID that is unique within its owner document
long getID() const {return _Id;}
/// returns the name that is safe to be exported to other document
std::string getExportName(bool forced=false) const;
/// Return the object full name of the form DocName#ObjName
virtual std::string getFullName() const override;
virtual bool isAttachedToDocument() const;
@@ -155,6 +164,8 @@ public:
void setStatus(ObjectStatus pos, bool on) {StatusBits.set((size_t)pos, on);}
//@}
int isExporting() const;
/** Child element handling
*/
//@{
@@ -275,8 +286,11 @@ public:
*/
virtual short mustExecute(void) const;
/// Recompute only this feature
bool recomputeFeature();
/** Recompute only this feature
*
* @param recursive: set to true to recompute any dependent objects as well
*/
bool recomputeFeature(bool recursive=false);
/// get the status Message
const char *getStatusString(void) const;
@@ -493,6 +507,14 @@ public:
virtual bool adjustRelativeLinks(const std::set<App::DocumentObject*> &inList,
std::set<App::DocumentObject*> *visited=0);
/** allow partial loading of dependent objects
*
* @return Returns 0 means do not support partial loading. 1 means allow
* dependent objects to be partially loaded, i.e. only create, but not
* restored. 2 means this object itself can be partially loaded.
*/
virtual int canLoadPartial() const {return 0;}
/** Allow object to redirect a subname path
*
* @param ss: input as the current subname path from \a topParent leading

View File

@@ -57,7 +57,7 @@
</Methode>
<Methode Name="recompute">
<Documentation>
<UserDocu>Recomputes this object</UserDocu>
<UserDocu>recompute(recursive=False): Recomputes this object</UserDocu>
</Documentation>
</Methode>
<Methode Name="getSubObject" Keyword="true">
@@ -250,12 +250,24 @@ or None if the GUI is not up</UserDocu>
<Parameter Name="ViewObject" Type="Object"/>
</Attribute>
<CustomAttributes />
<Attribute Name="MustExecute" ReadOnly="true">
<Documentation>
<UserDocu>Check if the object must be recomputed</UserDocu>
</Documentation>
<Parameter Name="MustExecute" Type="Boolean"/>
</Attribute>
<Attribute Name="ID" ReadOnly="true">
<Documentation>
<UserDocu>The unique identifier (among its document) of this object</UserDocu>
</Documentation>
<Parameter Name="ID" Type="Int"/>
</Attribute>
<Attribute Name="Removing" ReadOnly="true">
<Documentation>
<UserDocu>Indicate if the object is being removed</UserDocu>
</Documentation>
<Parameter Name="Removing" Type="Boolean"/>
</Attribute>
<Attribute Name="Parents" ReadOnly="true">
<Documentation>
<UserDocu>A List of tuple(parent,subname) holding all parents to this object</UserDocu>
@@ -268,5 +280,11 @@ or None if the GUI is not up</UserDocu>
</Documentation>
<Parameter Name="OldLabel" Type="String"/>
</Attribute>
<Attribute Name="NoTouch">
<Documentation>
<UserDocu>Enable/disable no touch on any property change</UserDocu>
</Documentation>
<Parameter Name="NoTouch" Type="Boolean"/>
</Attribute>
</PythonExport>
</GenerateModel>

View File

@@ -192,6 +192,9 @@ Py::List DocumentObjectPy::getState(void) const
uptodate = false;
list.append(Py::String("Recompute"));
}
if (object->testStatus(App::Recompute2)) {
list.append(Py::String("Recompute2"));
}
if (object->isRestoring()) {
uptodate = false;
list.append(Py::String("Restore"));
@@ -199,6 +202,12 @@ Py::List DocumentObjectPy::getState(void) const
if (object->testStatus(App::Expand)){
list.append(Py::String("Expanded"));
}
if (object->testStatus(App::PartialObject)){
list.append(Py::String("Partial"));
}
if (object->testStatus(App::ObjImporting)){
list.append(Py::String("Importing"));
}
if (uptodate) {
list.append(Py::String("Up-to-date"));
}
@@ -361,11 +370,12 @@ PyObject* DocumentObjectPy::setExpression(PyObject * args)
PyObject* DocumentObjectPy::recompute(PyObject *args)
{
if (!PyArg_ParseTuple(args, ""))
PyObject *recursive=Py_False;
if (!PyArg_ParseTuple(args, "|O",&recursive))
return NULL;
try {
bool ok = getDocumentObjectPtr()->recomputeFeature();
bool ok = getDocumentObjectPtr()->recomputeFeature(PyObject_IsTrue(recursive));
return Py_BuildValue("O", (ok ? Py_True : Py_False));
}
catch (const Base::Exception& e) {
@@ -650,6 +660,16 @@ PyObject* DocumentObjectPy::getParentGeoFeatureGroup(PyObject *args)
}
}
Py::Boolean DocumentObjectPy::getMustExecute() const
{
try {
return Py::Boolean(getDocumentObjectPtr()->mustExecute()?true:false);
}
catch (const Base::Exception& e) {
throw Py::RuntimeError(e.what());
}
}
PyObject* DocumentObjectPy::getPathsByOutList(PyObject *args)
{
PyObject* o;
@@ -838,3 +858,11 @@ PyObject *DocumentObjectPy::adjustRelativeLinks(PyObject *args) {
Py::String DocumentObjectPy::getOldLabel() const {
return Py::String(getDocumentObjectPtr()->getOldLabel());
}
Py::Boolean DocumentObjectPy::getNoTouch() const {
return Py::Boolean(getDocumentObjectPtr()->testStatus(ObjectStatus::NoTouch));
}
void DocumentObjectPy::setNoTouch(Py::Boolean value) {
getDocumentObjectPtr()->setStatus(ObjectStatus::NoTouch,value.isTrue());
}

View File

@@ -93,14 +93,38 @@ viewType (String): override the view provider type directly, only effective when
</Methode>
<Methode Name="copyObject">
<Documentation>
<UserDocu>copyObject(object, bool with_dependencies = False, bool ignored_argument = False)
Copy an object from another document to this document. If with_dependencies is True, all objects this object depends on are copied too.</UserDocu>
<UserDocu>
copyObject(object, with_dependencies=False)
Copy an object or objects from another document to this document.
object: can either a single object or sequence of objects
with_dependencies: if True, all internal dependent objects are copied too.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="moveObject">
<Documentation>
<UserDocu>moveObject(object, bool with_dependencies = False)
Transfers an object from another document to this document. If with_dependencies is True, all objects this object depends on are transferred too.</UserDocu>
<UserDocu>
moveObject(object, bool with_dependencies = False)
Transfers an object from another document to this document.
object: can either a single object or sequence of objects
with_dependencies: if True, all internal dependent objects are copied too.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="importLinks">
<Documentation>
<UserDocu>
importLinks(object|[object...])
Import any externally linked object given a list of objects in
this document. Any link type properties of the input objects
will be automatically reassigned to the imported object
If no object is given as input, it import all externally linked
object of this document.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="undo">
@@ -120,7 +144,7 @@ viewType (String): override the view provider type directly, only effective when
</Methode>
<Methode Name="recompute">
<Documentation>
<UserDocu>Recompute the document and returns the amount of recomputed features</UserDocu>
<UserDocu>recompute(objs=None): Recompute the document and returns the amount of recomputed features</UserDocu>
</Documentation>
</Methode>
<Methode Name="getObject">
@@ -144,10 +168,10 @@ Both parameters are optional.</UserDocu>
<Methode Name="getLinksTo">
<Documentation>
<UserDocu>
getLinksTo(obj, options=0, maxCount=0): return objects linked to 'obj'
getLinksTo(obj, options=0, maxCount=0): return objects linked to 'obj'
options: 1: recursive, 2: check link array. Options can combine.
maxCount: to limit the number of links returned
options: 1: recursive, 2: check link array. Options can combine.
maxCount: to limit the number of links returned
</UserDocu>
</Documentation>
</Methode>
@@ -161,6 +185,17 @@ Both parameters are optional.</UserDocu>
<UserDocu>Returns a file name with path in the temp directory of the document.</UserDocu>
</Documentation>
</Methode>
<Methode Name="getDependentDocuments">
<Documentation>
<UserDocu>
getDependentDocuments(sort=True)
Returns a list of documents that this document directly or indirectly links to including itself.
sort: whether to topologically sort the return list
</UserDocu>
</Documentation>
</Methode>
<Attribute Name="DependencyGraph" ReadOnly="true">
<Documentation>
<UserDocu>The dependency graph as GraphViz text</UserDocu>
@@ -245,12 +280,54 @@ Both parameters are optional.</UserDocu>
</Documentation>
<Parameter Name="HasPendingTransaction" Type="Boolean"/>
</Attribute>
<Attribute Name="InList" ReadOnly="true">
<Documentation>
<UserDocu>A list of all documents that link to this document.</UserDocu>
</Documentation>
<Parameter Name="InList" Type="List" />
</Attribute>
<Attribute Name="OutList" ReadOnly="true">
<Documentation>
<UserDocu>A list of all documents that this document links to.</UserDocu>
</Documentation>
<Parameter Name="OutList" Type="List" />
</Attribute>
<Attribute Name="Restoring" ReadOnly="true">
<Documentation>
<UserDocu>Indicate if the document is restoring</UserDocu>
</Documentation>
<Parameter Name="Restoring" Type="Boolean" />
</Attribute>
<Attribute Name="Partial" ReadOnly="true">
<Documentation>
<UserDocu>Indicate if the document is partially loaded</UserDocu>
</Documentation>
<Parameter Name="Partial" Type="Boolean" />
</Attribute>
<Attribute Name="Importing" ReadOnly="true">
<Documentation>
<UserDocu>Indicate if the document is importing. Note the document will also report Restoring while importing</UserDocu>
</Documentation>
<Parameter Name="Importing" Type="Boolean" />
</Attribute>
<Attribute Name="Recomputing" ReadOnly="true">
<Documentation>
<UserDocu>Indicate if the document is recomputing</UserDocu>
</Documentation>
<Parameter Name="Recomputing" Type="Boolean" />
</Attribute>
<Attribute Name="Transacting" ReadOnly="true">
<Documentation>
<UserDocu>Indicate whether the document is undoing/redoing</UserDocu>
</Documentation>
<Parameter Name="Transacting" Type="Boolean" />
</Attribute>
<Attribute Name="OldLabel" ReadOnly="true">
<Documentation>
<UserDocu>Contains the old label before change</UserDocu>
</Documentation>
<Parameter Name="OldLabel" Type="String"/>
</Attribute>
<CustomAttributes />
</PythonExport>
</GenerateModel>

View File

@@ -289,21 +289,77 @@ PyObject* DocumentPy::removeObject(PyObject *args)
PyObject* DocumentPy::copyObject(PyObject *args)
{
// 'keep' is not needed any more but leave it there for backward compatibility
PyObject *obj, *rec=Py_False, *keep=Py_False;
if (!PyArg_ParseTuple(args, "O!|O!O!",&(DocumentObjectPy::Type),&obj,&PyBool_Type,&rec,&PyBool_Type,&keep))
PyObject *obj, *rec=Py_False;
if (!PyArg_ParseTuple(args, "O|O",&obj,&rec))
return NULL; // NULL triggers exception
DocumentObjectPy* docObj = static_cast<DocumentObjectPy*>(obj);
DocumentObject* copy = getDocumentPtr()->copyObject(docObj->getDocumentObjectPtr(),
PyObject_IsTrue(rec) ? true : false);
if (copy) {
return copy->getPyObject();
}
else {
std::string str("Failed to copy the object");
throw Py::Exception(Base::BaseExceptionFreeCADError,str);
std::vector<App::DocumentObject*> objs;
bool single = false;
if(PySequence_Check(obj)) {
Py::Sequence seq(obj);
for(size_t i=0;i<seq.size();++i) {
if(!PyObject_TypeCheck(seq[i].ptr(),&DocumentObjectPy::Type)) {
PyErr_SetString(PyExc_TypeError, "Expect element in sequence to be of type document object");
return 0;
}
objs.push_back(static_cast<DocumentObjectPy*>(seq[i].ptr())->getDocumentObjectPtr());
}
}else if(!PyObject_TypeCheck(obj,&DocumentObjectPy::Type)) {
PyErr_SetString(PyExc_TypeError,
"Expect first argument to be either a document object or sequence of document objects");
return 0;
}else {
objs.push_back(static_cast<DocumentObjectPy*>(obj)->getDocumentObjectPtr());
single = true;
}
PY_TRY {
auto ret = getDocumentPtr()->copyObject(objs,PyObject_IsTrue(rec));
if(ret.size()==1 && single)
return ret[0]->getPyObject();
Py::Tuple tuple(ret.size());
for(size_t i=0;i<ret.size();++i)
tuple.setItem(i,Py::Object(ret[i]->getPyObject(),true));
return Py::new_reference_to(tuple);
}PY_CATCH
}
PyObject* DocumentPy::importLinks(PyObject *args)
{
PyObject *obj = Py_None;
if (!PyArg_ParseTuple(args, "|O",&obj))
return NULL; // NULL triggers exception
std::vector<App::DocumentObject*> objs;
if(PySequence_Check(obj)) {
Py::Sequence seq(obj);
for(size_t i=0;i<seq.size();++i) {
if(!PyObject_TypeCheck(seq[i].ptr(),&DocumentObjectPy::Type)) {
PyErr_SetString(PyExc_TypeError, "Expect element in sequence to be of type document object");
return 0;
}
objs.push_back(static_cast<DocumentObjectPy*>(seq[i].ptr())->getDocumentObjectPtr());
}
}else if(obj == Py_None) {
}else if(!PyObject_TypeCheck(obj,&DocumentObjectPy::Type)) {
PyErr_SetString(PyExc_TypeError,
"Expect first argument to be either a document object or sequence of document objects");
return 0;
}else
objs.push_back(static_cast<DocumentObjectPy*>(obj)->getDocumentObjectPtr());
if(objs.empty())
objs = getDocumentPtr()->getObjects();
PY_TRY {
auto ret = getDocumentPtr()->importLinks(objs);
Py::Tuple tuple(ret.size());
for(size_t i=0;i<ret.size();++i)
tuple.setItem(i,Py::Object(ret[i]->getPyObject(),true));
return Py::new_reference_to(tuple);
}PY_CATCH
}
PyObject* DocumentPy::moveObject(PyObject *args)
@@ -405,16 +461,34 @@ PyObject* DocumentPy::clearUndos(PyObject * args)
PyObject* DocumentPy::recompute(PyObject * args)
{
if (!PyArg_ParseTuple(args, "")) // convert args: Python->C
PyObject *pyobjs = Py_None;
PyObject *force = Py_False;
PyObject *checkCycle = Py_False;
if (!PyArg_ParseTuple(args, "|OO!O!",&pyobjs,
&PyBool_Type,&force,&PyBool_Type,&checkCycle)) // convert args: Python->C
return NULL; // NULL triggers exception
try {
int objectCount = getDocumentPtr()->recompute();
PY_TRY {
std::vector<App::DocumentObject *> objs;
if(pyobjs!=Py_None) {
if(!PySequence_Check(pyobjs)) {
PyErr_SetString(PyExc_TypeError, "expect input of sequence of document objects");
return 0;
}
Py::Sequence seq(pyobjs);
for(size_t i=0;i<seq.size();++i) {
if(!PyObject_TypeCheck(seq[i].ptr(),&DocumentObjectPy::Type)) {
PyErr_SetString(PyExc_TypeError, "Expect element in sequence to be of type document object");
return 0;
}
objs.push_back(static_cast<DocumentObjectPy*>(seq[i].ptr())->getDocumentObjectPtr());
}
}
int options = 0;
if(PyObject_IsTrue(checkCycle))
options = Document::DepNoCycle;
int objectCount = getDocumentPtr()->recompute(objs,PyObject_IsTrue(force),0,options);
return Py::new_reference_to(Py::Int(objectCount));
}
catch (const Base::RuntimeError& e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
return 0;
}
} PY_CATCH;
}
PyObject* DocumentPy::getObject(PyObject *args)
@@ -727,9 +801,68 @@ PyObject* DocumentPy::getLinksTo(PyObject *args)
ret.setItem(i++,Py::Object(o->getPyObject(),true));
return Py::new_reference_to(ret);
}PY_CATCH
}
Py::List DocumentPy::getInList(void) const
{
Py::List ret;
auto lists = PropertyXLink::getDocumentInList(getDocumentPtr());
if(lists.size()==1) {
for(auto doc : lists.begin()->second)
ret.append(Py::Object(doc->getPyObject(), true));
}
return ret;
}
Py::List DocumentPy::getOutList(void) const
{
Py::List ret;
auto lists = PropertyXLink::getDocumentOutList(getDocumentPtr());
if(lists.size()==1) {
for(auto doc : lists.begin()->second)
ret.append(Py::Object(doc->getPyObject(), true));
}
return ret;
}
PyObject *DocumentPy::getDependentDocuments(PyObject *args) {
PyObject *sort = Py_True;
if (!PyArg_ParseTuple(args, "|O", &sort))
return 0;
PY_TRY {
auto docs = getDocumentPtr()->getDependentDocuments(PyObject_IsTrue(sort));
Py::List ret;
for(auto doc : docs)
ret.append(Py::Object(doc->getPyObject(), true));
return Py::new_reference_to(ret);
} PY_CATCH;
}
Py::Boolean DocumentPy::getRestoring(void) const
{
return Py::Boolean(getDocumentPtr()->testStatus(Document::Status::Restoring));
}
Py::Boolean DocumentPy::getPartial(void) const
{
return Py::Boolean(getDocumentPtr()->testStatus(Document::Status::PartialDoc));
}
Py::Boolean DocumentPy::getImporting(void) const
{
return Py::Boolean(getDocumentPtr()->testStatus(Document::Status::Importing));
}
Py::Boolean DocumentPy::getRecomputing(void) const
{
return Py::Boolean(getDocumentPtr()->testStatus(Document::Status::Recomputing));
}
Py::Boolean DocumentPy::getTransacting() const {
return Py::Boolean(getDocumentPtr()->isPerformingTransaction());
}
Py::String DocumentPy::getOldLabel() const {
return Py::String(getDocumentPtr()->getOldLabel());
}

View File

@@ -63,6 +63,14 @@ public:
return true;
}
protected:
// It is not safe to change potential object name reference at this level.
// For example, a LinkSub with sub element name Face1 may also be some
// object's name that may potentially be mapped. In addition, with the
// introduction of full quanlified SubName reference, the Sub value inside
// LinkSub may require customized mapping. So we move the mapping logic to
// various link property's Restore() function.
#if 0
void startElement(const XMLCh* const uri, const XMLCh* const localname,
const XMLCh* const qname,
const XERCES_CPP_NAMESPACE_QUALIFIER Attributes& attrs)
@@ -107,6 +115,7 @@ protected:
if (LocalName == "Property")
propertyStack.pop();
}
#endif
private:
std::map<std::string, std::string>& nameMap;

View File

@@ -50,6 +50,8 @@ public:
void SaveDocFile (Base::Writer & w) const;
void RestoreDocFile(Base::Reader & r);
const std::map<std::string, std::string> &getNameMap() const {return nameMap;}
private:
bool guiup;
bool verbose;

View File

@@ -155,10 +155,35 @@ public:
/// Get valid paths for this property; used by auto completer
virtual void getPaths(std::vector<App::ObjectIdentifier> & paths) const;
/// Called at the begining of Document::afterRestore(). See comments there.
/** Called at the begining of Document::afterRestore()
*
* This function is called without dependency sorting, because some
* types of link property can only reconstructs the linking information
* inside this function.
*
* One example use case of this function is PropertyLinkSub that uses
* afterRestore() to parse and restore subname references, which may
* contain sub-object reference from external document, and there will be
* special mapping required during object import.
*
* Another example is PropertyExpressionEngine which only parse the
* restored expression in afterRestore(). The reason, in addition to
* subname mapping like PropertyLinkSub, is that it can handle document
* name adjustment as well. It internally relies on PropertyXLink to store
* the external document path for external linking. When the extenal
* document is restored, its internal name may change due to name conflict
* with existing documents. PropertyExpressionEngine can now auto adjust
* external references without any problem.
*/
virtual void afterRestore() {}
/// Called before calling DocumentObject::onDocumentRestored()
/** Called before calling DocumentObject::onDocumentRestored()
*
* This function is called after finished calling Property::afterRestore()
* of all properies of objects. By then, the object dependency information
* is assumed ready. So, unlike Property::afterRestore(), this function is
* called on objects with dependency order.
*/
virtual void onContainerRestored() {}
/** Property status handling

View File

@@ -150,6 +150,9 @@ public:
*/
bool wasCanceled() const;
/// Check if the operation is aborted by user
virtual void checkAbort() {}
protected:
/**
* Starts a new operation, returns false if there is already a pending operation,

View File

@@ -63,6 +63,9 @@ public:
return true;
}
protected:
// See App::MergeDocument::XMLMergeReader for comments, with one additional
// benefits, we can save repetitive coding here.
#if 0
void startElement(const XMLCh* const uri, const XMLCh* const localname,
const XMLCh* const qname,
const XERCES_CPP_NAMESPACE_QUALIFIER Attributes& attrs)
@@ -107,6 +110,7 @@ protected:
if (LocalName == "Property")
propertyStack.pop();
}
#endif
private:
std::map<std::string, std::string>& nameMap;