X-Git-Url: http://git.salome-platform.org/gitweb/?a=blobdiff_plain;f=src%2FModel%2FModel_Document.cpp;h=75cfb1de8b761910f7b3875a35add957da0a6834;hb=ad2e59f946f2fb5092b1e3de09bae96d6f9a3a10;hp=fb00ed5bf3f3c43e29ecf690de1d39317e8ca79b;hpb=02bde297c205d1f7dd3f4b4c91bd5d97b5a7d791;p=modules%2Fshaper.git diff --git a/src/Model/Model_Document.cpp b/src/Model/Model_Document.cpp index fb00ed5bf..be2877848 100644 --- a/src/Model/Model_Document.cpp +++ b/src/Model/Model_Document.cpp @@ -1,34 +1,75 @@ -// Copyright (C) 2014-20xx CEA/DEN, EDF R&D - -// File: Model_Document.cxx -// Created: 28 Feb 2014 -// Author: Mikhail PONIKAROV +// Copyright (C) 2014-2019 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// #include #include +#include #include #include #include -#include -#include -#include -#include +#include #include #include +#include +#include +#include #include -#include +#include +#include #include #include #include #include +#include +#include #include #include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + #include #include +#include +#include #include #ifndef WIN32 @@ -44,38 +85,52 @@ static const int UNDO_LIMIT = 1000; // number of possible undo operations (big for sketcher) static const int TAG_GENERAL = 1; // general properties tag -static const int TAG_OBJECTS = 2; // tag of the objects sub-tree (features, results) -static const int TAG_HISTORY = 3; // tag of the history sub-tree (python dump) - -// feature sub-labels -static const int TAG_FEATURE_ARGUMENTS = 1; ///< where the arguments are located -static const int TAG_FEATURE_RESULTS = 2; ///< where the results are located - -/// -/// 0:1:2 - where features are located -/// 0:1:2:N:1 - data of the feature N -/// 0:1:2:N:2:K:1 - data of the K result of the feature N -Model_Document::Model_Document(const std::string theID, const std::string theKind) - : myID(theID), myKind(theKind), +// general sub-labels +/// where the reference to the current feature label is located (or no attribute if null feature) +static const int TAG_CURRENT_FEATURE = 1; ///< reference to the current feature +/// integer, index of the transaction + GUID for auto recomputation blocking +static const int TAG_CURRENT_TRANSACTION = 2; +static const int TAG_SELECTION_FEATURE = 3; ///< integer, tag of the selection feature label +static const int TAG_NODES_STATE = 4; ///< array, tag of the Object Browser nodes states +///< naming structures constructions selected from other document +static const int TAG_EXTERNAL_CONSTRUCTIONS = 5; + +/// reference to the shape in external document: sting list attribute identifier +static const Standard_GUID kEXTERNAL_SHAPE_REF("9aa5dd14-6d34-4a8d-8786-05842fd7bbbd"); + +Model_Document::Model_Document(const int theID, const std::string theKind) + : myID(theID), myKind(theKind), myIsActive(false), myIsSetCurrentFeature(false), myDoc(new TDocStd_Document("BinOcaf")) // binary OCAF format { - myDoc->SetUndoLimit(UNDO_LIMIT); +#ifdef TINSPECTOR + CDF_Session::CurrentSession()->Directory()->Add(myDoc); +#endif + myObjs = new Model_Objects(myDoc->Main()); + myDoc->SetUndoLimit(UNDO_LIMIT); myTransactionSave = 0; myExecuteFeatures = true; // to have something in the document and avoid empty doc open/save problem // in transaction for nesting correct working myDoc->NewCommand(); TDataStd_Integer::Set(myDoc->Main().Father(), 0); + // this to avoid creation of integer attribute outside the transaction after undo + transactionID(); myDoc->CommitCommand(); } -/// Returns the file name of this document by the nameof directory and identifuer of a document -static TCollection_ExtendedString DocFileName(const char* theFileName, const std::string& theID) +void Model_Document::setThis(DocumentPtr theDoc) +{ + myObjs->setOwner(theDoc); +} + +/// Returns the file name of this document by the name of directory and identifier of a document +static TCollection_ExtendedString DocFileName(const char* theDirName, const std::string& theID) { - TCollection_ExtendedString aPath((const Standard_CString) theFileName); + TCollection_ExtendedString aPath((const Standard_CString) theDirName); // remove end-separators - while(aPath.Length() && (aPath.Value(aPath.Length()) == '\\' || aPath.Value(aPath.Length()) == '/')) + while(aPath.Length() && + (aPath.Value(aPath.Length()) == '\\' || aPath.Value(aPath.Length()) == '/')) aPath.Remove(aPath.Length()); aPath += _separator_; aPath += theID.c_str(); @@ -83,154 +138,306 @@ static TCollection_ExtendedString DocFileName(const char* theFileName, const std return aPath; } -bool Model_Document::load(const char* theFileName) +bool Model_Document::isRoot() const +{ + return this == Model_Session::get()->moduleDocument().get(); +} + +// LCOV_EXCL_START +/// Makes all modification and generation naming shapes that have old shapes corresponding to +/// shapes in a root document be equal to this root document +static void updateShapesFromRoot(const TDF_Label theThisAccess, const TDF_Label theRootAccess) +{ + TopTools_DataMapOfShapeShape aCurrentToRoot; // shapes that must be updated: from this to root + TDF_ChildIDIterator aThisIter(theThisAccess.Root(), kEXTERNAL_SHAPE_REF, true); + for(; aThisIter.More(); aThisIter.Next()) { + aCurrentToRoot.Clear(); + Handle(TNaming_NamedShape) aNS; + if (!aThisIter.Value()->Label().FindAttribute(TNaming_NamedShape::GetID(), aNS)) + continue; + if (aNS->Evolution() != TNaming_GENERATED && aNS->Evolution() != TNaming_MODIFY) + continue; + for (TNaming_Iterator aNSIter(aNS); aNSIter.More(); aNSIter.Next()) { + const TopoDS_Shape& anOld = aNSIter.OldShape(); + if (anOld.IsNull()) + continue; + TNaming_OldShapeIterator aNewIter(anOld, theThisAccess); + for (; aNewIter.More(); aNewIter.Next()) { + TNaming_Evolution anEvolution = aNewIter.NamedShape()->Evolution(); + if (anEvolution != TNaming_SELECTED && anEvolution != TNaming_DELETE) + break; + } + if (aNewIter.More()) + continue; + GeomShapePtr anOldShape(new GeomAPI_Shape), aRootShape(new GeomAPI_Shape); + anOldShape->setImpl(new TopoDS_Shape(anOld)); + anOldShape = GeomAPI_Tools::getTypedShape(anOldShape); + + // search the same shape in the root document + Handle(TDataStd_ExtStringList) anEntries = + Handle(TDataStd_ExtStringList)::DownCast(aThisIter.Value()); + TDataStd_ListOfExtendedString::Iterator anIter(anEntries->List()); + for (; anIter.More(); anIter.Next()) { + TDF_Label aRootLab; + TDF_Tool::Label(theRootAccess.Data(), anIter.Value(), aRootLab); + if (aRootLab.IsNull()) + continue; + Handle(TNaming_NamedShape) aRootNS; + if (!aRootLab.FindAttribute(TNaming_NamedShape::GetID(), aRootNS)) + continue; + TNaming_Iterator aRootShapes(aRootNS); + for (; aRootShapes.More(); aRootShapes.Next()) { + if (aRootShapes.NewShape().IsNull()) + continue; + aRootShape->setImpl(new TopoDS_Shape(aRootShapes.NewShape())); + aRootShape = GeomAPI_Tools::getTypedShape(aRootShape); + if (!anOldShape->isEqual(aRootShape)) // special checking by geometry + continue; + // found a good corresponded shape + if (!anOld.IsEqual(aRootShapes.NewShape())) + aCurrentToRoot.Bind(anOld, aRootShapes.NewShape()); + } + } + } + if (!aCurrentToRoot.IsEmpty()) { // update the whole named shape content + TopTools_ListOfShape anOld, aNew; + TNaming_Evolution anEvol = aNS->Evolution(); + for(TNaming_Iterator aNSIter(aNS); aNSIter.More(); aNSIter.Next()) { + anOld.Prepend(aCurrentToRoot.IsBound(aNSIter.OldShape()) ? + aCurrentToRoot.Find(aNSIter.OldShape()) : aNSIter.OldShape()); + aNew.Prepend(aNSIter.NewShape()); + } + TNaming_Builder aBuilder(aNS->Label()); + TopTools_ListOfShape::Iterator anOldIter(anOld), aNewIter(aNew); + for(; anOldIter.More(); anOldIter.Next(), aNewIter.Next()) { + if (anEvol == TNaming_GENERATED) { + aBuilder.Generated(anOldIter.Value(), aNewIter.Value()); + } else if (anEvol == TNaming_MODIFY) { + aBuilder.Modify(anOldIter.Value(), aNewIter.Value()); + } + } + } + } +} +// LCOV_EXCL_STOP + +bool Model_Document::load(const char* theDirName, const char* theFileName, DocumentPtr theThis) { Handle(Model_Application) anApp = Model_Application::getApplication(); - if (this == Model_Session::get()->moduleDocument().get()) { - anApp->setLoadPath(theFileName); + if (isRoot()) { + anApp->setLoadPath(theDirName); } - TCollection_ExtendedString aPath(DocFileName(theFileName, myID)); + TCollection_ExtendedString aPath(DocFileName(theDirName, theFileName)); PCDM_ReaderStatus aStatus = (PCDM_ReaderStatus) -1; + Handle(TDocStd_Document) aLoaded; try { - aStatus = anApp->Open(aPath, myDoc); - } catch (Standard_Failure) { - Handle(Standard_Failure) aFail = Standard_Failure::Caught(); - Events_Error::send( - std::string("Exception in opening of document: ") + aFail->GetMessageString()); + aStatus = anApp->Open(aPath, aLoaded); + } catch (Standard_Failure const& anException) { + Events_InfoMessage("Model_Document", + "Exception in opening of document: %1").arg(anException.GetMessageString()).send(); return false; } bool isError = aStatus != PCDM_RS_OK; if (isError) { + // LCOV_EXCL_START switch (aStatus) { case PCDM_RS_UnknownDocument: - Events_Error::send(std::string("Can not open document")); + Events_InfoMessage("Model_Document", "Can not open document").send(); break; case PCDM_RS_AlreadyRetrieved: - Events_Error::send(std::string("Can not open document: already opened")); + Events_InfoMessage("Model_Document", "Can not open document: already opened").send(); break; case PCDM_RS_AlreadyRetrievedAndModified: - Events_Error::send( - std::string("Can not open document: already opened and modified")); + Events_InfoMessage("Model_Document", + "Can not open document: already opened and modified").send(); break; case PCDM_RS_NoDriver: - Events_Error::send(std::string("Can not open document: driver library is not found")); + Events_InfoMessage("Model_Document", + "Can not open document: driver library is not found").send(); break; case PCDM_RS_UnknownFileDriver: - Events_Error::send(std::string("Can not open document: unknown driver for opening")); + Events_InfoMessage("Model_Document", + "Can not open document: unknown driver for opening").send(); break; case PCDM_RS_OpenError: - Events_Error::send(std::string("Can not open document: file open error")); + Events_InfoMessage("Model_Document", "Can not open document: file open error").send(); break; case PCDM_RS_NoVersion: - Events_Error::send(std::string("Can not open document: invalid version")); + Events_InfoMessage("Model_Document", "Can not open document: invalid version").send(); break; case PCDM_RS_NoModel: - Events_Error::send(std::string("Can not open document: no data model")); + Events_InfoMessage("Model_Document", "Can not open document: no data model").send(); break; case PCDM_RS_NoDocument: - Events_Error::send(std::string("Can not open document: no document inside")); + Events_InfoMessage("Model_Document", "Can not open document: no document inside").send(); break; case PCDM_RS_FormatFailure: - Events_Error::send(std::string("Can not open document: format failure")); + Events_InfoMessage("Model_Document", "Can not open document: format failure").send(); break; case PCDM_RS_TypeNotFoundInSchema: - Events_Error::send(std::string("Can not open document: invalid object")); + Events_InfoMessage("Model_Document", "Can not open document: invalid object").send(); break; case PCDM_RS_UnrecognizedFileFormat: - Events_Error::send(std::string("Can not open document: unrecognized file format")); + Events_InfoMessage("Model_Document", + "Can not open document: unrecognized file format").send(); break; case PCDM_RS_MakeFailure: - Events_Error::send(std::string("Can not open document: make failure")); + Events_InfoMessage("Model_Document", "Can not open document: make failure").send(); break; case PCDM_RS_PermissionDenied: - Events_Error::send(std::string("Can not open document: permission denied")); + Events_InfoMessage("Model_Document", "Can not open document: permission denied").send(); break; case PCDM_RS_DriverFailure: - Events_Error::send(std::string("Can not open document: driver failure")); + Events_InfoMessage("Model_Document", "Can not open document: driver failure").send(); break; default: - Events_Error::send(std::string("Can not open document: unknown error")); + Events_InfoMessage("Model_Document", "Can not open document: unknown error").send(); break; } + // LCOV_EXCL_STOP } + std::shared_ptr aSession = + std::dynamic_pointer_cast(Model_Session::get()); if (!isError) { + myDoc = aLoaded; myDoc->SetUndoLimit(UNDO_LIMIT); + // to avoid the problem that feature is created in the current, not this, document - std::shared_ptr aSession = - std::dynamic_pointer_cast(Model_Session::get()); - aSession->setActiveDocument(anApp->getDocument(myID), false); + aSession->setActiveDocument(anApp->document(myID), false); aSession->setCheckTransactions(false); - synchronizeFeatures(false, true); + if (myObjs) + delete myObjs; + myObjs = new Model_Objects(myDoc->Main()); // synchronization is inside + myObjs->setOwner(theThis); + // update the current features status + setCurrentFeature(currentFeature(false), false); aSession->setCheckTransactions(true); + aSession->setActiveDocument(aSession->moduleDocument(), false); + // this is done in Part result "activate", so no needed here. Causes not-blue active part. + // aSession->setActiveDocument(anApp->getDocument(myID), true); + + // make sub-parts as loaded by demand + std::list aPartResults; + myObjs->allResults(ModelAPI_ResultPart::group(), aPartResults); + std::list::iterator aPartRes = aPartResults.begin(); + for(; aPartRes != aPartResults.end(); aPartRes++) { + ResultPartPtr aPart = std::dynamic_pointer_cast(*aPartRes); + if (aPart.get()) + anApp->setLoadByDemand(aPart->data()->name(), + aPart->data()->document(ModelAPI_ResultPart::DOC_REF())->docId()); + } + if (!isRoot()) { + updateShapesFromRoot(myDoc->Main(), + std::dynamic_pointer_cast(aSession->moduleDocument())->generalLabel()); + } + } else { // open failed, but new document was created to work with it: inform the model aSession->setActiveDocument(Model_Session::get()->moduleDocument(), false); - aSession->setActiveDocument(anApp->getDocument(myID), true); } return !isError; } -bool Model_Document::save(const char* theFileName, std::list& theResults) +bool Model_Document::save( + const char* theDirName, const char* theFileName, std::list& theResults) { + // if the history line is not in the end, move it to the end before save, otherwise + // problems with results restore and (the most important) naming problems will appear + // due to change evolution to SELECTION (problems in NamedShape and Name) + FeaturePtr aWasCurrent; + std::shared_ptr aSession = + std::dynamic_pointer_cast(Model_Session::get()); + if (currentFeature(false) != lastFeature()) { + aSession->setCheckTransactions(false); + aWasCurrent = currentFeature(false); + // if last is nested into something else, make this something else as last: + // otherwise it will look like edition of sub-element, so, the main will be disabled + FeaturePtr aLast = lastFeature(); + if (aLast.get()) { + CompositeFeaturePtr aMain = ModelAPI_Tools::compositeOwner(aLast); + while(aMain.get()) { + aLast = aMain; + aMain = ModelAPI_Tools::compositeOwner(aLast); + } + } + setCurrentFeature(aLast, true); + } // create a directory in the root document if it is not yet exist Handle(Model_Application) anApp = Model_Application::getApplication(); - if (this == Model_Session::get()->moduleDocument().get()) { + if (isRoot()) { #ifdef WIN32 - CreateDirectory(theFileName, NULL); + size_t aDirLen = strlen(theDirName); + std::wstring aWStr(aDirLen, L'#'); + mbstowcs(&aWStr[0], theDirName, aDirLen); + CreateDirectory(aWStr.c_str(), NULL); #else - mkdir(theFileName, 0x1ff); + mkdir(theDirName, 0x1ff); #endif } // filename in the dir is id of document inside of the given directory - TCollection_ExtendedString aPath(DocFileName(theFileName, myID)); + TCollection_ExtendedString aPath(DocFileName(theDirName, theFileName)); PCDM_StoreStatus aStatus; try { aStatus = anApp->SaveAs(myDoc, aPath); - } catch (Standard_Failure) { - Handle(Standard_Failure) aFail = Standard_Failure::Caught(); - Events_Error::send( - std::string("Exception in saving of document: ") + aFail->GetMessageString()); + } catch (Standard_Failure const& anException) { + Events_InfoMessage("Model_Document", + "Exception in saving of document: %1").arg(anException.GetMessageString()).send(); + if (aWasCurrent.get()) { // return the current feature to the initial position + setCurrentFeature(aWasCurrent, false); + aSession->setCheckTransactions(true); + } return false; } bool isDone = aStatus == PCDM_SS_OK || aStatus == PCDM_SS_No_Obj; if (!isDone) { switch (aStatus) { case PCDM_SS_DriverFailure: - Events_Error::send(std::string("Can not save document: save driver-library failure")); + Events_InfoMessage("Model_Document", + "Can not save document: save driver-library failure").send(); break; case PCDM_SS_WriteFailure: - Events_Error::send(std::string("Can not save document: file writing failure")); + Events_InfoMessage("Model_Document", "Can not save document: file writing failure").send(); break; case PCDM_SS_Failure: default: - Events_Error::send(std::string("Can not save document")); + Events_InfoMessage("Model_Document", "Can not save document").send(); break; } } - myTransactionSave = myTransactions.size(); + + if (aWasCurrent.get()) { // return the current feature to the initial position + setCurrentFeature(aWasCurrent, false); + aSession->setCheckTransactions(true); + } + + myTransactionSave = int(myTransactions.size()); if (isDone) { // save also sub-documents if any theResults.push_back(TCollection_AsciiString(aPath).ToCString()); - const std::set aSubs = subDocuments(false); - std::set::iterator aSubIter = aSubs.begin(); - for (; aSubIter != aSubs.end() && isDone; aSubIter++) { - if (anApp->isLoadByDemand(*aSubIter)) { + // iterate all result parts to find all loaded or not yet loaded documents + std::list aPartResults; + myObjs->allResults(ModelAPI_ResultPart::group(), aPartResults); + std::list::iterator aPartRes = aPartResults.begin(); + for(; aPartRes != aPartResults.end(); aPartRes++) { + ResultPartPtr aPart = std::dynamic_pointer_cast(*aPartRes); + if (!aPart->isActivated()) { // copy not-activated document that is not in the memory - std::string aDocName = *aSubIter; + std::string aDocName = aPart->data()->name(); if (!aDocName.empty()) { // just copy file TCollection_AsciiString aSubPath(DocFileName(anApp->loadPath().c_str(), aDocName)); OSD_Path aPath(aSubPath); OSD_File aFile(aPath); if (aFile.Exists()) { - TCollection_AsciiString aDestinationDir(DocFileName(theFileName, aDocName)); + TCollection_AsciiString aDestinationDir(DocFileName(theDirName, aDocName)); OSD_Path aDestination(aDestinationDir); aFile.Copy(aDestination); theResults.push_back(aDestinationDir.ToCString()); } else { - Events_Error::send( - std::string("Can not open file ") + aSubPath.ToCString() + " for saving"); + Events_InfoMessage("Model_Document", + "Can not open file %1 for saving").arg(aSubPath.ToCString()).send(); } } } else { // simply save opened document - isDone = subDoc(*aSubIter)->save(theFileName, theResults); + isDone = std::dynamic_pointer_cast(aPart->partDoc())-> + save(theDirName, aPart->data()->name().c_str(), theResults); } } } @@ -240,48 +447,38 @@ bool Model_Document::save(const char* theFileName, std::list& theRe void Model_Document::close(const bool theForever) { std::shared_ptr aPM = Model_Session::get(); - if (this != aPM->moduleDocument().get() && this == aPM->activeDocument().get()) { + if (!isRoot() && this == aPM->activeDocument().get()) { aPM->setActiveDocument(aPM->moduleDocument()); - } else if (this == aPM->moduleDocument().get()) { + } else if (isRoot()) { // erase the active document if root is closed aPM->setActiveDocument(DocumentPtr()); } // close all subs - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); - for (; aSubIter != aSubs.end(); aSubIter++) - subDoc(*aSubIter)->close(theForever); + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); + for (; aSubIter != aSubs.end(); aSubIter++) { + std::shared_ptr aSub = subDoc(*aSubIter); + if (aSub->myObjs) // if it was not closed before + aSub->close(theForever); + } - // close for thid document needs no transaction in this document + // close for this document needs no transaction in this document std::static_pointer_cast(Model_Session::get())->setCheckTransactions(false); - // delete all features of this document - std::shared_ptr aThis = - Model_Application::getApplication()->getDocument(myID); - Events_Loop* aLoop = Events_Loop::loop(); - NCollection_DataMap::Iterator aFeaturesIter(myObjs); - for(; aFeaturesIter.More(); aFeaturesIter.Next()) { - FeaturePtr aFeature = aFeaturesIter.Value(); - static Events_ID EVENT_DISP = aLoop->eventByName(EVENT_OBJECT_TO_REDISPLAY); - ModelAPI_EventCreator::get()->sendDeleted(aThis, ModelAPI_Feature::group()); - ModelAPI_EventCreator::get()->sendUpdated(aFeature, EVENT_DISP); - aFeature->eraseResults(); - if (theForever) { // issue #294: do not delete content of the document until it can be redone - aFeature->erase(); - } else { - aFeature->data()->execState(ModelAPI_StateMustBeUpdated); - } - } - if (theForever) { - myObjs.Clear(); - } - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_DELETED)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_TO_REDISPLAY)); - - // close all only if it is really asked, otherwise it can be undoed/redoed + // close all only if it is really asked, otherwise it can be undone/redone if (theForever) { + // flush everything to avoid messages with bad objects + delete myObjs; + myObjs = 0; if (myDoc->CanClose() == CDM_CCS_OK) myDoc->Close(); + mySelectionFeature.reset(); + } else { + setCurrentFeature(FeaturePtr(), false); // disables all features + // update the OB: features are disabled (on remove of Part) + Events_Loop* aLoop = Events_Loop::loop(); + static Events_ID aDeleteEvent = Events_Loop::eventByName(EVENT_OBJECT_DELETED); + aLoop->flush(aDeleteEvent); } std::static_pointer_cast(Model_Session::get())->setCheckTransactions(true); @@ -289,8 +486,10 @@ void Model_Document::close(const bool theForever) void Model_Document::startOperation() { + incrementTransactionID(); // outside of transaction in order to avoid empty transactions keeping if (myDoc->HasOpenCommand()) { // start of nested command - if (myDoc->CommitCommand()) { // commit the current: it will contain all nested after compactification + if (myDoc->CommitCommand()) { + // commit the current: it will contain all nested after compactification myTransactions.rbegin()->myOCAFNum++; // if has open command, the list is not empty } myNestedNum.push_back(0); // start of nested operation with zero transactions inside yet @@ -304,8 +503,8 @@ void Model_Document::startOperation() (*myNestedNum.rbegin())++; myRedos.clear(); // new command for all subs - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); for (; aSubIter != aSubs.end(); aSubIter++) subDoc(*aSubIter)->startOperation(); } @@ -319,49 +518,216 @@ void Model_Document::compactNested() aSumOfTransaction += myTransactions.rbegin()->myOCAFNum; myTransactions.pop_back(); } - // the latest transaction is the start of lower-level operation which startes the nested + // the latest transaction is the start of lower-level operation which starts the nested myTransactions.rbegin()->myOCAFNum += aSumOfTransaction; myNestedNum.pop_back(); } } +/// Compares the content of the given attributes, returns true if equal. +/// This method is used to avoid empty transactions when only "current" is changed +/// to some value and then comes back in this transaction, so, it compares only +/// references and Boolean and Integer Arrays for the current moment. +static bool isEqualContent(Handle(TDF_Attribute) theAttr1, Handle(TDF_Attribute) theAttr2) +{ + if (Standard_GUID::IsEqual(theAttr1->ID(), TDF_Reference::GetID())) { // reference + Handle(TDF_Reference) aRef1 = Handle(TDF_Reference)::DownCast(theAttr1); + Handle(TDF_Reference) aRef2 = Handle(TDF_Reference)::DownCast(theAttr2); + if (aRef1.IsNull() && aRef2.IsNull()) + return true; + if (aRef1.IsNull() || aRef2.IsNull()) + return false; + return aRef1->Get().IsEqual(aRef2->Get()) == Standard_True; + } else if (Standard_GUID::IsEqual(theAttr1->ID(), TDataStd_BooleanArray::GetID())) { + Handle(TDataStd_BooleanArray) anArr1 = Handle(TDataStd_BooleanArray)::DownCast(theAttr1); + Handle(TDataStd_BooleanArray) anArr2 = Handle(TDataStd_BooleanArray)::DownCast(theAttr2); + if (anArr1.IsNull() && anArr2.IsNull()) + return true; + if (anArr1.IsNull() || anArr2.IsNull()) + return false; + if (anArr1->Lower() == anArr2->Lower() && anArr1->Upper() == anArr2->Upper()) { + for(int a = anArr1->Lower(); a <= anArr1->Upper(); a++) { + if (a == 1 && // second is for display + anArr2->Label().Tag() == 1 && (anArr2->Label().Depth() == 4 || + anArr2->Label().Depth() == 6)) + continue; + if (anArr1->Value(a) != anArr2->Value(a)) + return false; + } + return true; + } + } else if (Standard_GUID::IsEqual(theAttr1->ID(), TDataStd_IntegerArray::GetID())) { + Handle(TDataStd_IntegerArray) anArr1 = Handle(TDataStd_IntegerArray)::DownCast(theAttr1); + Handle(TDataStd_IntegerArray) anArr2 = Handle(TDataStd_IntegerArray)::DownCast(theAttr2); + if (anArr1.IsNull() && anArr2.IsNull()) + return true; + if (anArr1.IsNull() || anArr2.IsNull()) + return false; + if (anArr1->Lower() == anArr2->Lower() && anArr1->Upper() == anArr2->Upper()) { + for(int a = anArr1->Lower(); a <= anArr1->Upper(); a++) + if (anArr1->Value(a) != anArr2->Value(a)) { + // avoid the transaction ID checking + if (a == 2 && anArr1->Upper() == 2 && anArr2->Label().Tag() == 1 && + (anArr2->Label().Depth() == 4 || anArr2->Label().Depth() == 6)) + continue; + return false; + } + return true; + } + } else if (Standard_GUID::IsEqual(theAttr1->ID(), TDataStd_ReferenceArray::GetID())) { + Handle(TDataStd_ReferenceArray) anArr1 = Handle(TDataStd_ReferenceArray)::DownCast(theAttr1); + Handle(TDataStd_ReferenceArray) anArr2 = Handle(TDataStd_ReferenceArray)::DownCast(theAttr2); + if (anArr1.IsNull() && anArr2.IsNull()) + return true; + if (anArr1.IsNull() || anArr2.IsNull()) + return false; + if (anArr1->Lower() == anArr2->Lower() && anArr1->Upper() == anArr2->Upper()) { + for(int a = anArr1->Lower(); a <= anArr1->Upper(); a++) + if (anArr1->Value(a) != anArr2->Value(a)) { + // avoid the transaction ID checking + if (a == 2 && anArr1->Upper() == 2 && anArr2->Label().Tag() == 1 && + (anArr2->Label().Depth() == 4 || anArr2->Label().Depth() == 6)) + continue; + return false; + } + return true; + } + } else if (Standard_GUID::IsEqual(theAttr1->ID(), TDataStd_ReferenceList::GetID())) { + Handle(TDataStd_ReferenceList) aList1 = Handle(TDataStd_ReferenceList)::DownCast(theAttr1); + Handle(TDataStd_ReferenceList) aList2= Handle(TDataStd_ReferenceList)::DownCast(theAttr2); + if (aList1.IsNull() && aList2.IsNull()) + return true; + if (aList1.IsNull() || aList2.IsNull()) + return false; + const TDF_LabelList& aLList1 = aList1->List(); + const TDF_LabelList& aLList2 = aList2->List(); + TDF_ListIteratorOfLabelList aLIter1(aLList1); + TDF_ListIteratorOfLabelList aLIter2(aLList2); + for(; aLIter1.More() && aLIter2.More(); aLIter1.Next(), aLIter2.Next()) { + if (aLIter1.Value() != aLIter2.Value()) + return false; + } + return !aLIter1.More() && !aLIter2.More(); // both lists are with the same size + } else if (Standard_GUID::IsEqual(theAttr1->ID(), TDF_TagSource::GetID())) { + return true; // it just for created and removed feature: nothing is changed + } + return false; +} + +/// Returns true if the last transaction is actually empty: modification to the same values +/// were performed only +static bool isEmptyTransaction(const Handle(TDocStd_Document)& theDoc) { + Handle(TDF_Delta) aDelta; + aDelta = theDoc->GetUndos().Last(); + TDF_LabelList aDeltaList; + aDelta->Labels(aDeltaList); // it clears list, so, use new one and then append to the result + for(TDF_ListIteratorOfLabelList aListIter(aDeltaList); aListIter.More(); aListIter.Next()) { + return false; + } + // add also label of the modified attributes + const TDF_AttributeDeltaList& anAttrs = aDelta->AttributeDeltas(); + for (TDF_ListIteratorOfAttributeDeltaList anAttr(anAttrs); anAttr.More(); anAttr.Next()) { + Handle(TDF_AttributeDelta)& anADelta = anAttr.Value(); + Handle(TDF_DeltaOnAddition) anAddition = Handle(TDF_DeltaOnAddition)::DownCast(anADelta); + if (anAddition.IsNull()) { // if the attribute was added, transaction is not empty + if (!anADelta->Label().IsNull() && !anADelta->Attribute().IsNull()) { + Handle(TDF_Attribute) aCurrentAttr; + if (anADelta->Label().FindAttribute(anADelta->Attribute()->ID(), aCurrentAttr)) { + if (isEqualContent(anADelta->Attribute(), aCurrentAttr)) { + continue; // attribute is not changed actually + } + } else + if (Standard_GUID::IsEqual(anADelta->Attribute()->ID(), TDataStd_AsciiString::GetID())) { + continue; // error message is disappeared + } + } + } + return false; + } + return true; +} + bool Model_Document::finishOperation() { bool isNestedClosed = !myDoc->HasOpenCommand() && !myNestedNum.empty(); - static std::shared_ptr aSession = + static std::shared_ptr aSession = std::static_pointer_cast(Model_Session::get()); - synchronizeBackRefs(); + + // open transaction if nested is closed to fit inside + // all synchronizeBackRefs and flushed consequences + if (isNestedClosed) { + myDoc->OpenCommand(); + } + // do it before flashes to enable and recompute nesting features correctly + if (myNestedNum.empty() || (isNestedClosed && myNestedNum.size() == 1)) { + // if all nested operations are closed, make current the higher level objects (to perform + // it in the python scripts correctly): sketch become current after creation of sub-elements + FeaturePtr aCurrent = currentFeature(false); + CompositeFeaturePtr aMain, aNext = ModelAPI_Tools::compositeOwner(aCurrent); + while(aNext.get()) { + aMain = aNext; + aNext = ModelAPI_Tools::compositeOwner(aMain); + } + if (aMain.get() && aMain != aCurrent) + setCurrentFeature(aMain, false); + } + myObjs->synchronizeBackRefs(); Events_Loop* aLoop = Events_Loop::loop(); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_CREATED)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_UPDATED)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_TO_REDISPLAY)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_TOHIDE)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_DELETED)); + static const Events_ID kCreatedEvent = aLoop->eventByName(EVENT_OBJECT_CREATED); + static const Events_ID kUpdatedEvent = aLoop->eventByName(EVENT_OBJECT_UPDATED); + static const Events_ID kRedispEvent = aLoop->eventByName(EVENT_OBJECT_TO_REDISPLAY); + static const Events_ID kDeletedEvent = aLoop->eventByName(EVENT_OBJECT_DELETED); + aLoop->flush(kCreatedEvent); + aLoop->flush(kUpdatedEvent); + aLoop->flush(kRedispEvent); + aLoop->flush(kDeletedEvent); + + if (isNestedClosed) { + if (myDoc->CommitCommand()) + myTransactions.rbegin()->myOCAFNum++; + } + // this must be here just after everything is finished but before real transaction stop // to avoid messages about modifications outside of the transaction // and to rebuild everything after all updates and creates - if (Model_Session::get()->moduleDocument().get() == this) { // once for root document - Events_Loop::loop()->autoFlush(Events_Loop::eventByName(EVENT_OBJECT_UPDATED)); + if (isRoot()) { // once for root document static std::shared_ptr aFinishMsg (new Events_Message(Events_Loop::eventByName("FinishOperation"))); Events_Loop::loop()->send(aFinishMsg); - Events_Loop::loop()->autoFlush(Events_Loop::eventByName(EVENT_OBJECT_UPDATED), false); } + + // for open of document with primitive box inside (finish transaction in initAttributes) + bool aWasActivatedFlushes = aLoop->activateFlushes(true); + while(aLoop->hasGrouppedEvent(kCreatedEvent) || aLoop->hasGrouppedEvent(kUpdatedEvent) || + aLoop->hasGrouppedEvent(kRedispEvent) || aLoop->hasGrouppedEvent(kDeletedEvent)) { + aLoop->flush(kCreatedEvent); + aLoop->flush(kUpdatedEvent); + aLoop->flush(kRedispEvent); + aLoop->flush(kDeletedEvent); + } + aLoop->activateFlushes(aWasActivatedFlushes); + // to avoid "updated" message appearance by updater //aLoop->clear(Events_Loop::eventByName(EVENT_OBJECT_UPDATED)); // finish for all subs first: to avoid nested finishing and "isOperation" calls problems inside bool aResult = false; - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); for (; aSubIter != aSubs.end(); aSubIter++) if (subDoc(*aSubIter)->finishOperation()) aResult = true; // transaction may be empty if this document was created during this transaction (create part) - if (!myTransactions.empty() && myDoc->CommitCommand()) { // if commit is successfull, just increment counters - myTransactions.rbegin()->myOCAFNum++; - aResult = true; + if (!myTransactions.empty() && myDoc->CommitCommand()) { + // if commit is successful, just increment counters + if (isEmptyTransaction(myDoc)) { // erase this transaction + myDoc->Undo(); + myDoc->ClearRedos(); + } else { + myTransactions.rbegin()->myOCAFNum++; + aResult = true; + } } if (isNestedClosed) { @@ -370,19 +736,80 @@ bool Model_Document::finishOperation() if (!aResult && !myTransactions.empty() /* it can be for just created part document */) aResult = myTransactions.rbegin()->myOCAFNum != 0; - if (!aResult && Model_Session::get()->moduleDocument().get() == this) { + if (!aResult && isRoot()) { // nothing inside in all documents, so remove this transaction from the transactions list undoInternal(true, false); - myDoc->ClearRedos(); - myRedos.clear(); } + // on finish clear redo in any case (issue 446) and for all subs (issue 408) + myDoc->ClearRedos(); + myRedos.clear(); + for (aSubIter = aSubs.begin(); aSubIter != aSubs.end(); aSubIter++) { + subDoc(*aSubIter)->myDoc->ClearRedos(); + subDoc(*aSubIter)->myRedos.clear(); + } + return aResult; } +/// Returns in theDelta labels that has been modified in the latest transaction of theDoc +static void modifiedLabels(const Handle(TDocStd_Document)& theDoc, TDF_LabelList& theDelta, + const bool isRedo = false) { + Handle(TDF_Delta) aDelta; + if (isRedo) + aDelta = theDoc->GetRedos().First(); + else + aDelta = theDoc->GetUndos().Last(); + TDF_LabelList aDeltaList; + aDelta->Labels(aDeltaList); // it clears list, so, use new one and then append to the result + for(TDF_ListIteratorOfLabelList aListIter(aDeltaList); aListIter.More(); aListIter.Next()) { + theDelta.Append(aListIter.Value()); + } + // add also label of the modified attributes + const TDF_AttributeDeltaList& anAttrs = aDelta->AttributeDeltas(); + /// named shape evolution also modifies integer on this label: exclude it + TDF_LabelMap anExcludedInt; + for (TDF_ListIteratorOfAttributeDeltaList anAttr(anAttrs); anAttr.More(); anAttr.Next()) { + if (anAttr.Value()->Attribute()->ID() == TDataStd_BooleanArray::GetID()) { + // Boolean array is used for feature auxiliary attributes only, feature args are not modified + continue; + } + if (anAttr.Value()->Attribute()->ID() == TNaming_NamedShape::GetID()) { + anExcludedInt.Add(anAttr.Value()->Label()); + // named shape evolution is changed in history update => skip them, + // they are not the features arguments + continue; + } + if (anAttr.Value()->Attribute()->ID() == TDataStd_Integer::GetID()) { + if (anExcludedInt.Contains(anAttr.Value()->Label())) + continue; + } + theDelta.Append(anAttr.Value()->Label()); + } + TDF_ListIteratorOfLabelList aDeltaIter(theDelta); + for(; aDeltaIter.More(); aDeltaIter.Next()) { + if (anExcludedInt.Contains(aDeltaIter.Value())) { + theDelta.Remove(aDeltaIter); + if (!aDeltaIter.More()) + break; + } + } +} + void Model_Document::abortOperation() { + TDF_LabelList aDeltaLabels; // labels that are updated during "abort" if (!myNestedNum.empty() && !myDoc->HasOpenCommand()) { // abort all what was done in nested compactNested(); + // store undo-delta here as undo actually does in the method later + int a, aNumTransactions = myTransactions.rbegin()->myOCAFNum; + for(a = 0; a < aNumTransactions; a++) { + modifiedLabels(myDoc, aDeltaLabels); + myDoc->Undo(); + } + for(a = 0; a < aNumTransactions; a++) { + myDoc->Redo(); + } + undoInternal(false, false); myDoc->ClearRedos(); myRedos.clear(); @@ -392,17 +819,25 @@ void Model_Document::abortOperation() if (!myNestedNum.empty()) (*myNestedNum.rbegin())--; // roll back the needed number of transactions - myDoc->AbortCommand(); - for(int a = 0; a < aNumTransactions; a++) + //myDoc->AbortCommand(); + // instead of abort, do commit and undo: to get the delta of modifications + if (myDoc->CommitCommand()) { + modifiedLabels(myDoc, aDeltaLabels); myDoc->Undo(); + } + for(int a = 0; a < aNumTransactions; a++) { + modifiedLabels(myDoc, aDeltaLabels); + myDoc->Undo(); + } myDoc->ClearRedos(); } - synchronizeFeatures(true, false); // references were not changed since transaction start - // abort for all subs - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); + // abort for all subs, flushes will be later, in the end of root abort + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); for (; aSubIter != aSubs.end(); aSubIter++) subDoc(*aSubIter)->abortOperation(); + // references may be changed because they are set in attributes on the fly + myObjs->synchronizeFeatures(aDeltaLabels, true, false, false, isRoot()); } bool Model_Document::isOperation() const @@ -413,7 +848,7 @@ bool Model_Document::isOperation() const bool Model_Document::isModified() { - // is modified if at least one operation was commited and not undoed + // is modified if at least one operation was committed and not undone return myTransactions.size() != myTransactionSave || isOperation(); } @@ -421,38 +856,71 @@ bool Model_Document::canUndo() { // issue 406 : if transaction is opened, but nothing to undo behind, can not undo int aCurrentNum = isOperation() ? 1 : 0; - if (myDoc->GetAvailableUndos() > 0 && - (myNestedNum.empty() || *myNestedNum.rbegin() - aCurrentNum > 0) && // there is something to undo in nested + if (myDoc->GetAvailableUndos() > 0 && + // there is something to undo in nested + (myNestedNum.empty() || *myNestedNum.rbegin() - aCurrentNum > 0) && myTransactions.size() - aCurrentNum > 0 /* for omitting the first useless transaction */) return true; - // check other subs contains operation that can be undoed - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); - for (; aSubIter != aSubs.end(); aSubIter++) - if (subDoc(*aSubIter)->canUndo()) - return true; + // check other subs contains operation that can be undone + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); + for (; aSubIter != aSubs.end(); aSubIter++) { + std::shared_ptr aSub = subDoc(*aSubIter); + if (aSub->myObjs) {// if it was not closed before + if (aSub->canUndo()) + return true; + } + } + return false; } void Model_Document::undoInternal(const bool theWithSubs, const bool theSynchronize) { + if (myTransactions.empty()) + return; int aNumTransactions = myTransactions.rbegin()->myOCAFNum; myRedos.push_back(*myTransactions.rbegin()); myTransactions.pop_back(); if (!myNestedNum.empty()) (*myNestedNum.rbegin())--; // roll back the needed number of transactions - for(int a = 0; a < aNumTransactions; a++) + TDF_LabelList aDeltaLabels; + for(int a = 0; a < aNumTransactions; a++) { + if (theSynchronize) + modifiedLabels(myDoc, aDeltaLabels); myDoc->Undo(); + } - if (theSynchronize) - synchronizeFeatures(true, true); + std::set aSubs; if (theWithSubs) { // undo for all subs - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); - for (; aSubIter != aSubs.end(); aSubIter++) + aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); + for (; aSubIter != aSubs.end(); aSubIter++) { + if (!subDoc(*aSubIter)->myObjs) + continue; subDoc(*aSubIter)->undoInternal(theWithSubs, theSynchronize); + } + } + // after undo of all sub-documents to avoid updates on not-modified data (issue 370) + if (theSynchronize) { + myObjs->synchronizeFeatures(aDeltaLabels, true, false, false, isRoot()); + // update the current features status + setCurrentFeature(currentFeature(false), false); + + if (theWithSubs) { + // undo for all subs + const std::set aNewSubs = subDocuments(); + std::set::iterator aNewSubIter = aNewSubs.begin(); + for (; aNewSubIter != aNewSubs.end(); aNewSubIter++) { + // synchronize only newly appeared documents + if (!subDoc(*aNewSubIter)->myObjs || aSubs.find(*aNewSubIter) != aSubs.end()) + continue; + TDF_LabelList anEmptyDeltas; + subDoc(*aNewSubIter)->myObjs->synchronizeFeatures(anEmptyDeltas, true, false, true, true); + } + } } } @@ -465,12 +933,15 @@ bool Model_Document::canRedo() { if (!myRedos.empty()) return true; - // check other subs contains operation that can be redoed - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); - for (; aSubIter != aSubs.end(); aSubIter++) + // check other subs contains operation that can be redone + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); + for (; aSubIter != aSubs.end(); aSubIter++) { + if (!subDoc(*aSubIter)->myObjs) + continue; if (subDoc(*aSubIter)->canRedo()) return true; + } return false; } @@ -481,24 +952,32 @@ void Model_Document::redo() int aNumRedos = myRedos.rbegin()->myOCAFNum; myTransactions.push_back(*myRedos.rbegin()); myRedos.pop_back(); - for(int a = 0; a < aNumRedos; a++) + TDF_LabelList aDeltaLabels; + for(int a = 0; a < aNumRedos; a++) { + modifiedLabels(myDoc, aDeltaLabels, true); myDoc->Redo(); + } - synchronizeFeatures(true, true); // redo for all subs - const std::set aSubs = subDocuments(true); - std::set::iterator aSubIter = aSubs.begin(); + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); for (; aSubIter != aSubs.end(); aSubIter++) subDoc(*aSubIter)->redo(); -} + // after redo of all sub-documents to avoid updates on not-modified data (issue 370) + myObjs->synchronizeFeatures(aDeltaLabels, true, false, false, isRoot()); + // update the current features status + setCurrentFeature(currentFeature(false), false); +} +// this is used for creation of undo/redo1-list by GUI +// LCOV_EXCL_START std::list Model_Document::undoList() const { std::list aResult; // the number of skipped current operations (on undo they will be aborted) int aSkipCurrent = isOperation() ? 1 : 0; std::list::const_reverse_iterator aTrIter = myTransactions.crbegin(); - int aNumUndo = myTransactions.size(); + int aNumUndo = int(myTransactions.size()); if (!myNestedNum.empty()) aNumUndo = *myNestedNum.rbegin(); for( ; aNumUndo > 0; aTrIter++, aNumUndo--) { @@ -517,57 +996,62 @@ std::list Model_Document::redoList() const } return aResult; } +// LCOV_EXCL_STOP void Model_Document::operationId(const std::string& theId) { myTransactions.rbegin()->myId = theId; } -/// Append to the array of references a new referenced label -static void AddToRefArray(TDF_Label& theArrayLab, TDF_Label& theReferenced) -{ - Handle(TDataStd_ReferenceArray) aRefs; - if (!theArrayLab.FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) { - aRefs = TDataStd_ReferenceArray::Set(theArrayLab, 0, 0); - aRefs->SetValue(0, theReferenced); - } else { // extend array by one more element - Handle(TDataStd_HLabelArray1) aNewArray = new TDataStd_HLabelArray1(aRefs->Lower(), - aRefs->Upper() + 1); - for (int a = aRefs->Lower(); a <= aRefs->Upper(); a++) { - aNewArray->SetValue(a, aRefs->Value(a)); - } - aNewArray->SetValue(aRefs->Upper() + 1, theReferenced); - aRefs->SetInternalArray(aNewArray); - } -} - -FeaturePtr Model_Document::addFeature(std::string theID) +FeaturePtr Model_Document::addFeature(std::string theID, const bool theMakeCurrent) { - TDF_Label anEmptyLab; - FeaturePtr anEmptyFeature; - FeaturePtr aFeature = ModelAPI_Session::get()->createFeature(theID); + std::shared_ptr aSession = + std::dynamic_pointer_cast(ModelAPI_Session::get()); + FeaturePtr aFeature = aSession->createFeature(theID, this); if (!aFeature) return aFeature; - std::shared_ptr aDocToAdd = std::dynamic_pointer_cast( - aFeature->documentToAdd()); + aFeature->init(); + Model_Document* aDocToAdd; + if (!aFeature->documentToAdd().empty()) { // use the customized document to add + if (aFeature->documentToAdd() != kind()) { // the root document by default + aDocToAdd = std::dynamic_pointer_cast(aSession->moduleDocument()).get(); + } else { + aDocToAdd = this; + } + } else { // if customized is not presented, add to "this" document + aDocToAdd = this; + } if (aFeature) { - TDF_Label aFeatureLab; - if (!aFeature->isAction()) { // do not add action to the data model - TDF_Label aFeaturesLab = aDocToAdd->featuresLabel(); - aFeatureLab = aFeaturesLab.NewChild(); - aDocToAdd->initData(aFeature, aFeatureLab, TAG_FEATURE_ARGUMENTS); - // keep the feature ID to restore document later correctly - TDataStd_Comment::Set(aFeatureLab, aFeature->getKind().c_str()); - aDocToAdd->myObjs.Bind(aFeatureLab, aFeature); - // store feature in the history of features array - if (aFeature->isInHistory()) { - AddToRefArray(aFeaturesLab, aFeatureLab); + // searching for feature after which must be added the next feature: this is the current feature + // but also all sub-features of this feature + FeaturePtr aCurrent = aDocToAdd->currentFeature(false); + bool isModified = true; + for(CompositeFeaturePtr aComp = std::dynamic_pointer_cast(aCurrent); + aComp.get() && isModified; + aComp = std::dynamic_pointer_cast(aCurrent)) { + isModified = false; + int aSubs = aComp->numberOfSubs(false); + for(int a = 0; a < aSubs; a++) { + FeaturePtr aSub = aComp->subFeature(a, false); + if (aSub && myObjs->isLater(aSub, aCurrent)) { + isModified = true; + aCurrent = aSub; + } } } + // #2861,3029: if the parameter is added, add it after parameters existing in the list + if (aCurrent.get() && + (aFeature->getKind() == "Parameter" || aFeature->getKind() == "ParametersMgr")) { + int anIndex = kUNDEFINED_FEATURE_INDEX; + for(FeaturePtr aNextFeat = myObjs->nextFeature(aCurrent, anIndex); + aNextFeat.get() && aNextFeat->getKind() == "Parameter"; + aNextFeat = myObjs->nextFeature(aCurrent, anIndex)) + aCurrent = aNextFeat; + } + aDocToAdd->myObjs->addFeature(aFeature, aCurrent); if (!aFeature->isAction()) { // do not add action to the data model - // event: feature is added - static Events_ID anEvent = Events_Loop::eventByName(EVENT_OBJECT_CREATED); - ModelAPI_EventCreator::get()->sendUpdated(aFeature, anEvent); + if (theMakeCurrent) // after all this feature stays in the document, so make it current + aDocToAdd->setCurrentFeature(aFeature, false); } else { // feature must be executed // no creation event => updater not working, problem with remove part aFeature->execute(); @@ -576,684 +1060,1058 @@ FeaturePtr Model_Document::addFeature(std::string theID) return aFeature; } -/// Appenad to the array of references a new referenced label. -/// If theIndex is not -1, removes element at thisindex, not theReferenced. -/// \returns the index of removed element -static int RemoveFromRefArray(TDF_Label theArrayLab, TDF_Label theReferenced, const int theIndex = - -1) +void Model_Document::refsToFeature(FeaturePtr theFeature, + std::set >& theRefs, const bool isSendError) { - int aResult = -1; // no returned - Handle(TDataStd_ReferenceArray) aRefs; - if (theArrayLab.FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) { - if (aRefs->Length() == 1) { // just erase an array - if ((theIndex == -1 && aRefs->Value(0) == theReferenced) || theIndex == 0) { - theArrayLab.ForgetAttribute(TDataStd_ReferenceArray::GetID()); - } - aResult = 0; - } else { // reduce the array - Handle(TDataStd_HLabelArray1) aNewArray = new TDataStd_HLabelArray1(aRefs->Lower(), - aRefs->Upper() - 1); - int aCount = aRefs->Lower(); - for (int a = aCount; a <= aRefs->Upper(); a++, aCount++) { - if ((theIndex == -1 && aRefs->Value(a) == theReferenced) || theIndex == a) { - aCount--; - aResult = a; - } else { - aNewArray->SetValue(aCount, aRefs->Value(a)); - } + myObjs->refsToFeature(theFeature, theRefs, isSendError); +} + +void Model_Document::removeFeature(FeaturePtr theFeature) +{ + myObjs->removeFeature(theFeature); + // fix for #2723: send signal that part is updated + if (!isRoot() && isOperation()) { + std::shared_ptr aRoot = + std::dynamic_pointer_cast(ModelAPI_Session::get()->moduleDocument()); + std::list allParts; + aRoot->objects()->allResults(ModelAPI_ResultPart::group(), allParts); + std::list::iterator aParts = allParts.begin(); + for(; aParts != allParts.end(); aParts++) { + ResultPartPtr aPart = std::dynamic_pointer_cast(*aParts); + if (aPart->partDoc().get() == this) { + static Events_ID anEvent = Events_Loop::eventByName(EVENT_OBJECT_UPDATED); + ModelAPI_EventCreator::get()->sendUpdated(aRoot->feature(aPart), anEvent); + break; } - aRefs->SetInternalArray(aNewArray); } } - return aResult; } -void Model_Document::refsToFeature(FeaturePtr theFeature, - std::set >& theRefs, - const bool isSendError) -{ - // check the feature: it must have no depended objects on it - // the dependencies can be in the feature results - std::list::const_iterator aResIter = theFeature->results().cbegin(); - for(; aResIter != theFeature->results().cend(); aResIter++) { - ResultPtr aResult = (*aResIter); - std::shared_ptr aData = - std::dynamic_pointer_cast(aResult->data()); - if (aData.get() != NULL) { - const std::set& aRefs = aData->refsToMe(); - std::set::const_iterator aRefIt = aRefs.begin(), aRefLast = aRefs.end(); - for(; aRefIt != aRefLast; aRefIt++) { - FeaturePtr aFeature = std::dynamic_pointer_cast((*aRefIt)->owner()); - if (aFeature.get() != NULL) - theRefs.insert(aFeature); - } - } +// recursive function to check if theSub is a child of theMain composite feature +// through all the hierarchy of parents +static bool isSub(const CompositeFeaturePtr theMain, const FeaturePtr theSub) { + CompositeFeaturePtr aParent = ModelAPI_Tools::compositeOwner(theSub); + if (!aParent.get()) + return false; + if (aParent == theMain) + return true; + return isSub(theMain, aParent); +} + +void Model_Document::moveFeature(FeaturePtr theMoved, FeaturePtr theAfterThis) +{ + bool aCurrentUp = theMoved == currentFeature(false); + if (aCurrentUp) { + setCurrentFeatureUp(); } - // the dependencies can be in the feature itself - std::shared_ptr aData = - std::dynamic_pointer_cast(theFeature->data()); - if (aData && !aData->refsToMe().empty()) { - const std::set& aRefs = aData->refsToMe(); - std::set::const_iterator aRefIt = aRefs.begin(), aRefLast = aRefs.end(); - for(; aRefIt != aRefLast; aRefIt++) { - FeaturePtr aFeature = std::dynamic_pointer_cast((*aRefIt)->owner()); - if (aFeature.get() != NULL) - theRefs.insert(aFeature); - } + // if user adds after high-level feature with nested, + // add it after all nested (otherwise the nested will be disabled) + CompositeFeaturePtr aCompositeAfter = + std::dynamic_pointer_cast(theAfterThis); + FeaturePtr anAfterThisSub = theAfterThis; + if (aCompositeAfter.get()) { + FeaturePtr aSub = aCompositeAfter; + int anIndex = kUNDEFINED_FEATURE_INDEX; + do { + FeaturePtr aNext = myObjs->nextFeature(aSub, anIndex); + if (!isSub(aCompositeAfter, aNext)) { + anAfterThisSub = aSub; + break; + } + aSub = aNext; + } while (aSub.get()); } - if (!theRefs.empty() && isSendError) { - Events_Error::send( - "Feature '" + theFeature->data()->name() + "' is used and can not be deleted"); + myObjs->moveFeature(theMoved, anAfterThisSub); + if (aCurrentUp) { // make the moved feature enabled or disabled due to the real status + setCurrentFeature(currentFeature(false), false); + } else if (theAfterThis == currentFeature(false) || anAfterThisSub == currentFeature(false)) { + // must be after move to make enabled all features which are before theMoved + setCurrentFeature(theMoved, true); } } -void Model_Document::removeFeature(FeaturePtr theFeature/*, const bool theCheck*/) +void Model_Document::updateHistory(const std::shared_ptr theObject) { - std::shared_ptr aData = std::static_pointer_cast(theFeature->data()); - if (aData) { - TDF_Label aFeatureLabel = aData->label().Father(); - if (myObjs.IsBound(aFeatureLabel)) - myObjs.UnBind(aFeatureLabel); - else - return; // not found feature => do not remove - - // checking that the sub-element of composite feature is removed: if yes, inform the owner - std::set > aRefs; - refsToFeature(theFeature, aRefs, false); - std::set >::iterator aRefIter = aRefs.begin(); - for(; aRefIter != aRefs.end(); aRefIter++) { - std::shared_ptr aComposite = - std::dynamic_pointer_cast(*aRefIter); - if (aComposite.get()) { - aComposite->removeFeature(theFeature); - } - } + if (myObjs) + myObjs->updateHistory(theObject); +} - // erase fields - theFeature->erase(); - static Events_ID EVENT_DISP = Events_Loop::loop()->eventByName(EVENT_OBJECT_TO_REDISPLAY); - ModelAPI_EventCreator::get()->sendUpdated(theFeature, EVENT_DISP); - // erase all attributes under the label of feature - aFeatureLabel.ForgetAllAttributes(); - // remove it from the references array - if (theFeature->isInHistory()) { - RemoveFromRefArray(featuresLabel(), aFeatureLabel); +void Model_Document::updateHistory(const std::string theGroup) +{ + if (myObjs) + myObjs->updateHistory(theGroup); +} + +const std::set Model_Document::subDocuments() const +{ + std::set aResult; + std::list aPartResults; + myObjs->allResults(ModelAPI_ResultPart::group(), aPartResults); + std::list::iterator aPartRes = aPartResults.begin(); + for(; aPartRes != aPartResults.end(); aPartRes++) { + ResultPartPtr aPart = std::dynamic_pointer_cast(*aPartRes); + if (aPart && aPart->isActivated()) { + aResult.insert(aPart->original()->partDoc()->id()); } - // event: feature is deleted - ModelAPI_EventCreator::get()->sendDeleted(theFeature->document(), ModelAPI_Feature::group()); } + return aResult; } -FeaturePtr Model_Document::feature(TDF_Label& theLabel) const +std::shared_ptr Model_Document::subDoc(int theDocID) { - if (myObjs.IsBound(theLabel)) - return myObjs.Find(theLabel); - return FeaturePtr(); // not found + // just store sub-document identifier here to manage it later + return std::dynamic_pointer_cast( + Model_Application::getApplication()->document(theDocID)); } -ObjectPtr Model_Document::object(TDF_Label theLabel) +ObjectPtr Model_Document::object(const std::string& theGroupID, + const int theIndex, + const bool theAllowFolder) { - // try feature by label - FeaturePtr aFeature = feature(theLabel); - if (aFeature) - return feature(theLabel); - TDF_Label aFeatureLabel = theLabel.Father().Father(); // let's suppose it is result - aFeature = feature(aFeatureLabel); - if (aFeature) { - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.cbegin(); - for (; aRIter != aResults.cend(); aRIter++) { - std::shared_ptr aResData = std::dynamic_pointer_cast( - (*aRIter)->data()); - if (aResData->label().Father().IsEqual(theLabel)) - return *aRIter; - } - } - return FeaturePtr(); // not found + return myObjs->object(theGroupID, theIndex, theAllowFolder); } -std::shared_ptr Model_Document::subDocument(std::string theDocID) +std::shared_ptr Model_Document::objectByName( + const std::string& theGroupID, const std::string& theName) { - return Model_Application::getApplication()->getDocument(theDocID); + return myObjs->objectByName(theGroupID, theName); } -const std::set Model_Document::subDocuments(const bool theActivatedOnly) const +const int Model_Document::index(std::shared_ptr theObject, + const bool theAllowFolder) { - std::set aResult; - // comment must be in any feature: it is kind - int anIndex = 0; - TDF_ChildIDIterator aLabIter(featuresLabel(), TDataStd_Comment::GetID()); - for (; aLabIter.More(); aLabIter.Next()) { - TDF_Label aFLabel = aLabIter.Value()->Label(); - FeaturePtr aFeature = feature(aFLabel); - if (aFeature.get()) { // if document is closed the feature may be not in myObjs map - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.begin(); - for (; aRIter != aResults.cend(); aRIter++) { - if ((*aRIter)->groupName() != ModelAPI_ResultPart::group()) continue; - if ((*aRIter)->isInHistory()) { - ResultPartPtr aPart = std::dynamic_pointer_cast(*aRIter); - if (aPart && (!theActivatedOnly || aPart->isActivated())) - aResult.insert(aPart->data()->name()); - } - } - } - } - return aResult; + return myObjs->index(theObject, theAllowFolder); } -std::shared_ptr Model_Document::subDoc(std::string theDocID) +int Model_Document::size(const std::string& theGroupID, const bool theAllowFolder) { - // just store sub-document identifier here to manage it later - return std::dynamic_pointer_cast( - Model_Application::getApplication()->getDocument(theDocID)); + if (myObjs == 0) // may be on close + return 0; + return myObjs->size(theGroupID, theAllowFolder); } -ObjectPtr Model_Document::object(const std::string& theGroupID, const int theIndex, - const bool theHidden) +std::shared_ptr Model_Document::parent( + const std::shared_ptr theChild) { - if (theGroupID == ModelAPI_Feature::group()) { - if (theHidden) { - int anIndex = 0; - TDF_ChildIDIterator aLabIter(featuresLabel(), TDataStd_Comment::GetID()); - for (; aLabIter.More(); aLabIter.Next()) { - if (theIndex == anIndex) { - TDF_Label aFLabel = aLabIter.Value()->Label(); - return feature(aFLabel); - } - anIndex++; - } - } else { - Handle(TDataStd_ReferenceArray) aRefs; - if (!featuresLabel().FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) - return ObjectPtr(); - if (aRefs->Lower() > theIndex || aRefs->Upper() < theIndex) - return ObjectPtr(); - TDF_Label aFeatureLabel = aRefs->Value(theIndex); - return feature(aFeatureLabel); - } - } else { - // comment must be in any feature: it is kind - int anIndex = 0; - TDF_ChildIDIterator aLabIter(featuresLabel(), TDataStd_Comment::GetID()); - for (; aLabIter.More(); aLabIter.Next()) { - TDF_Label aFLabel = aLabIter.Value()->Label(); - FeaturePtr aFeature = feature(aFLabel); - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.begin(); - for (; aRIter != aResults.cend(); aRIter++) { - if ((*aRIter)->groupName() != theGroupID) continue; - bool isIn = theHidden && (*aRIter)->isInHistory(); - if (!isIn && (*aRIter)->isInHistory()) { // check that there is nobody references this result - isIn = !(*aRIter)->isConcealed(); - } - if (isIn) { - if (anIndex == theIndex) - return *aRIter; - anIndex++; - } + if(myObjs == 0) // may be on close + return ObjectPtr(); + return myObjs->parent(theChild); +} + +std::shared_ptr Model_Document::currentFeature(const bool theVisible) +{ + if (!myObjs) // on close document feature destruction it may call this method + return std::shared_ptr(); + TDF_Label aRefLab = generalLabel().FindChild(TAG_CURRENT_FEATURE); + Handle(TDF_Reference) aRef; + if (aRefLab.FindAttribute(TDF_Reference::GetID(), aRef)) { + TDF_Label aLab = aRef->Get(); + FeaturePtr aResult = myObjs->feature(aLab); + if (theVisible) { // get nearest visible (in history) going up + int anIndex = kUNDEFINED_FEATURE_INDEX; + while(aResult.get() && !aResult->isInHistory()) { + aResult = myObjs->nextFeature(aResult, anIndex, true); } } + return aResult; } - // not found - return ObjectPtr(); + return std::shared_ptr(); // null feature means the higher than first } -int Model_Document::size(const std::string& theGroupID, const bool theHidden) +void Model_Document::setCurrentFeature( + std::shared_ptr theCurrent, const bool theVisible) { - int aResult = 0; - if (theGroupID == ModelAPI_Feature::group()) { - if (theHidden) { - return myObjs.Size(); - } else { - Handle(TDataStd_ReferenceArray) aRefs; - if (featuresLabel().FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) - return aRefs->Length(); + if (myIsSetCurrentFeature) + return; + myIsSetCurrentFeature = true; + // blocks the flush signals to avoid each objects visualization in the viewer + // they should not be shown once after all modifications are performed + Events_Loop* aLoop = Events_Loop::loop(); + bool isActive = aLoop->activateFlushes(false); + + TDF_Label aRefLab = generalLabel().FindChild(TAG_CURRENT_FEATURE); + CompositeFeaturePtr aMain; // main feature that may nest the new current + std::set anOwners; // composites that contain theCurrent (with any level of nesting) + if (theCurrent.get()) { + aMain = std::dynamic_pointer_cast(theCurrent); + CompositeFeaturePtr anOwner = ModelAPI_Tools::compositeOwner(theCurrent); + while(anOwner.get()) { + if (!aMain.get()) { + aMain = anOwner; + } + anOwners.insert(anOwner); + anOwner = ModelAPI_Tools::compositeOwner(anOwner); } - } else { - // comment must be in any feature: it is kind - TDF_ChildIDIterator aLabIter(featuresLabel(), TDataStd_Comment::GetID()); - for (; aLabIter.More(); aLabIter.Next()) { - TDF_Label aFLabel = aLabIter.Value()->Label(); - FeaturePtr aFeature = feature(aFLabel); - if (!aFeature) // may be on close - continue; - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.begin(); - for (; aRIter != aResults.cend(); aRIter++) { - if ((*aRIter)->groupName() != theGroupID) continue; - bool isIn = theHidden; - if (!isIn && (*aRIter)->isInHistory()) { // check that there is nobody references this result - isIn = !(*aRIter)->isConcealed(); - } - if (isIn) - aResult++; + } + + if (theVisible && !theCurrent.get()) { + // needed to avoid disabling of PartSet initial constructions + int anIndex = kUNDEFINED_FEATURE_INDEX; + FeaturePtr aNext = + theCurrent.get() ? myObjs->nextFeature(theCurrent, anIndex, false) : myObjs->firstFeature(); + for (; aNext.get(); aNext = myObjs->nextFeature(theCurrent, anIndex, false)) { + if (aNext->isInHistory()) { + break; // next in history is not needed + } else { // next not in history is good for making current + theCurrent = aNext; } } } - // group is not found - return aResult; -} + if (theVisible) { // make RemoveResults feature be active even it is performed after the current + int anIndex = kUNDEFINED_FEATURE_INDEX; + FeaturePtr aNext = + theCurrent.get() ? myObjs->nextFeature(theCurrent, anIndex, false) : myObjs->firstFeature(); + for (; aNext.get(); aNext = myObjs->nextFeature(theCurrent, anIndex, false)) { + if (aNext->isInHistory()) { + break; // next in history is not needed + } else if (aNext->getKind() == "RemoveResults"){ + theCurrent = aNext; + } + } + } + if (theCurrent.get()) { + std::shared_ptr aData = std::static_pointer_cast(theCurrent->data()); + if (!aData.get() || !aData->isValid()) { + aLoop->activateFlushes(isActive); + myIsSetCurrentFeature = false; + return; + } + TDF_Label aFeatureLabel = aData->label().Father(); + + Handle(TDF_Reference) aRef; + if (aRefLab.FindAttribute(TDF_Reference::GetID(), aRef)) { + aRef->Set(aFeatureLabel); + } else { + aRef = TDF_Reference::Set(aRefLab, aFeatureLabel); + } + } else { // remove reference for the null feature + aRefLab.ForgetAttribute(TDF_Reference::GetID()); + } + // make all features after this feature disabled in reversed order + // (to remove results without dependencies) + static Events_ID aRedispEvent = aLoop->eventByName(EVENT_OBJECT_TO_REDISPLAY); + + bool aPassed = false; // flag that the current object is already passed in cycle + FeaturePtr anIter = myObjs->lastFeature(); + bool aWasChanged = false; + bool isCurrentParameter = theCurrent.get() && theCurrent->getKind() == "Parameter"; + int anIndex = kUNDEFINED_FEATURE_INDEX; + for(; anIter.get(); anIter = myObjs->nextFeature(anIter, anIndex, true)) { + // check this before passed become enabled: the current feature is enabled! + if (anIter == theCurrent) aPassed = true; + + bool aDisabledFlag = !aPassed; + if (aMain.get()) { + if (isSub(aMain, anIter)) // sub-elements of not-disabled feature are not disabled + aDisabledFlag = false; + else if (anOwners.find(anIter) != anOwners.end()) + // disable the higher-level feature if the nested is the current + if (aMain->getKind() != "Import") // exception for the import XAO feature with Group (2430) + aDisabledFlag = true; + } + + if (anIter->getKind() == "Parameter") { + // parameters are always out of the history of features, but not parameters + // due to the issue 1491 all parameters are kept enabled any time + //if (!isCurrentParameter) + aDisabledFlag = false; + } else if (isCurrentParameter) { + // if parameter is active, all other features become enabled (issue 1307) + aDisabledFlag = false; + } -TDF_Label Model_Document::featuresLabel() const -{ - return myDoc->Main().FindChild(TAG_OBJECTS); -} - -void Model_Document::setUniqueName(FeaturePtr theFeature) -{ - if (!theFeature->data()->name().empty()) - return; // not needed, name is already defined - std::string aName; // result - // first count all objects of such kind to start with index = count + 1 - int aNumObjects = 0; - NCollection_DataMap::Iterator aFIter(myObjs); - for (; aFIter.More(); aFIter.Next()) { - if (aFIter.Value()->getKind() == theFeature->getKind()) - aNumObjects++; - } - // generate candidate name - std::stringstream aNameStream; - aNameStream << theFeature->getKind() << "_" << aNumObjects + 1; - aName = aNameStream.str(); - // check this is unique, if not, increase index by 1 - for (aFIter.Initialize(myObjs); aFIter.More();) { - FeaturePtr aFeature = aFIter.Value(); - bool isSameName = aFeature->data()->name() == aName; - if (!isSameName) { // check also results to avoid same results names (actual for Parts) - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.begin(); - for (; aRIter != aResults.cend(); aRIter++) { - isSameName = (*aRIter)->data()->name() == aName; + if (anIter->setDisabled(aDisabledFlag)) { + static Events_ID anUpdateEvent = aLoop->eventByName(EVENT_OBJECT_UPDATED); + // state of feature is changed => so inform that it must be updated if it has such state + if (!aDisabledFlag && + (anIter->data()->execState() == ModelAPI_StateMustBeUpdated || + anIter->data()->execState() == ModelAPI_StateInvalidArgument)) + ModelAPI_EventCreator::get()->sendUpdated(anIter, anUpdateEvent); + // flush is in the end of this method + ModelAPI_EventCreator::get()->sendUpdated(anIter, aRedispEvent /*, false*/); + aWasChanged = true; + } + // update for everyone concealment flag immediately: on edit feature in the middle of history + if (aWasChanged) { + std::list aResults; + ModelAPI_Tools::allResults(anIter, aResults); + std::list::const_iterator aRes = aResults.begin(); + for(; aRes != aResults.end(); aRes++) { + if ((*aRes).get() && (*aRes)->data()->isValid() && !(*aRes)->isDisabled()) + std::dynamic_pointer_cast((*aRes)->data())->updateConcealmentFlag(); + } + // update the concealment status for display in isConcealed of ResultBody + for(aRes = aResults.begin(); aRes != aResults.end(); aRes++) { + if ((*aRes).get() && (*aRes)->data()->isValid() && !(*aRes)->isDisabled()) + (*aRes)->isConcealed(); } } - if (isSameName) { - aNumObjects++; - std::stringstream aNameStream; - aNameStream << theFeature->getKind() << "_" << aNumObjects + 1; - aName = aNameStream.str(); - // reinitialize iterator to make sure a new name is unique - aFIter.Initialize(myObjs); - } else - aFIter.Next(); - } - theFeature->data()->setName(aName); -} - -void Model_Document::initData(ObjectPtr theObj, TDF_Label theLab, const int theTag) -{ - std::shared_ptr aThis = Model_Application::getApplication()->getDocument( - myID); - std::shared_ptr aData(new Model_Data); - aData->setLabel(theLab.FindChild(theTag)); - aData->setObject(theObj); - theObj->setDoc(aThis); - theObj->setData(aData); - FeaturePtr aFeature = std::dynamic_pointer_cast(theObj); - if (aFeature) { - setUniqueName(aFeature); // must be before "initAttributes" because duplicate part uses name } - theObj->initAttributes(); + myIsSetCurrentFeature = false; + // unblock the flush signals and up them after this + aLoop->activateFlushes(isActive); } -void Model_Document::synchronizeFeatures(const bool theMarkUpdated, const bool theUpdateReferences) +void Model_Document::setCurrentFeatureUp() { - std::shared_ptr aThis = - Model_Application::getApplication()->getDocument(myID); - // after all updates, sends a message that groups of features were created or updated - Events_Loop* aLoop = Events_Loop::loop(); - aLoop->activateFlushes(false); - - // update all objects by checking are they of labels or not - std::set aNewFeatures, aKeptFeatures; - TDF_ChildIDIterator aLabIter(featuresLabel(), TDataStd_Comment::GetID()); - for (; aLabIter.More(); aLabIter.Next()) { - TDF_Label aFeatureLabel = aLabIter.Value()->Label(); - FeaturePtr aFeature; - if (!myObjs.IsBound(aFeatureLabel)) { // a new feature is inserted - // create a feature - aFeature = ModelAPI_Session::get()->createFeature( - TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast(aLabIter.Value())->Get()) - .ToCString()); - if (!aFeature) { // somethig is wrong, most probably, the opened document has invalid structure - Events_Error::send("Invalid type of object in the document"); - aLabIter.Value()->Label().ForgetAllAttributes(); - continue; - } - // this must be before "setData" to redo the sketch line correctly - myObjs.Bind(aFeatureLabel, aFeature); - aNewFeatures.insert(aFeature); - initData(aFeature, aFeatureLabel, TAG_FEATURE_ARGUMENTS); - - // event: model is updated - static Events_ID anEvent = Events_Loop::eventByName(EVENT_OBJECT_CREATED); - ModelAPI_EventCreator::get()->sendUpdated(aFeature, anEvent); - } else { // nothing is changed, both iterators are incremented - aFeature = myObjs.Find(aFeatureLabel); - aKeptFeatures.insert(aFeature); - if (theMarkUpdated) { - static Events_ID anEvent = Events_Loop::eventByName(EVENT_OBJECT_UPDATED); - ModelAPI_EventCreator::get()->sendUpdated(aFeature, anEvent); - } + // on remove just go up for minimum step: highlight external objects in sketch causes + // problems if it is true: here and in "setCurrentFeature" + FeaturePtr aCurrent = currentFeature(false); + if (aCurrent.get()) { // if not, do nothing because null is the upper + int anIndex = kUNDEFINED_FEATURE_INDEX; + FeaturePtr aPrev = myObjs->nextFeature(aCurrent, anIndex, true); + // make the higher level composite as current (sketch becomes disabled if line is enabled) + if (aPrev.get()) { + FeaturePtr aComp = ModelAPI_Tools::compositeOwner(aPrev); + // without cycle (issue 1555): otherwise extrusion fuse + // will be enabled and displayed when inside sketch + if (aComp.get()) + aPrev = aComp; } + // do not flush: it is called only on remove, it will be flushed in the end of transaction + setCurrentFeature(aPrev, false); } - // update results of thefeatures (after features created because they may be connected, like sketch and sub elements) - std::list aComposites; // composites must be updated after their subs (issue 360) - TDF_ChildIDIterator aLabIter2(featuresLabel(), TDataStd_Comment::GetID()); - for (; aLabIter2.More(); aLabIter2.Next()) { - TDF_Label aFeatureLabel = aLabIter2.Value()->Label(); - if (myObjs.IsBound(aFeatureLabel)) { // a new feature is inserted - FeaturePtr aFeature = myObjs.Find(aFeatureLabel); - if (std::dynamic_pointer_cast(aFeature).get()) - aComposites.push_back(aFeature); - updateResults(aFeature); +} + +TDF_Label Model_Document::generalLabel() const +{ + return myDoc->Main().FindChild(TAG_GENERAL); +} + +std::shared_ptr Model_Document::createConstruction( + const std::shared_ptr& theFeatureData, const int theIndex) +{ + return myObjs->createConstruction(theFeatureData, theIndex); +} + +std::shared_ptr Model_Document::createBody( + const std::shared_ptr& theFeatureData, const int theIndex) +{ + return myObjs->createBody(theFeatureData, theIndex); +} + +std::shared_ptr Model_Document::createPart( + const std::shared_ptr& theFeatureData, const int theIndex) +{ + return myObjs->createPart(theFeatureData, theIndex); +} + +std::shared_ptr Model_Document::copyPart( + const std::shared_ptr& theOrigin, + const std::shared_ptr& theFeatureData, const int theIndex) +{ + return myObjs->copyPart(theOrigin, theFeatureData, theIndex); +} + +std::shared_ptr Model_Document::createGroup( + const std::shared_ptr& theFeatureData, const int theIndex) +{ + return myObjs->createGroup(theFeatureData, theIndex); +} + +std::shared_ptr Model_Document::createField( + const std::shared_ptr& theFeatureData, const int theIndex) +{ + return myObjs->createField(theFeatureData, theIndex); +} + +std::shared_ptr Model_Document::createParameter( + const std::shared_ptr& theFeatureData, const int theIndex) +{ + return myObjs->createParameter(theFeatureData, theIndex); +} + +std::shared_ptr Model_Document::addFolder( + std::shared_ptr theAddBefore) +{ + return myObjs->createFolder(theAddBefore); +} + +void Model_Document::removeFolder(std::shared_ptr theFolder) +{ + if (theFolder) + myObjs->removeFolder(theFolder); +} + +std::shared_ptr Model_Document::findFolderAbove( + const std::list >& theFeatures) +{ + return myObjs->findFolder(theFeatures, false); +} + +std::shared_ptr Model_Document::findFolderBelow( + const std::list >& theFeatures) +{ + return myObjs->findFolder(theFeatures, true); +} + +std::shared_ptr Model_Document::findContainingFolder( + const std::shared_ptr& theFeature, + int& theIndexInFolder) +{ + return myObjs->findContainingFolder(theFeature, theIndexInFolder); +} + +bool Model_Document::moveToFolder( + const std::list >& theFeatures, + const std::shared_ptr& theFolder) +{ + return myObjs->moveToFolder(theFeatures, theFolder); +} + +bool Model_Document::removeFromFolder( + const std::list >& theFeatures, + const bool theBefore) +{ + return myObjs->removeFromFolder(theFeatures, theBefore); +} + +std::shared_ptr Model_Document::feature( + const std::shared_ptr& theResult) +{ + if (myObjs == 0) // may be on close + return std::shared_ptr(); + return myObjs->feature(theResult); +} + +FeaturePtr Model_Document::featureByLab(const TDF_Label& theLab) { + TDF_Label aCurrentLab = theLab; + while(aCurrentLab.Depth() > 3) + aCurrentLab = aCurrentLab.Father(); + return myObjs->feature(aCurrentLab); +} + +ResultPtr Model_Document::resultByLab(const TDF_Label& theLab) +{ + TDF_Label aCurrentLab = theLab; + while(aCurrentLab.Depth() > 3) { + ObjectPtr aResultObj = myObjs->object(aCurrentLab); + if (aResultObj.get()) { + return std::dynamic_pointer_cast(aResultObj); // this may be null if feature } + aCurrentLab = aCurrentLab.Father(); } - std::list::iterator aComposite = aComposites.begin(); - for(; aComposite != aComposites.end(); aComposite++) { - updateResults(*aComposite); - } - - // check all features are checked: if not => it was removed - NCollection_DataMap::Iterator aFIter(myObjs); - while (aFIter.More()) { - if (aKeptFeatures.find(aFIter.Value()) == aKeptFeatures.end() - && aNewFeatures.find(aFIter.Value()) == aNewFeatures.end()) { - FeaturePtr aFeature = aFIter.Value(); - // event: model is updated - //if (aFeature->isInHistory()) { - ModelAPI_EventCreator::get()->sendDeleted(aThis, ModelAPI_Feature::group()); - //} - // results of this feature must be redisplayed (hided) - static Events_ID EVENT_DISP = aLoop->eventByName(EVENT_OBJECT_TO_REDISPLAY); - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.begin(); - // redisplay also removed feature (used for sketch and AISObject) - ModelAPI_EventCreator::get()->sendUpdated(aFeature, EVENT_DISP); - aFeature->erase(); - // unbind after the "erase" call: on abort sketch is removes sub-objects that corrupts aFIter - myObjs.UnBind(aFIter.Key()); - // reinitialize iterator because unbind may corrupt the previous order in the map - aFIter.Initialize(myObjs); - } else - aFIter.Next(); - } - - if (theUpdateReferences) { - synchronizeBackRefs(); - } - - myExecuteFeatures = false; - aLoop->activateFlushes(true); - - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_CREATED)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_DELETED)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_UPDATED)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_TO_REDISPLAY)); - aLoop->flush(Events_Loop::eventByName(EVENT_OBJECT_TOHIDE)); - myExecuteFeatures = true; + return ResultPtr(); // not found } -void Model_Document::synchronizeBackRefs() -{ - std::shared_ptr aThis = - Model_Application::getApplication()->getDocument(myID); - // keeps the concealed flags of result to catch the change and create created/deleted events - std::list > aConcealed; - // first cycle: erase all data about back-references - NCollection_DataMap::Iterator aFeatures(myObjs); - for(; aFeatures.More(); aFeatures.Next()) { - FeaturePtr aFeature = aFeatures.Value(); - std::shared_ptr aFData = - std::dynamic_pointer_cast(aFeature->data()); - if (aFData) { - aFData->eraseBackReferences(); +void Model_Document::addNamingName(const TDF_Label theLabel, std::string theName) +{ + std::map >::iterator aFind = myNamingNames.find(theName); + + if (aFind != myNamingNames.end()) { // to avoid duplicate-labels + // to keep correct order in spite of history line management + std::list::iterator anAddAfterThis = aFind->second.end(); + FeaturePtr anAddedFeature = featureByLab(theLabel); + std::list::iterator aLabIter = aFind->second.begin(); + while(aLabIter != aFind->second.end()) { + if (theLabel.IsEqual(*aLabIter)) { + std::list::iterator aTmpIter = aLabIter; + aLabIter++; + aFind->second.erase(aTmpIter); + } else { + FeaturePtr aCurFeature = featureByLab(*aLabIter); + if (aCurFeature.get() && anAddedFeature.get() && + myObjs->isLater(anAddedFeature, aCurFeature)) + anAddAfterThis = aLabIter; + + aLabIter++; + } } - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.begin(); - for (; aRIter != aResults.cend(); aRIter++) { - std::shared_ptr aResData = - std::dynamic_pointer_cast((*aRIter)->data()); - if (aResData) { - aConcealed.push_back(std::pair(*aRIter, (*aRIter)->isConcealed())); - aResData->eraseBackReferences(); + if (anAddAfterThis != aFind->second.end()) { + anAddAfterThis++; + if (anAddAfterThis != aFind->second.end()) { + myNamingNames[theName].insert(anAddAfterThis, theLabel); // inserts before anAddAfterThis + return; } } } + myNamingNames[theName].push_back(theLabel); +} - // second cycle: set new back-references: only features may have reference, iterate only them - ModelAPI_ValidatorsFactory* aValidators = ModelAPI_Session::get()->validators(); - for(aFeatures.Initialize(myObjs); aFeatures.More(); aFeatures.Next()) { - FeaturePtr aFeature = aFeatures.Value(); - std::shared_ptr aFData = - std::dynamic_pointer_cast(aFeature->data()); - if (aFData) { - std::list > > aRefs; - aFData->referencesToObjects(aRefs); - std::list > >::iterator aRefsIter = aRefs.begin(); - for(; aRefsIter != aRefs.end(); aRefsIter++) { - std::list::iterator aRefTo = aRefsIter->second.begin(); - for(; aRefTo != aRefsIter->second.end(); aRefTo++) { - if (*aRefTo) { - std::shared_ptr aRefData = - std::dynamic_pointer_cast((*aRefTo)->data()); - aRefData->addBackReference(aFeature, aRefsIter->first); // here the Concealed flag is updated +void Model_Document::changeNamingName(const std::string theOldName, + const std::string theNewName, + const TDF_Label& theLabel) +{ + std::map >::iterator aFind = myNamingNames.find(theOldName); + if (aFind != myNamingNames.end()) { + std::list::iterator aLabIter = aFind->second.begin(); + for(; aLabIter != aFind->second.end(); aLabIter++) { + if (theLabel.IsEqual(*aLabIter)) { // found the label + myNamingNames[theNewName].push_back(theLabel); + if (aFind->second.size() == 1) { // only one element, so, just change the name + myNamingNames.erase(theOldName); + } else { // remove from the list + aFind->second.erase(aLabIter); + } + // check the sketch vertex name located under renamed sketch line + TDF_ChildIDIterator aChild(theLabel, TDataStd_Name::GetID()); + for(; aChild.More(); aChild.Next()) { + Handle(TDataStd_Name) aSubName = Handle(TDataStd_Name)::DownCast(aChild.Value()); + std::string aName = TCollection_AsciiString(aSubName->Get()).ToCString(); + if (aName.find(theOldName) == 0) { // started from parent name + std::string aNewSubName = theNewName + aName.substr(theOldName.size()); + changeNamingName(aName, aNewSubName, aSubName->Label()); + aSubName->Set(aNewSubName.c_str()); } } + return; } } } - std::list >::iterator aCIter = aConcealed.begin(); - for(; aCIter != aConcealed.end(); aCIter++) { - if (aCIter->first->isConcealed() != aCIter->second) { // somethign is changed => produce event - if (aCIter->second) { // was concealed become not => creation event - static Events_ID anEvent = Events_Loop::eventByName(EVENT_OBJECT_CREATED); - ModelAPI_EventCreator::get()->sendUpdated(aCIter->first, anEvent); - } else { // was not concealed become concealed => delete event - ModelAPI_EventCreator::get()->sendDeleted(aThis, aCIter->first->groupName()); - // redisplay for the viewer (it must be disappeared also) - static Events_ID EVENT_DISP = - Events_Loop::loop()->eventByName(EVENT_OBJECT_TO_REDISPLAY); - ModelAPI_EventCreator::get()->sendUpdated(aCIter->first, EVENT_DISP); +} + +TDF_Label Model_Document::findNamingName(std::string theName, ResultPtr theContext) +{ + std::map >::iterator aFind = myNamingNames.find(theName); + if (aFind != myNamingNames.end()) { + std::list::reverse_iterator aLabIter = aFind->second.rbegin(); + for(; aLabIter != aFind->second.rend(); aLabIter++) { + if (theContext.get()) { + // context is defined and not like this, so, skip + if (theContext == myObjs->object(aLabIter->Father())) + return *aLabIter; + } } + return *(aFind->second.rbegin()); // no more variants, so, return the last + } + // not found exact name, try to find by sub-components + std::string::size_type aSlash = theName.rfind('/'); + if (aSlash != std::string::npos) { + std::string anObjName = theName.substr(0, aSlash); + aFind = myNamingNames.find(anObjName); + if (aFind != myNamingNames.end()) { + TCollection_ExtendedString aSubName(theName.substr(aSlash + 1).c_str()); + // iterate all possible same-named labels starting from the last one (the recent) + std::list::reverse_iterator aLabIter = aFind->second.rbegin(); + for(; aLabIter != aFind->second.rend(); aLabIter++) { + if (theContext.get()) { + // context is defined and not like this, so, skip + if (theContext != myObjs->object(aLabIter->Father())) + continue; + } + // copy aSubName to avoid incorrect further processing after its suffix cutting + TCollection_ExtendedString aSubNameCopy(aSubName); + // searching sub-labels with this name + TDF_ChildIDIterator aNamesIter(*aLabIter, TDataStd_Name::GetID(), Standard_True); + for(; aNamesIter.More(); aNamesIter.Next()) { + Handle(TDataStd_Name) aName = Handle(TDataStd_Name)::DownCast(aNamesIter.Value()); + if (aName->Get() == aSubNameCopy) + return aName->Label(); + } + // If not found child label with the exact sub-name, then try to find compound with + // such sub-name without suffix. + Standard_Integer aSuffixPos = aSubNameCopy.SearchFromEnd('_'); + if (aSuffixPos != -1 && aSuffixPos != aSubNameCopy.Length()) { + TCollection_ExtendedString anIndexStr = aSubNameCopy.Split(aSuffixPos); + aSubNameCopy.Remove(aSuffixPos); + aNamesIter.Initialize(*aLabIter, TDataStd_Name::GetID(), Standard_True); + for(; aNamesIter.More(); aNamesIter.Next()) { + Handle(TDataStd_Name) aName = Handle(TDataStd_Name)::DownCast(aNamesIter.Value()); + if (aName->Get() == aSubNameCopy) { + return aName->Label(); + } + } + // check also "this" label + Handle(TDataStd_Name) aName; + if (aLabIter->FindAttribute(TDataStd_Name::GetID(), aName)) { + if (aName->Get() == aSubNameCopy) { + return aName->Label(); + } + } + } + } + // verify context's name is same as sub-component's and use context's label + if (aSubName.IsEqual(anObjName.c_str())) + return *(aFind->second.rbegin()); } } + return TDF_Label(); // not found } -TDF_Label Model_Document::resultLabel( - const std::shared_ptr& theFeatureData, const int theResultIndex) -{ - const std::shared_ptr& aData = - std::dynamic_pointer_cast(theFeatureData); - return aData->label().Father().FindChild(TAG_FEATURE_RESULTS).FindChild(theResultIndex + 1); +bool Model_Document::isLaterByDep(FeaturePtr theThis, FeaturePtr theOther) { + // check dependencies first: if theOther depends on theThis, theThis is not later + std::list > > > aRefs; + theOther->data()->referencesToObjects(aRefs); + std::list > > >::iterator + aRefIt = aRefs.begin(); + for(; aRefIt != aRefs.end(); aRefIt++) { + std::list::iterator aRefObjIt = aRefIt->second.begin(); + for(; aRefObjIt != aRefIt->second.end(); aRefObjIt++) { + ObjectPtr aRefObj = *aRefObjIt; + if (aRefObj.get()) { + FeaturePtr aRefFeat = std::dynamic_pointer_cast(aRefObj); + if (!aRefFeat.get()) { // take feature of the result + aRefFeat = feature(std::dynamic_pointer_cast(aRefObj)); + } + if (aRefFeat.get()) { + if (aRefFeat == theThis) + return false; // other references to this, so other later than this + //if (std::dynamic_pointer_cast(aRefFeat)) { + // if (!isLaterByDep(theThis, aRefFeat)) // nested composites: recursion + // return false; + //} + } + } + } + } + FeaturePtr aThisOwner = ModelAPI_Tools::compositeOwner(theThis); + if (aThisOwner.get()) { + if (aThisOwner == theOther) + return true; // composite owner is later that its sub + if (!isLaterByDep(aThisOwner, theOther)) + return false; + } + return myObjs->isLater(theThis, theOther); } -void Model_Document::storeResult(std::shared_ptr theFeatureData, - std::shared_ptr theResult, - const int theResultIndex) +int Model_Document::numberOfNameInHistory( + const ObjectPtr& theNameObject, const TDF_Label& theStartFrom) { - std::shared_ptr aThis = - Model_Application::getApplication()->getDocument(myID); - theResult->setDoc(aThis); - initData(theResult, resultLabel(theFeatureData, theResultIndex), TAG_FEATURE_ARGUMENTS); - if (theResult->data()->name().empty()) { // if was not initialized, generate event and set a name - theResult->data()->setName(theFeatureData->name()); + std::map >::iterator aFind = + myNamingNames.find(theNameObject->data()->name()); + if (aFind == myNamingNames.end() || aFind->second.size() < 2) { + return 1; // no need to specify the name by additional identifiers + } + // get the feature of the object for relative compare + FeaturePtr aStart = myObjs->feature(theStartFrom); + if (!aStart.get()) // strange, but can not find feature by the label + return 1; + // feature that contain result with this name + FeaturePtr aNameFeature; + ResultPtr aNameResult = std::dynamic_pointer_cast(theNameObject); + if (aNameResult) + aNameFeature = myObjs->feature(aNameResult); + else + aNameFeature = std::dynamic_pointer_cast(theNameObject); + // iterate all labels with this name to find the nearest just before or equal relative + std::list::reverse_iterator aLabIter = aFind->second.rbegin(); + for(; aLabIter != aFind->second.rend(); aLabIter++) { + FeaturePtr aLabFeat = featureByLab(*aLabIter); + if (!aLabFeat.get()) + continue; + if (isLaterByDep(aStart, aLabFeat)) // skip also start: its result don't used + break; + } + int aResIndex = 1; + for(; aLabIter != aFind->second.rend(); aLabIter++) { + FeaturePtr aLabFeat = featureByLab(*aLabIter); + if (!aLabFeat.get()) + continue; + if (aLabFeat == aNameFeature || isLaterByDep(aNameFeature, aLabFeat)) + return aResIndex; + aResIndex++; } + return aResIndex; // strange } -std::shared_ptr Model_Document::createConstruction( - const std::shared_ptr& theFeatureData, const int theIndex) +ResultPtr Model_Document::findByName( + std::string& theName, std::string& theSubShapeName, bool& theUniqueContext) { - TDF_Label aLab = resultLabel(theFeatureData, theIndex); - TDataStd_Comment::Set(aLab, ModelAPI_ResultConstruction::group().c_str()); - ObjectPtr anOldObject = object(aLab); - std::shared_ptr aResult; - if (anOldObject) { - aResult = std::dynamic_pointer_cast(anOldObject); + int aNumInHistory = 0; + std::string aName = theName; + ResultPtr aRes = myObjs->findByName(aName); + theUniqueContext = !(aRes.get() && myNamingNames.find(aName) != myNamingNames.end()); + while(!aRes.get() && aName[0] == '_') { // this may be theContext with the history index + aNumInHistory++; + aName = aName.substr(1); + aRes = myObjs->findByName(aName); } - if (!aResult) { - aResult = std::shared_ptr(new Model_ResultConstruction); - storeResult(theFeatureData, aResult, theIndex); + if (aNumInHistory) { + std::map >::iterator aFind = myNamingNames.find(aName); + if (aFind != myNamingNames.end() && aFind->second.size() > aNumInHistory) { + std::list::reverse_iterator aLibIt = aFind->second.rbegin(); + for(; aNumInHistory != 0; aNumInHistory--) + aLibIt++; + const TDF_Label& aResultLab = *aLibIt; + aRes = std::dynamic_pointer_cast(myObjs->object(aResultLab.Father())); + if (aRes) { // modify the incoming names + if (!theSubShapeName.empty()) + theSubShapeName = theSubShapeName.substr(theName.size() - aName.size()); + theName = aName; + } + } } - return aResult; + return aRes; } -std::shared_ptr Model_Document::createBody( - const std::shared_ptr& theFeatureData, const int theIndex) +std::list > Model_Document::allFeatures() { - TDF_Label aLab = resultLabel(theFeatureData, theIndex); - TDataStd_Comment::Set(aLab, ModelAPI_ResultBody::group().c_str()); - ObjectPtr anOldObject = object(aLab); - std::shared_ptr aResult; - if (anOldObject) { - aResult = std::dynamic_pointer_cast(anOldObject); - } - if (!aResult) { - aResult = std::shared_ptr(new Model_ResultBody); - storeResult(theFeatureData, aResult, theIndex); - } - return aResult; + return myObjs->allFeatures(); } -std::shared_ptr Model_Document::createPart( - const std::shared_ptr& theFeatureData, const int theIndex) +std::list > Model_Document::allObjects() { - TDF_Label aLab = resultLabel(theFeatureData, theIndex); - TDataStd_Comment::Set(aLab, ModelAPI_ResultPart::group().c_str()); - ObjectPtr anOldObject = object(aLab); - std::shared_ptr aResult; - if (anOldObject) { - aResult = std::dynamic_pointer_cast(anOldObject); - } - if (!aResult) { - aResult = std::shared_ptr(new Model_ResultPart); - storeResult(theFeatureData, aResult, theIndex); + return myObjs->allObjects(); +} + +void Model_Document::setActive(const bool theFlag) +{ + if (theFlag != myIsActive) { + myIsActive = theFlag; + // redisplay all the objects of this part + static Events_Loop* aLoop = Events_Loop::loop(); + static Events_ID aRedispEvent = aLoop->eventByName(EVENT_OBJECT_TO_REDISPLAY); + + for(int a = size(ModelAPI_Feature::group()) - 1; a >= 0; a--) { + FeaturePtr aFeature = std::dynamic_pointer_cast( + object(ModelAPI_Feature::group(), a)); + if (aFeature.get() && aFeature->data()->isValid()) { + std::list aResults; + ModelAPI_Tools::allResults(aFeature, aResults); + for (std::list::iterator aRes = aResults.begin(); + aRes != aResults.end(); aRes++) { + ModelAPI_EventCreator::get()->sendUpdated(*aRes, aRedispEvent); + } + } + } } - return aResult; } -std::shared_ptr Model_Document::createGroup( - const std::shared_ptr& theFeatureData, const int theIndex) +bool Model_Document::isActive() const +{ + return myIsActive; +} + +int Model_Document::transactionID() { - TDF_Label aLab = resultLabel(theFeatureData, theIndex); - TDataStd_Comment::Set(aLab, ModelAPI_ResultGroup::group().c_str()); - ObjectPtr anOldObject = object(aLab); - std::shared_ptr aResult; - if (anOldObject) { - aResult = std::dynamic_pointer_cast(anOldObject); + Handle(TDataStd_Integer) anIndex; + if (!generalLabel().FindChild(TAG_CURRENT_TRANSACTION). + FindAttribute(TDataStd_Integer::GetID(), anIndex)) { + anIndex = TDataStd_Integer::Set(generalLabel().FindChild(TAG_CURRENT_TRANSACTION), 1); } - if (!aResult) { - aResult = std::shared_ptr(new Model_ResultGroup(theFeatureData)); - storeResult(theFeatureData, aResult, theIndex); + return anIndex->Get(); +} + +void Model_Document::incrementTransactionID() +{ + int aNewVal = transactionID() + 1; + TDataStd_Integer::Set(generalLabel().FindChild(TAG_CURRENT_TRANSACTION), aNewVal); +} + +TDF_Label Model_Document::extConstructionsLabel() const +{ + return myDoc->Main().FindChild(TAG_EXTERNAL_CONSTRUCTIONS); +} + +bool Model_Document::isOpened() +{ + return myObjs && !myDoc.IsNull(); +} + +int Model_Document::numInternalFeatures() +{ + return myObjs->numInternalFeatures(); +} + +std::shared_ptr Model_Document::internalFeature(const int theIndex) +{ + return myObjs->internalFeature(theIndex); +} + +void Model_Document::synchronizeTransactions() +{ + Model_Document* aRoot = + std::dynamic_pointer_cast(ModelAPI_Session::get()->moduleDocument()).get(); + if (aRoot == this) + return; // don't need to synchronize root with root + + std::shared_ptr aSession = + std::dynamic_pointer_cast(Model_Session::get()); + while(myRedos.size() > aRoot->myRedos.size()) { // remove redo in this + aSession->setCheckTransactions(false); + redo(); + aSession->setCheckTransactions(true); } - return aResult; + /* this case can not be reproduced in any known case for the current moment, so, just comment + while(myRedos.size() < aRoot->myRedos.size()) { // add more redo in this + undoInternal(false, true); + }*/ } -std::shared_ptr Model_Document::feature( - const std::shared_ptr& theResult) +/// Feature that is used for selection in the Part document by the external request +class Model_SelectionInPartFeature : public ModelAPI_Feature { +public: + /// Nothing to do in constructor + Model_SelectionInPartFeature() : ModelAPI_Feature() {} + + /// Returns the unique kind of a feature + virtual const std::string& getKind() { + static std::string MY_KIND("InternalSelectionInPartFeature"); + return MY_KIND; + } + /// Request for initialization of data model of the object: adding all attributes + virtual void initAttributes() { + data()->addAttribute("selection", ModelAPI_AttributeSelectionList::typeId()); + } + /// Nothing to do in the execution function + virtual void execute() {} + +}; + +//! Returns the feature that is used for calculation of selection externally from the document +AttributeSelectionListPtr Model_Document::selectionInPartFeature() { - std::shared_ptr aData = std::dynamic_pointer_cast(theResult->data()); - if (aData) { - TDF_Label aFeatureLab = aData->label().Father().Father().Father(); - return feature(aFeatureLab); + // return already created, otherwise create + if (!mySelectionFeature.get() || !mySelectionFeature->data()->isValid()) { + // create a new one + mySelectionFeature = FeaturePtr(new Model_SelectionInPartFeature); + + TDF_Label aFeatureLab = generalLabel().FindChild(TAG_SELECTION_FEATURE); + std::shared_ptr aData(new Model_Data); + aData->setLabel(aFeatureLab.FindChild(1)); + aData->setObject(mySelectionFeature); + mySelectionFeature->setDoc(myObjs->owner()); + mySelectionFeature->setData(aData); + std::string aName = id() + "_Part"; + mySelectionFeature->data()->setName(aName); + mySelectionFeature->setDoc(myObjs->owner()); + mySelectionFeature->initAttributes(); + mySelectionFeature->init(); // to make it enabled and Update correctly + // this update may cause recomputation of the part after selection on it, that is not needed + mySelectionFeature->data()->blockSendAttributeUpdated(true); } + return mySelectionFeature->selectionList("selection"); +} + +FeaturePtr Model_Document::lastFeature() +{ + if (myObjs) + return myObjs->lastFeature(); return FeaturePtr(); } -void Model_Document::updateResults(FeaturePtr theFeature) -{ - // for not persistent is will be done by parametric updater automatically - //if (!theFeature->isPersistentResult()) return; - // check the existing results and remove them if there is nothing on the label - std::list::const_iterator aResIter = theFeature->results().cbegin(); - while(aResIter != theFeature->results().cend()) { - ResultPtr aBody = std::dynamic_pointer_cast(*aResIter); - if (aBody) { - if (!aBody->data()->isValid()) { - // found a disappeared result => remove it - theFeature->removeResult(aBody); - // start iterate from beginning because iterator is corrupted by removing - aResIter = theFeature->results().cbegin(); - continue; +static Handle(TNaming_NamedShape) searchForOriginalShape(TopoDS_Shape theShape, TDF_Label aMain) { + Handle(TNaming_NamedShape) aResult; + while(!theShape.IsNull()) { // searching for the very initial shape that produces this one + TopoDS_Shape aShape = theShape; + theShape.Nullify(); + // to avoid crash of TNaming_SameShapeIterator if pure shape does not exists + if (!TNaming_Tool::HasLabel(aMain, aShape)) + break; + for(TNaming_SameShapeIterator anIter(aShape, aMain); anIter.More(); anIter.Next()) { + TDF_Label aNSLab = anIter.Label(); + Handle(TNaming_NamedShape) aNS; + if (aNSLab.FindAttribute(TNaming_NamedShape::GetID(), aNS)) { + for(TNaming_Iterator aShapesIter(aNS); aShapesIter.More(); aShapesIter.Next()) { + if (aShapesIter.Evolution() == TNaming_SELECTED || + aShapesIter.Evolution() == TNaming_DELETE) + continue; // don't use the selection evolution + if (aShapesIter.NewShape().IsSame(aShape)) { // found the original shape + aResult = aNS; + if (aResult->Evolution() == TNaming_MODIFY) + theShape = aShapesIter.OldShape(); + // otherwise may me searching for another item of this shape with longer history + if (!theShape.IsNull()) + break; + } + } } } - aResIter++; } - // it may be on undo - if (!theFeature->data() || !theFeature->data()->isValid()) - return; - // check that results are presented on all labels - int aResSize = theFeature->results().size(); - TDF_ChildIterator aLabIter(resultLabel(theFeature->data(), 0).Father()); - for(; aLabIter.More(); aLabIter.Next()) { - // here must be GUID of the feature - int aResIndex = aLabIter.Value().Tag() - 1; - ResultPtr aNewBody; - if (aResSize <= aResIndex) { - TDF_Label anArgLab = aLabIter.Value(); - Handle(TDataStd_Comment) aGroup; - if (anArgLab.FindAttribute(TDataStd_Comment::GetID(), aGroup)) { - if (aGroup->Get() == ModelAPI_ResultBody::group().c_str()) { - aNewBody = createBody(theFeature->data(), aResIndex); - } else if (aGroup->Get() == ModelAPI_ResultPart::group().c_str()) { - aNewBody = createPart(theFeature->data(), aResIndex); - } else if (aGroup->Get() == ModelAPI_ResultConstruction::group().c_str()) { - theFeature->execute(); // construction shapes are needed for sketch solver + return aResult; +} + +std::shared_ptr Model_Document::producedByFeature( + std::shared_ptr theResult, + const std::shared_ptr& theShape) +{ + ResultBodyPtr aBody = std::dynamic_pointer_cast(theResult); + if (!aBody.get()) { + return feature(theResult); // for not-body just returns the feature that produced this result + } + // otherwise get the shape and search the very initial label for it + TopoDS_Shape aShape = theShape->impl(); + if (aShape.IsNull()) + return FeaturePtr(); + + // for compsolids and compounds all the naming is located in the main object, so, try to use + // it first + ResultBodyPtr aMain = ModelAPI_Tools::bodyOwner(theResult); + while (aMain.get()) { // get the top-most main + ResultBodyPtr aNextMain = ModelAPI_Tools::bodyOwner(aMain); + if (aNextMain.get()) + aMain = aNextMain; + else break; + } + if (aMain.get()) { + FeaturePtr aMainRes = producedByFeature(aMain, theShape); + if (aMainRes) + return aMainRes; + } + + std::shared_ptr aBodyData = std::dynamic_pointer_cast(theResult->data()); + if (!aBodyData.get() || !aBodyData->isValid()) + return FeaturePtr(); + + TopoDS_Shape anOldShape; // old shape in the pair old shape->theShape in the named shape + TopoDS_Shape aShapeContainer; // old shape of the shape that contains aShape as sub-element + Handle(TNaming_NamedShape) aCandidatInThis, aCandidatContainer; + TDF_Label aBodyLab = aBodyData->shapeLab(); + // use child and this label (the lowest priority) + TDF_ChildIDIterator aNSIter(aBodyLab, TNaming_NamedShape::GetID(), Standard_True); + bool aUseThis = !aNSIter.More(); + while(anOldShape.IsNull() && (aNSIter.More() || aUseThis)) { + Handle(TNaming_NamedShape) aNS; + if (aUseThis) { + if (!aBodyLab.FindAttribute(TNaming_NamedShape::GetID(), aNS)) + break; + } else { + aNS = Handle(TNaming_NamedShape)::DownCast(aNSIter.Value()); + } + for(TNaming_Iterator aShapesIter(aNS); aShapesIter.More(); aShapesIter.Next()) { + if (aShapesIter.Evolution() == TNaming_SELECTED || aShapesIter.Evolution() == TNaming_DELETE) + continue; // don't use the selection evolution + if (aShapesIter.NewShape().IsSame(aShape)) { // found the original shape + aCandidatInThis = aNS; + if (aCandidatInThis->Evolution() == TNaming_MODIFY) + anOldShape = aShapesIter.OldShape(); + // otherwise may me searching for another item of this shape with longer history + if (!anOldShape.IsNull()) break; - } else if (aGroup->Get() == ModelAPI_ResultGroup::group().c_str()) { - aNewBody = createGroup(theFeature->data(), aResIndex); - } else { - Events_Error::send(std::string("Unknown type of result is found in the document:") + - TCollection_AsciiString(aGroup->Get()).ToCString()); - } } - if (aNewBody) { - theFeature->setResult(aNewBody, aResIndex); + // check that the shape contains aShape as sub-shape to fill container + if (aShapesIter.NewShape().ShapeType() < aShape.ShapeType() && aCandidatContainer.IsNull()) { + TopExp_Explorer anExp(aShapesIter.NewShape(), aShape.ShapeType()); + for(; anExp.More(); anExp.Next()) { + if (aShape.IsSame(anExp.Current())) { + aCandidatContainer = aNS; + aShapeContainer = aShapesIter.NewShape(); + } + } } } + // iterate to the next label or to the body label in the end + if (!aUseThis) + aNSIter.Next(); + if (!aNSIter.More()) { + if (aUseThis) + break; + aUseThis = true; + } + } + if (aCandidatInThis.IsNull()) { + // to fix 1512: searching for original shape of this shape + // if modification of it is not in this result + aCandidatInThis = searchForOriginalShape(aShape, myDoc->Main()); + if (aCandidatInThis.IsNull()) { + if (aCandidatContainer.IsNull()) + return FeaturePtr(); + // with the lower priority use the higher level shape that contains aShape + aCandidatInThis = aCandidatContainer; + anOldShape = aShapeContainer; + } else { + // to stop the searching by the following searchForOriginalShape + anOldShape.Nullify(); + } + } + + Handle(TNaming_NamedShape) aNS = searchForOriginalShape(anOldShape, myDoc->Main()); + if (!aNS.IsNull()) + aCandidatInThis = aNS; + + FeaturePtr aResult; + TDF_Label aResultLab = aCandidatInThis->Label(); + while(aResultLab.Depth() > 3) + aResultLab = aResultLab.Father(); + FeaturePtr aFeature = myObjs->feature(aResultLab); + if (aFeature.get()) { + if (!aResult.get() || myObjs->isLater(aResult, aFeature)) { + aResult = aFeature; + } } + return aResult; } -Standard_Integer HashCode(const TDF_Label& theLab, const Standard_Integer theUpper) +bool Model_Document::isLater(FeaturePtr theLater, FeaturePtr theCurrent) const { - return TDF_LabelMapHasher::HashCode(theLab, theUpper); + return myObjs->isLater(theLater, theCurrent); +} +// Object Browser nodes states +// LCOV_EXCL_START +void Model_Document::storeNodesState(const std::list& theStates) +{ + TDF_Label aLab = generalLabel().FindChild(TAG_NODES_STATE); + aLab.ForgetAllAttributes(); + if (!theStates.empty()) { + Handle(TDataStd_BooleanArray) anArray = + TDataStd_BooleanArray::Set(aLab, 0, int(theStates.size()) - 1); + std::list::const_iterator aState = theStates.begin(); + for(int anIndex = 0; aState != theStates.end(); aState++, anIndex++) { + anArray->SetValue(anIndex, *aState); + } + } } -Standard_Boolean IsEqual(const TDF_Label& theLab1, const TDF_Label& theLab2) + +void Model_Document::restoreNodesState(std::list& theStates) const { - return TDF_LabelMapHasher::IsEqual(theLab1, theLab2); + TDF_Label aLab = generalLabel().FindChild(TAG_NODES_STATE); + Handle(TDataStd_BooleanArray) anArray; + if (aLab.FindAttribute(TDataStd_BooleanArray::GetID(), anArray)) { + int anUpper = anArray->Upper(); + for(int anIndex = 0; anIndex <= anUpper; anIndex++) { + theStates.push_back(anArray->Value(anIndex) == Standard_True); + } + } } +// LCOV_EXCL_STOP -void Model_Document::addNamingName(const TDF_Label theLabel, std::string theName) +void Model_Document::eraseAllFeatures() { - myNamingNames[theName] = theLabel; + if (myObjs) + myObjs->eraseAllFeatures(); } -TDF_Label Model_Document::findNamingName(std::string theName) +std::shared_ptr Model_Document::nextFeature( + std::shared_ptr theCurrent, const bool theReverse) const { - std::map::iterator aFind = myNamingNames.find(theName); - if (aFind == myNamingNames.end()) - return TDF_Label(); // not found - return aFind->second; + if (theCurrent.get() && myObjs) { + int anIndex = kUNDEFINED_FEATURE_INDEX; + return myObjs->nextFeature(theCurrent, anIndex, theReverse); + } + return FeaturePtr(); // nothing by default } -ResultPtr Model_Document::findByName(const std::string theName) +void Model_Document::setExecuteFeatures(const bool theFlag) { - NCollection_DataMap::Iterator anObjIter(myObjs); - for(; anObjIter.More(); anObjIter.Next()) { - FeaturePtr& aFeature = anObjIter.ChangeValue(); - if (!aFeature) // may be on close + myExecuteFeatures = theFlag; + const std::set aSubs = subDocuments(); + std::set::iterator aSubIter = aSubs.begin(); + for (; aSubIter != aSubs.end(); aSubIter++) { + if (!subDoc(*aSubIter)->myObjs) continue; - const std::list >& aResults = aFeature->results(); - std::list >::const_iterator aRIter = aResults.begin(); - for (; aRIter != aResults.cend(); aRIter++) { - if (aRIter->get() && (*aRIter)->data() && (*aRIter)->data()->isValid() && - (*aRIter)->data()->name() == theName) { - return *aRIter; - } - } + subDoc(*aSubIter)->setExecuteFeatures(theFlag); } - // not found - return ResultPtr(); +} + +void Model_Document::appendTransactionToPrevious() +{ + Transaction anAppended = myTransactions.back(); + myTransactions.pop_back(); + if (!myTransactions.empty()) { // if it is empty, just forget the appended + myTransactions.back().myOCAFNum += anAppended.myOCAFNum; + } + // propagate the same action to sub-documents + const std::set aSubs = subDocuments(); + for (std::set::iterator aSubIter = aSubs.begin(); aSubIter != aSubs.end(); aSubIter++) { + subDoc(*aSubIter)->appendTransactionToPrevious(); + } +} + +/// GUID for keeping information about the auto-recomputation state +static const Standard_GUID kAutoRecomputationID("8493fb74-0674-4912-a100-1cf46c7cfab3"); + +void Model_Document::setAutoRecomutationState(const bool theState) +{ + if (theState) + generalLabel().FindChild(TAG_CURRENT_TRANSACTION).ForgetAttribute(kAutoRecomputationID); + else + TDataStd_UAttribute::Set( + generalLabel().FindChild(TAG_CURRENT_TRANSACTION), kAutoRecomputationID); +} + +bool Model_Document::autoRecomutationState() const +{ + return !generalLabel().FindChild(TAG_CURRENT_TRANSACTION).IsAttribute(kAutoRecomputationID); }