Salome HOME
Merge remote-tracking branch 'remotes/origin/Dev_FolderFeature'
authorazv <azv@opencascade.com>
Fri, 1 Dec 2017 12:40:50 +0000 (15:40 +0300)
committerazv <azv@opencascade.com>
Fri, 1 Dec 2017 12:40:50 +0000 (15:40 +0300)
49 files changed:
src/Model/Model_Data.cpp
src/Model/Model_Data.h
src/Model/Model_Document.cpp
src/Model/Model_Document.h
src/Model/Model_Objects.cpp
src/Model/Model_Objects.h
src/ModelAPI/CMakeLists.txt
src/ModelAPI/ModelAPI.i
src/ModelAPI/ModelAPI_Document.h
src/ModelAPI/ModelAPI_Folder.cpp [new file with mode: 0644]
src/ModelAPI/ModelAPI_Folder.h [new file with mode: 0644]
src/ModelAPI/ModelAPI_Tools.cpp
src/ModelAPI/ModelAPI_swig.h
src/ModelAPI/Test/TestFolder_Create.py [new file with mode: 0644]
src/ModelAPI/Test/TestFolder_Remove.py [new file with mode: 0644]
src/ModelAPI/Test/TestFolder_Sketch.py [new file with mode: 0644]
src/ModelAPI/Test/TestFolder_Stability.py [new file with mode: 0644]
src/ModelAPI/Test/TestFolder_Update.py [new file with mode: 0644]
src/ModelHighAPI/CMakeLists.txt
src/ModelHighAPI/ModelHighAPI.i
src/ModelHighAPI/ModelHighAPI_Dumper.cpp
src/ModelHighAPI/ModelHighAPI_Dumper.h
src/ModelHighAPI/ModelHighAPI_FeatureStore.cpp
src/ModelHighAPI/ModelHighAPI_FeatureStore.h
src/ModelHighAPI/ModelHighAPI_Folder.cpp [new file with mode: 0644]
src/ModelHighAPI/ModelHighAPI_Folder.h [new file with mode: 0644]
src/ModelHighAPI/ModelHighAPI_Tools.cpp
src/ModelHighAPI/ModelHighAPI_swig.h
src/ModuleBase/ModuleBase_Tools.cpp
src/ModuleBase/ModuleBase_Tools.h
src/PartSet/PartSet_Module.cpp
src/PartSet/PartSet_Tools.h
src/PythonAPI/model/dump/DumpAssistant.py
src/PythonAPI/model/services/__init__.py
src/XGUI/XGUI_ContextMenuMgr.cpp
src/XGUI/XGUI_ContextMenuMgr.h
src/XGUI/XGUI_DataModel.cpp
src/XGUI/XGUI_DataModel.h
src/XGUI/XGUI_SelectionMgr.cpp
src/XGUI/XGUI_SelectionMgr.h
src/XGUI/XGUI_Workshop.cpp
src/XGUI/XGUI_Workshop.h
src/XGUI/XGUI_pictures.qrc
src/XGUI/pictures/create_folder.png [new file with mode: 0644]
src/XGUI/pictures/features_folder.png [new file with mode: 0644]
src/XGUI/pictures/insert_folder_after.png [new file with mode: 0644]
src/XGUI/pictures/insert_folder_before.png [new file with mode: 0644]
src/XGUI/pictures/move_out_after.png [new file with mode: 0644]
src/XGUI/pictures/move_out_before.png [new file with mode: 0644]

index 9911a796463fe41ec18cfc94fa6e9e22b048003b..49bca1d2752823a715467acb473d742a254b4a2b 100644 (file)
@@ -468,9 +468,9 @@ void Model_Data::eraseBackReferences()
     aRes->setIsConcealed(false);
 }
 
-void Model_Data::removeBackReference(FeaturePtr theFeature, std::string theAttrID)
+void Model_Data::removeBackReference(ObjectPtr theObject, std::string theAttrID)
 {
-  AttributePtr anAttribute = theFeature->data()->attribute(theAttrID);
+  AttributePtr anAttribute = theObject->data()->attribute(theAttrID);
   removeBackReference(anAttribute);
 }
 
@@ -492,10 +492,7 @@ void Model_Data::removeBackReference(AttributePtr theAttr)
 void Model_Data::addBackReference(FeaturePtr theFeature, std::string theAttrID,
    const bool theApplyConcealment)
 {
-  // it is possible to add the same attribute twice: may be last time the owner was not Stable...
-  AttributePtr anAttribute = theFeature->data()->attribute(theAttrID);
-  if (myRefsToMe.find(anAttribute) == myRefsToMe.end())
-    myRefsToMe.insert(theFeature->data()->attribute(theAttrID));
+  addBackReference(ObjectPtr(theFeature), theAttrID);
 
   if (theApplyConcealment &&  theFeature->isStable() &&
       ModelAPI_Session::get()->validators()->isConcealed(theFeature->getKind(), theAttrID)) {
@@ -510,6 +507,14 @@ void Model_Data::addBackReference(FeaturePtr theFeature, std::string theAttrID,
   }
 }
 
+void Model_Data::addBackReference(ObjectPtr theObject, std::string theAttrID)
+{
+  // it is possible to add the same attribute twice: may be last time the owner was not Stable...
+  AttributePtr anAttribute = theObject->data()->attribute(theAttrID);
+  if (myRefsToMe.find(anAttribute) == myRefsToMe.end())
+    myRefsToMe.insert(anAttribute);
+}
+
 void Model_Data::updateConcealmentFlag()
 {
   std::set<AttributePtr>::iterator aRefsIter = myRefsToMe.begin();
index 186cfefc2a353a030c9df47d881a6b8140a4d3c2..836c4d646c038bec34d17fed3e914ee03b55cce5 100644 (file)
@@ -36,6 +36,7 @@
 #include <ModelAPI_AttributeIntArray.h>
 #include <ModelAPI_Data.h>
 #include <ModelAPI_Feature.h>
+#include <ModelAPI_Folder.h>
 #include <ModelAPI_Object.h>
 
 #include <TDF_Label.hxx>
@@ -271,7 +272,7 @@ private:
   /// Removes a back reference (with identifier which attribute references to this object)
   /// \param theFeature feature referenced to this
   /// \param theAttrID identifier of the attribute that is references from theFeature to this
-  void removeBackReference(FeaturePtr theFeature, std::string theAttrID);
+  void removeBackReference(ObjectPtr theObject, std::string theAttrID);
   /// Removes a back reference (by the attribute)
   /// \param theAttr the referenced attribute
   void removeBackReference(AttributePtr theAttr);
@@ -281,6 +282,10 @@ private:
   /// \param theApplyConcealment applies consealment flag changes
   void addBackReference(FeaturePtr theFeature, std::string theAttrID,
     const bool theApplyConcealment = true);
+  /// Adds a back reference to an object
+  /// \param theObject object referenced to this
+  /// \param theAttrID identifier of the attribute that is references from theFolder to this
+  void addBackReference(ObjectPtr theObject, std::string theAttrID);
 
   /// Makes the concealment flag up to date for this object-owner.
   MODEL_EXPORT virtual void updateConcealmentFlag();
@@ -303,11 +308,15 @@ private:
 /// Without concealment change, it will be done later, on synchronization.
 #define ADD_BACK_REF(TARGET) \
   if (TARGET.get() != NULL) { \
+    std::shared_ptr<Model_Data> aTargetData = \
+        std::dynamic_pointer_cast<Model_Data>((TARGET)->data()); \
     FeaturePtr anAttributeOwnerFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(owner()); \
-    if (anAttributeOwnerFeature.get()) { \
-      std::shared_ptr<Model_Data> aTargetData = std::dynamic_pointer_cast<Model_Data>( \
-        (TARGET)->data()); \
+    if (anAttributeOwnerFeature.get()) \
       aTargetData->addBackReference(anAttributeOwnerFeature, id(), false); \
+    else { \
+      FolderPtr anAttributeOwnerFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(owner()); \
+      if (anAttributeOwnerFolder.get()) \
+        aTargetData->addBackReference(ObjectPtr(anAttributeOwnerFolder), id()); \
     } \
   }
 
@@ -315,11 +324,15 @@ private:
 /// Without concealment change, it will be done later, on synchronization.
 #define REMOVE_BACK_REF(TARGET) \
   if (TARGET.get() != NULL) { \
+    std::shared_ptr<Model_Data> aTargetData = \
+        std::dynamic_pointer_cast<Model_Data>((TARGET)->data()); \
     FeaturePtr anAttOwnerFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(owner()); \
-    if (anAttOwnerFeature.get()) { \
-      std::shared_ptr<Model_Data> aTargetData = std::dynamic_pointer_cast<Model_Data>( \
-        (TARGET)->data()); \
+    if (anAttOwnerFeature.get()) \
       aTargetData->removeBackReference(anAttOwnerFeature, id()); \
+    else { \
+      FolderPtr anAttributeOwnerFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(owner()); \
+      if (anAttributeOwnerFolder.get()) \
+        aTargetData->removeBackReference(ObjectPtr(anAttributeOwnerFolder), id()); \
     } \
   }
 
index e11f26faa5ebb62378031c1a75745267a7512f9d..c422dcf526c085a63aef1a4116c1ec1dddf5384e 100755 (executable)
@@ -925,7 +925,7 @@ FeaturePtr Model_Document::addFeature(std::string theID, const bool theMakeCurre
       int aSubs = aComp->numberOfSubs(false);
       for(int a = 0; a < aSubs; a++) {
         FeaturePtr aSub = aComp->subFeature(a, false);
-        if (myObjs->isLater(aSub, aCurrent)) {
+        if (aSub && myObjs->isLater(aSub, aCurrent)) {
           isModified =  true;
           aCurrent = aSub;
         }
@@ -1031,9 +1031,11 @@ std::shared_ptr<Model_Document> Model_Document::subDoc(int theDocID)
     Model_Application::getApplication()->document(theDocID));
 }
 
-ObjectPtr Model_Document::object(const std::string& theGroupID, const int theIndex)
+ObjectPtr Model_Document::object(const std::string& theGroupID,
+                                 const int theIndex,
+                                 const bool theAllowFolder)
 {
-  return myObjs->object(theGroupID, theIndex);
+  return myObjs->object(theGroupID, theIndex, theAllowFolder);
 }
 
 std::shared_ptr<ModelAPI_Object> Model_Document::objectByName(
@@ -1042,16 +1044,17 @@ std::shared_ptr<ModelAPI_Object> Model_Document::objectByName(
   return myObjs->objectByName(theGroupID, theName);
 }
 
-const int Model_Document::index(std::shared_ptr<ModelAPI_Object> theObject)
+const int Model_Document::index(std::shared_ptr<ModelAPI_Object> theObject,
+                                const bool theAllowFolder)
 {
-  return myObjs->index(theObject);
+  return myObjs->index(theObject, theAllowFolder);
 }
 
-int Model_Document::size(const std::string& theGroupID)
+int Model_Document::size(const std::string& theGroupID, const bool theAllowFolder)
 {
   if (myObjs == 0) // may be on close
     return 0;
-  return myObjs->size(theGroupID);
+  return myObjs->size(theGroupID, theAllowFolder);
 }
 
 std::shared_ptr<ModelAPI_Feature> Model_Document::currentFeature(const bool theVisible)
@@ -1255,6 +1258,51 @@ std::shared_ptr<ModelAPI_ResultParameter> Model_Document::createParameter(
   return myObjs->createParameter(theFeatureData, theIndex);
 }
 
+std::shared_ptr<ModelAPI_Folder> Model_Document::addFolder(
+    std::shared_ptr<ModelAPI_Feature> theAddBefore)
+{
+  return myObjs->createFolder(theAddBefore);
+}
+
+void Model_Document::removeFolder(std::shared_ptr<ModelAPI_Folder> theFolder)
+{
+  if (theFolder)
+    myObjs->removeFolder(theFolder);
+}
+
+std::shared_ptr<ModelAPI_Folder> Model_Document::findFolderAbove(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures)
+{
+  return myObjs->findFolder(theFeatures, false);
+}
+
+std::shared_ptr<ModelAPI_Folder> Model_Document::findFolderBelow(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures)
+{
+  return myObjs->findFolder(theFeatures, true);
+}
+
+std::shared_ptr<ModelAPI_Folder> Model_Document::findContainingFolder(
+      const std::shared_ptr<ModelAPI_Feature>& theFeature,
+      int& theIndexInFolder)
+{
+  return myObjs->findContainingFolder(theFeature, theIndexInFolder);
+}
+
+bool Model_Document::moveToFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const std::shared_ptr<ModelAPI_Folder>& theFolder)
+{
+  return myObjs->moveToFolder(theFeatures, theFolder);
+}
+
+bool Model_Document::removeFromFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const bool theBefore)
+{
+  return myObjs->removeFromFolder(theFeatures, theBefore);
+}
+
 std::shared_ptr<ModelAPI_Feature> Model_Document::feature(
     const std::shared_ptr<ModelAPI_Result>& theResult)
 {
@@ -1489,6 +1537,11 @@ std::list<std::shared_ptr<ModelAPI_Feature> > Model_Document::allFeatures()
   return myObjs->allFeatures();
 }
 
+std::list<std::shared_ptr<ModelAPI_Object> > Model_Document::allObjects()
+{
+  return myObjs->allObjects();
+}
+
 void Model_Document::setActive(const bool theFlag)
 {
   if (theFlag != myIsActive) {
index b6358eb4f18f44a03bd781f75483020d3e404a25..0f6b32ed6e8093863a453ecf130ee42d200155d7 100644 (file)
@@ -122,8 +122,10 @@ class Model_Document : public ModelAPI_Document
 
   //! Returns the object index in the group. Object must be visible. Otherwise returns -1.
   //! \param theObject object of this document
+  //! \param theAllowFolder take into account grouping feature by folders
   //! \returns index started from zero, or -1 if object is invisible or belongs to another document
-  MODEL_EXPORT virtual const int index(std::shared_ptr<ModelAPI_Object> theObject);
+  MODEL_EXPORT virtual const int index(std::shared_ptr<ModelAPI_Object> theObject,
+                                       const bool theAllowFolder = false);
 
   //! Internal sub-document by ID
   MODEL_EXPORT virtual std::shared_ptr<Model_Document> subDoc(int theDocID);
@@ -137,10 +139,15 @@ class Model_Document : public ModelAPI_Document
   //! Returns the feature in the group by the index (started from zero)
   //! \param theGroupID group that contains a feature
   //! \param theIndex zero-based index of feature in the group
-  MODEL_EXPORT virtual ObjectPtr object(const std::string& theGroupID, const int theIndex);
+  //! \param theAllowFolder take into account grouping feature by folders
+  MODEL_EXPORT virtual ObjectPtr object(const std::string& theGroupID,
+                                        const int theIndex,
+                                        const bool theAllowFolder = false);
 
   //! Returns the number of features in the group
-  MODEL_EXPORT virtual int size(const std::string& theGroupID);
+  //! \param theGroupID group of objects
+  //! \param theAllowFolder take into account grouping feature by folders
+  MODEL_EXPORT virtual int size(const std::string& theGroupID, const bool theAllowFolder = false);
 
   //! Returns the feature that is currently edited in this document, normally
   //! this is the latest created feature
@@ -199,6 +206,47 @@ class Model_Document : public ModelAPI_Document
   MODEL_EXPORT virtual std::shared_ptr<ModelAPI_Feature>
     feature(const std::shared_ptr<ModelAPI_Result>& theResult);
 
+  //! Creates a folder (group of the features in the object browser)
+  //! \param theAddBefore a feature, the folder is added before
+  //!                     (if empty, the folder is added after the last feature)
+  MODEL_EXPORT virtual std::shared_ptr<ModelAPI_Folder> addFolder(
+      std::shared_ptr<ModelAPI_Feature> theAddBefore = std::shared_ptr<ModelAPI_Feature>());
+  //! Removes the folder from the document (all features in the folder will be kept).
+  MODEL_EXPORT virtual void removeFolder(std::shared_ptr<ModelAPI_Folder> theFolder);
+  //! Search a folder above the list of features applicable to store them
+  //! (it means the list of features stored in the folder should be consequential)
+  //! \return Empty pointer if there is no applicable folder
+  MODEL_EXPORT virtual std::shared_ptr<ModelAPI_Folder> findFolderAbove(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures);
+  //! Search a folder below the list of features applicable to store them
+  //! (it means the list of features stored in the folder should be consequential)
+  //! \return Empty pointer if there is no applicable folder
+  MODEL_EXPORT virtual std::shared_ptr<ModelAPI_Folder> findFolderBelow(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures);
+  //! Search a folder containing the given feature.
+  //! Addtionally calculates a zero-based index of the feature in this folder.
+  //! \param theFeature feature to search
+  //! \param theIndexInFolder zero-based index in the folder or -1 if the feature is top-level.
+  //! \return the folder containing the feature or empty pointer if the feature is top-level.
+  MODEL_EXPORT virtual std::shared_ptr<ModelAPI_Folder> findContainingFolder(
+      const std::shared_ptr<ModelAPI_Feature>& theFeature,
+      int& theIndexInFolder);
+  //! Add a list of features to the folder. The correctness of the adding is not performed
+  //! (such checks have been done in corresponding find.. method).
+  //! \return \c true if the movement is successfull
+  MODEL_EXPORT virtual bool moveToFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const std::shared_ptr<ModelAPI_Folder>& theFolder);
+  //! Remove features from the folder
+  //! \param theFeatures list of features to be removed
+  //! \param theBefore   extract features before the folder (this parameter is applicable only
+  //!                    when all features in the folder are taking out,
+  //!                    in other cases the direction is taken automatically)
+  //! \return \c true if the features have been moved out
+  MODEL_EXPORT virtual bool removeFromFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const bool theBefore = true);
+
   ///! Returns true if parametric updater need to execute feature on recomputartion
   ///! On abort, undo or redo it is not necessary: results in document are updated automatically
   bool executeFeatures() {return myExecuteFeatures;}
@@ -224,6 +272,10 @@ class Model_Document : public ModelAPI_Document
   ///! history. Not very fast method, for calling once, not in big cycles.
   MODEL_EXPORT virtual std::list<std::shared_ptr<ModelAPI_Feature> > allFeatures();
 
+  //! Returns all objects of the document including the hidden features which are not in
+  //! history. Not very fast method, for calling once, not in big cycles.
+  MODEL_EXPORT virtual std::list<std::shared_ptr<ModelAPI_Object> > allObjects();
+
   /// Returns the global identifier of the current transaction (needed for the update algo)
   MODEL_EXPORT virtual int transactionID();
   /// Increases the transaction ID
index 17c62335760b13c2b0b1f26e037b2ef1a1b9f565..3a933410b1bdb405ffb5fead5fb907c5e5e9e414 100644 (file)
 #include <TDF_LabelMap.hxx>
 #include <TDF_ListIteratorOfLabelList.hxx>
 
+static const std::string& groupNameFoldering(const std::string& theGroupID,
+                                             const bool theAllowFolder)
+{
+  if (theAllowFolder) {
+    static const std::string anOutOfFolderName = std::string("__") + ModelAPI_Feature::group();
+    static const std::string aDummyName;
+    return theGroupID == ModelAPI_Feature::group() ? anOutOfFolderName : aDummyName;
+  }
+  return theGroupID;
+}
+
+
 static const int TAG_OBJECTS = 2;  // tag of the objects sub-tree (features, results)
 
 // feature sub-labels
@@ -92,6 +104,15 @@ Model_Objects::~Model_Objects()
     aFeature->erase();
     myFeatures.UnBind(aFeaturesIter.Key());
   }
+  while (!myFolders.IsEmpty()) {
+    NCollection_DataMap<TDF_Label, ObjectPtr>::Iterator aFoldersIter(myFolders);
+    ObjectPtr aFolder = aFoldersIter.Value();
+    static Events_ID EVENT_DISP = aLoop->eventByName(EVENT_OBJECT_TO_REDISPLAY);
+    ModelAPI_EventCreator::get()->sendDeleted(myDoc, ModelAPI_Folder::group());
+    ModelAPI_EventCreator::get()->sendUpdated(aFolder, EVENT_DISP);
+    aFolder->erase();
+    myFolders.UnBind(aFoldersIter.Key());
+  }
   myHistory.clear();
   aLoop->activateFlushes(isActive);
   // erase update, because features are destroyed and update should not performed for them anywhere
@@ -367,9 +388,8 @@ void Model_Objects::clearHistory(ObjectPtr theObj)
 {
   if (theObj.get()) {
     const std::string aGroup = theObj->groupName();
-    std::map<std::string, std::vector<ObjectPtr> >::iterator aHIter = myHistory.find(aGroup);
-    if (aHIter != myHistory.end())
-      myHistory.erase(aHIter); // erase from map => this means that it is not synchronized
+    updateHistory(aGroup);
+
     if (theObj->groupName() == ModelAPI_Feature::group()) { // clear results group of the feature
       FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theObj);
       std::string aResultGroup = featureResultGroup(aFeature);
@@ -387,9 +407,12 @@ void Model_Objects::createHistory(const std::string& theGroupID)
 {
   std::map<std::string, std::vector<ObjectPtr> >::iterator aHIter = myHistory.find(theGroupID);
   if (aHIter == myHistory.end()) {
-    std::vector<ObjectPtr> aResult = std::vector<ObjectPtr>();
+    std::vector<ObjectPtr> aResult;
+    std::vector<ObjectPtr> aResultOutOfFolder;
+    FeaturePtr aLastFeatureInFolder;
     // iterate the array of references and get feature by feature from the array
     bool isFeature = theGroupID == ModelAPI_Feature::group();
+    bool isFolder = theGroupID == ModelAPI_Folder::group();
     Handle(TDataStd_ReferenceArray) aRefs;
     if (featuresLabel().FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) {
       for(int a = aRefs->Lower(); a <= aRefs->Upper(); a++) {
@@ -401,6 +424,9 @@ void Model_Objects::createHistory(const std::string& theGroupID)
           if (isFeature) { // here may be also disabled features
             if (!isSub && aFeature->isInHistory()) {
               aResult.push_back(aFeature);
+              // the feature is out of the folders
+              if (aLastFeatureInFolder.get() == NULL)
+                aResultOutOfFolder.push_back(aFeature);
             }
           } else if (!aFeature->isDisabled()) { // iterate all results of not-disabled feature
             // construction results of sub-features should not be in the tree
@@ -418,12 +444,40 @@ void Model_Objects::createHistory(const std::string& theGroupID)
               }
             }
           }
+
+          // the feature closes the folder, so the next features will be treated as out-of-folder
+          if (aLastFeatureInFolder.get() && aLastFeatureInFolder == aFeature)
+            aLastFeatureInFolder = FeaturePtr();
+
+        } else {
+          // it may be a folder
+          ObjectPtr aFolder = folder(aRefs->Value(a));
+          if (aFolder.get()) {
+            // store folder information for the Features group only
+            if (isFeature || isFolder) {
+              aResult.push_back(aFolder);
+              if (!isFolder)
+                aResultOutOfFolder.push_back(aFolder);
+            }
+
+            // get the last feature in the folder
+            AttributeReferencePtr aLastFeatAttr =
+                aFolder->data()->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+            if (aLastFeatAttr)
+              aLastFeatureInFolder = ModelAPI_Feature::feature(aLastFeatAttr->value());
+          }
         }
       }
     }
     // to be sure that isConcealed did not update the history (issue 1089) during the iteration
-    if (myHistory.find(theGroupID) == myHistory.end())
+    if (myHistory.find(theGroupID) == myHistory.end()) {
       myHistory[theGroupID] = aResult;
+
+      // store the features placed out of any folder
+      const std::string& anOutOfFolderGroupID = groupNameFoldering(theGroupID, true);
+      if (!anOutOfFolderGroupID.empty())
+        myHistory[anOutOfFolderGroupID] = aResultOutOfFolder;
+    }
   }
 }
 
@@ -435,8 +489,21 @@ void Model_Objects::updateHistory(const std::shared_ptr<ModelAPI_Object> theObje
 void Model_Objects::updateHistory(const std::string theGroup)
 {
   std::map<std::string, std::vector<ObjectPtr> >::iterator aHIter = myHistory.find(theGroup);
-  if (aHIter != myHistory.end())
+  if (aHIter != myHistory.end()) {
     myHistory.erase(aHIter); // erase from map => this means that it is not synchronized
+
+    // erase history for the group of objects placed out of any folder
+    const std::string& anOutOfFolderGroupID = groupNameFoldering(theGroup, true);
+    if (!anOutOfFolderGroupID.empty())
+      myHistory.erase(anOutOfFolderGroupID);
+  }
+}
+
+ObjectPtr Model_Objects::folder(TDF_Label theLabel) const
+{
+  if (myFolders.IsBound(theLabel))
+    return myFolders.Find(theLabel);
+  return ObjectPtr();
 }
 
 FeaturePtr Model_Objects::feature(TDF_Label theLabel) const
@@ -489,12 +556,15 @@ ObjectPtr Model_Objects::object(TDF_Label theLabel)
   return FeaturePtr();  // not found
 }
 
-ObjectPtr Model_Objects::object(const std::string& theGroupID, const int theIndex)
+ObjectPtr Model_Objects::object(const std::string& theGroupID,
+                                const int theIndex,
+                                const bool theAllowFolder)
 {
   if (theIndex == -1)
     return ObjectPtr();
   createHistory(theGroupID);
-  return myHistory[theGroupID][theIndex];
+  const std::string& aGroupID = groupNameFoldering(theGroupID, theAllowFolder);
+  return aGroupID.empty() ? myHistory[theGroupID][theIndex] : myHistory[aGroupID][theIndex];
 }
 
 std::shared_ptr<ModelAPI_Object> Model_Objects::objectByName(
@@ -538,10 +608,19 @@ std::shared_ptr<ModelAPI_Object> Model_Objects::objectByName(
   return ObjectPtr();
 }
 
-const int Model_Objects::index(std::shared_ptr<ModelAPI_Object> theObject)
+const int Model_Objects::index(std::shared_ptr<ModelAPI_Object> theObject,
+                               const bool theAllowFolder)
 {
   std::string aGroup = theObject->groupName();
+  // treat folder as feature
+  if (aGroup == ModelAPI_Folder::group())
+    aGroup = ModelAPI_Feature::group();
   createHistory(aGroup);
+
+  // get the group of features out of folder (if enabled)
+  if (theAllowFolder && !groupNameFoldering(aGroup, theAllowFolder).empty())
+    aGroup = groupNameFoldering(aGroup, theAllowFolder);
+
   std::vector<ObjectPtr>& allObjs = myHistory[aGroup];
   std::vector<ObjectPtr>::iterator anObjIter = allObjs.begin(); // iterate to search object
   for(int anIndex = 0; anObjIter != allObjs.end(); anObjIter++, anIndex++) {
@@ -552,10 +631,11 @@ const int Model_Objects::index(std::shared_ptr<ModelAPI_Object> theObject)
   return -1;
 }
 
-int Model_Objects::size(const std::string& theGroupID)
+int Model_Objects::size(const std::string& theGroupID, const bool theAllowFolder)
 {
   createHistory(theGroupID);
-  return int(myHistory[theGroupID].size());
+  const std::string& aGroupID = groupNameFoldering(theGroupID, theAllowFolder);
+  return aGroupID.empty() ? int(myHistory[theGroupID].size()) : int(myHistory[aGroupID].size());
 }
 
 void Model_Objects::allResults(const std::string& theGroupID, std::list<ResultPtr>& theResults)
@@ -587,6 +667,13 @@ TDF_Label Model_Objects::featuresLabel() const
   return myMain.FindChild(TAG_OBJECTS);
 }
 
+static std::string composeName(const std::string& theFeatureKind, const int theIndex)
+{
+  std::stringstream aNameStream;
+  aNameStream << theFeatureKind << "_" << theIndex;
+  return aNameStream.str();
+}
+
 void Model_Objects::setUniqueName(FeaturePtr theFeature)
 {
   if (!theFeature->data()->name().empty())
@@ -600,9 +687,7 @@ void Model_Objects::setUniqueName(FeaturePtr theFeature)
       aNumObjects++;
   }
   // generate candidate name
-  std::stringstream aNameStream;
-  aNameStream << theFeature->getKind() << "_" << aNumObjects + 1;
-  aName = aNameStream.str();
+  aName = composeName(theFeature->getKind(), aNumObjects + 1);
   // check this is unique, if not, increase index by 1
   for (aFIter.Initialize(myFeatures); aFIter.More();) {
     FeaturePtr aFeature = aFIter.Value();
@@ -617,9 +702,7 @@ void Model_Objects::setUniqueName(FeaturePtr theFeature)
 
     if (isSameName) {
       aNumObjects++;
-      std::stringstream aNameStream;
-      aNameStream << theFeature->getKind() << "_" << aNumObjects + 1;
-      aName = aNameStream.str();
+      aName = composeName(theFeature->getKind(), aNumObjects + 1);
       // reinitialize iterator to make sure a new name is unique
       aFIter.Initialize(myFeatures);
     } else
@@ -628,6 +711,28 @@ void Model_Objects::setUniqueName(FeaturePtr theFeature)
   theFeature->data()->setName(aName);
 }
 
+void Model_Objects::setUniqueName(FolderPtr theFolder)
+{
+  if (!theFolder->name().empty())
+    return; // name is already defined
+
+  int aNbFolders = myFolders.Size();
+  std::string aName = composeName(ModelAPI_Folder::ID(), aNbFolders);
+
+  // check the uniqueness of the name
+  NCollection_DataMap<TDF_Label, ObjectPtr>::Iterator anIt(myFolders);
+  while (anIt.More()) {
+    if (anIt.Value()->data()->name() == aName) {
+      aName = composeName(ModelAPI_Folder::ID(), aNbFolders);
+      // reinitialize iterator to make sure a new name is unique
+      anIt.Initialize(myFolders);
+    } else
+      anIt.Next();
+  }
+
+  theFolder->data()->setName(aName);
+}
+
 void Model_Objects::initData(ObjectPtr theObj, TDF_Label theLab, const int theTag)
 {
   std::shared_ptr<Model_Data> aData(new Model_Data);
@@ -638,6 +743,10 @@ void Model_Objects::initData(ObjectPtr theObj, TDF_Label theLab, const int theTa
   FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theObj);
   if (aFeature.get()) {
     setUniqueName(aFeature);  // must be before "initAttributes" because duplicate part uses name
+  } else { // is it a folder?
+    FolderPtr aFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(theObj);
+    if (aFolder)
+      setUniqueName(aFolder);
   }
   theObj->initAttributes();
 }
@@ -680,16 +789,23 @@ void Model_Objects::synchronizeFeatures(
   }
 
   // update all objects by checking are they on labels or not
-  std::set<FeaturePtr> aNewFeatures, aKeptFeatures;
+  std::set<ObjectPtr> aNewFeatures, aKeptFeatures;
   TDF_ChildIDIterator aLabIter(featuresLabel(), TDataStd_Comment::GetID());
   for (; aLabIter.More(); aLabIter.Next()) {
     TDF_Label aFeatureLabel = aLabIter.Value()->Label();
-    FeaturePtr aFeature;
-    if (!myFeatures.IsBound(aFeatureLabel)) {  // a new feature is inserted
+    if (!myFeatures.IsBound(aFeatureLabel) && !myFolders.IsBound(aFeatureLabel)) {
+      // a new feature or folder is inserted
+
+      std::string aFeatureID = TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast(
+                               aLabIter.Value())->Get()).ToCString();
+      bool isFolder = aFeatureID == ModelAPI_Folder::ID();
+
+      std::shared_ptr<Model_Session> aSession =
+          std::dynamic_pointer_cast<Model_Session>(ModelAPI_Session::get());
+
       // create a feature
-      aFeature = std::dynamic_pointer_cast<Model_Session>(ModelAPI_Session::get())->createFeature(
-        TCollection_AsciiString(Handle(TDataStd_Comment)::DownCast(aLabIter.Value())->Get())
-        .ToCString(), anOwner);
+      ObjectPtr aFeature = isFolder ? ObjectPtr(new ModelAPI_Folder)
+                                    : ObjectPtr(aSession->createFeature(aFeatureID, anOwner));
       if (!aFeature.get()) {
         // somethig is wrong, most probably, the opened document has invalid structure
         Events_InfoMessage("Model_Objects", "Invalid type of object in the document").send();
@@ -698,7 +814,10 @@ void Model_Objects::synchronizeFeatures(
       }
       aFeature->init();
       // this must be before "setData" to redo the sketch line correctly
-      myFeatures.Bind(aFeatureLabel, aFeature);
+      if (isFolder)
+        myFolders.Bind(aFeatureLabel, aFeature);
+      else
+        myFeatures.Bind(aFeatureLabel, std::dynamic_pointer_cast<ModelAPI_Feature>(aFeature));
       aNewFeatures.insert(aFeature);
       initData(aFeature, aFeatureLabel, TAG_FEATURE_ARGUMENTS);
       updateHistory(aFeature);
@@ -706,18 +825,25 @@ void Model_Objects::synchronizeFeatures(
       // event: model is updated
       ModelAPI_EventCreator::get()->sendUpdated(aFeature, aCreateEvent);
     } else {  // nothing is changed, both iterators are incremented
-      aFeature = myFeatures.Find(aFeatureLabel);
-      aKeptFeatures.insert(aFeature);
+      ObjectPtr anObject;
+      FeaturePtr aFeature;
+      if (myFeatures.Find(aFeatureLabel, aFeature)) {
+        aKeptFeatures.insert(aFeature);
+        anObject = aFeature;
+      } else
+        if (myFolders.Find(aFeatureLabel, anObject))
+          aKeptFeatures.insert(anObject);
+
       if (anUpdatedMap.Contains(aFeatureLabel)) {
         if (!theOpen) { // on abort/undo/redo reinitialize attributes if something is changed
           std::list<std::shared_ptr<ModelAPI_Attribute> > anAttrs =
-            aFeature->data()->attributes("");
+            anObject->data()->attributes("");
           std::list<std::shared_ptr<ModelAPI_Attribute> >::iterator anAttr = anAttrs.begin();
           for(; anAttr != anAttrs.end(); anAttr++)
             (*anAttr)->reinit();
         }
-        ModelAPI_EventCreator::get()->sendUpdated(aFeature, anUpdateEvent);
-        if (aFeature->getKind() == "Parameter") {
+        ModelAPI_EventCreator::get()->sendUpdated(anObject, anUpdateEvent);
+        if (aFeature && aFeature->getKind() == "Parameter") {
           // if parameters are changed, update the results (issue 937)
           const std::list<std::shared_ptr<ModelAPI_Result> >& aResults = aFeature->results();
           std::list<std::shared_ptr<ModelAPI_Result> >::const_iterator aRIter = aResults.begin();
@@ -756,6 +882,24 @@ void Model_Objects::synchronizeFeatures(
     } else
       aFIter.Next();
   }
+  // verify folders are checked: if not => is was removed
+  for (NCollection_DataMap<TDF_Label, ObjectPtr>::Iterator aFldIt(myFolders);
+       aFldIt.More(); aFldIt.Next()) {
+    ObjectPtr aCurObj = aFldIt.Value();
+    if (aKeptFeatures.find(aCurObj) == aKeptFeatures.end() &&
+        aNewFeatures.find(aCurObj) == aNewFeatures.end()) {
+      ModelAPI_EventCreator::get()->sendDeleted(myDoc, ModelAPI_Folder::group());
+      // results of this feature must be redisplayed (hided)
+      // redisplay also removed feature (used for sketch and AISObject)
+      ModelAPI_EventCreator::get()->sendUpdated(aCurObj, aRedispEvent);
+      updateHistory(aCurObj);
+      aCurObj->erase();
+
+      // unbind after the "erase" call: on abort sketch
+      // is removes sub-objects that corrupts aFIter
+      myFolders.UnBind(aFldIt.Key());
+    }
+  }
 
   if (theUpdateReferences) {
     synchronizeBackRefs();
@@ -813,7 +957,10 @@ void Model_Objects::synchronizeBackRefsForObject(const std::set<AttributePtr>& t
   for(; aNewIter != theNewRefs.end(); aNewIter++) {
     if (aData->refsToMe().find(*aNewIter) == aData->refsToMe().end()) {
       FeaturePtr aRefFeat = std::dynamic_pointer_cast<ModelAPI_Feature>((*aNewIter)->owner());
-      aData->addBackReference(aRefFeat, (*aNewIter)->id());
+      if (aRefFeat)
+        aData->addBackReference(aRefFeat, (*aNewIter)->id());
+      else // add back reference to a folder
+        aData->addBackReference((*aNewIter)->owner(), (*aNewIter)->id());
     }
   }
   if (theNewRefs.size() != aData->refsToMe().size()) { // some back ref must be removed
@@ -852,6 +999,29 @@ void Model_Objects::synchronizeBackRefsForObject(const std::set<AttributePtr>& t
   aData->updateConcealmentFlag();
 }
 
+static void collectReferences(std::shared_ptr<ModelAPI_Data> theData,
+                              std::map<ObjectPtr, std::set<AttributePtr> >& theRefs)
+{
+  if (theData.get()) {
+    std::list<std::pair<std::string, std::list<ObjectPtr> > > aRefs;
+    theData->referencesToObjects(aRefs);
+    std::list<std::pair<std::string, std::list<ObjectPtr> > >::iterator aRefsIt = aRefs.begin();
+    for(; aRefsIt != aRefs.end(); aRefsIt++) {
+      std::list<ObjectPtr>::iterator aRefTo = aRefsIt->second.begin();
+      for(; aRefTo != aRefsIt->second.end(); aRefTo++) {
+        if (*aRefTo) {
+          std::map<ObjectPtr, std::set<AttributePtr> >::iterator aFound = theRefs.find(*aRefTo);
+          if (aFound == theRefs.end()) {
+            theRefs[*aRefTo] = std::set<AttributePtr>();
+            aFound = theRefs.find(*aRefTo);
+          }
+          aFound->second.insert(theData->attribute(aRefsIt->first));
+        }
+      }
+    }
+  }
+}
+
 void Model_Objects::synchronizeBackRefs()
 {
   // collect all back references in the separated container: to update everything at once,
@@ -862,25 +1032,12 @@ void Model_Objects::synchronizeBackRefs()
   NCollection_DataMap<TDF_Label, FeaturePtr>::Iterator aFeatures(myFeatures);
   for(; aFeatures.More(); aFeatures.Next()) {
     FeaturePtr aFeature = aFeatures.Value();
-    std::shared_ptr<Model_Data> aFData = std::dynamic_pointer_cast<Model_Data>(aFeature->data());
-    if (aFData.get()) {
-      std::list<std::pair<std::string, std::list<ObjectPtr> > > aRefs;
-      aFData->referencesToObjects(aRefs);
-      std::list<std::pair<std::string, std::list<ObjectPtr> > >::iterator aRefsIt = aRefs.begin();
-      for(; aRefsIt != aRefs.end(); aRefsIt++) {
-        std::list<ObjectPtr>::iterator aRefTo = aRefsIt->second.begin();
-        for(; aRefTo != aRefsIt->second.end(); aRefTo++) {
-          if (*aRefTo) {
-            std::map<ObjectPtr, std::set<AttributePtr> >::iterator aFound = allRefs.find(*aRefTo);
-            if (aFound == allRefs.end()) {
-              allRefs[*aRefTo] = std::set<AttributePtr>();
-              aFound = allRefs.find(*aRefTo);
-            }
-            aFound->second.insert(aFeature->data()->attribute(aRefsIt->first));
-          }
-        }
-      }
-    }
+    collectReferences(aFeature->data(), allRefs);
+  }
+  NCollection_DataMap<TDF_Label, ObjectPtr>::Iterator aFolders(myFolders);
+  for(; aFolders.More(); aFolders.Next()) {
+    ObjectPtr aFolder = aFolders.Value();
+    collectReferences(aFolder->data(), allRefs);
   }
   // second iteration: just compare back-references with existing in features and results
   for(aFeatures.Initialize(myFeatures); aFeatures.More(); aFeatures.Next()) {
@@ -1111,6 +1268,442 @@ std::shared_ptr<ModelAPI_ResultParameter> Model_Objects::createParameter(
   return aResult;
 }
 
+std::shared_ptr<ModelAPI_Folder> Model_Objects::createFolder(
+    const std::shared_ptr<ModelAPI_Feature>& theBeforeThis)
+{
+  FolderPtr aFolder(new ModelAPI_Folder);
+  if (!aFolder)
+    return aFolder;
+
+  TDF_Label aFeaturesLab = featuresLabel();
+  TDF_Label aFolderLab = aFeaturesLab.NewChild();
+  // store feature in the features array: before "initData" because in macro features
+  // in initData it creates new features, appeared later than this
+  TDF_Label aPrevFeatureLab;
+  if (theBeforeThis.get()) { // searching for the previous feature label
+    std::shared_ptr<Model_Data> aPrevData =
+        std::dynamic_pointer_cast<Model_Data>(theBeforeThis->data());
+    if (aPrevData.get()) {
+      aPrevFeatureLab = nextLabel(aPrevData->label().Father(), true);
+    }
+  } else { // find the label of the last feature
+    Handle(TDataStd_ReferenceArray) aRefs;
+    if (aFeaturesLab.FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs))
+      aPrevFeatureLab = aRefs->Value(aRefs->Upper());
+  }
+  AddToRefArray(aFeaturesLab, aFolderLab, aPrevFeatureLab);
+
+  // keep the feature ID to restore document later correctly
+  TDataStd_Comment::Set(aFolderLab, ModelAPI_Folder::ID().c_str());
+  myFolders.Bind(aFolderLab, aFolder);
+  // must be before the event sending: for OB the feature is already added
+  updateHistory(ModelAPI_Folder::group());
+  updateHistory(ModelAPI_Feature::group());
+
+  // must be after binding to the map because of "Box" macro feature that
+  // creates other features in "initData"
+  initData(aFolder, aFolderLab, TAG_FEATURE_ARGUMENTS);
+  // event: folder is added, must be before "initData" to update OB correctly on Duplicate:
+  // first new part, then the content
+  static Events_ID anEvent = Events_Loop::eventByName(EVENT_OBJECT_CREATED);
+  ModelAPI_EventCreator::get()->sendUpdated(aFolder, anEvent);
+
+  return aFolder;
+}
+
+void Model_Objects::removeFolder(std::shared_ptr<ModelAPI_Folder> theFolder)
+{
+  std::shared_ptr<Model_Data> aData = std::static_pointer_cast<Model_Data>(theFolder->data());
+  if (!aData.get() || !aData->isValid())
+    return;
+
+  // this must be before erase since theFolder erasing removes all information about it
+  clearHistory(theFolder);
+  // erase fields
+  theFolder->erase();
+
+  TDF_Label aFolderLabel = aData->label().Father();
+  if (myFolders.IsBound(aFolderLabel))
+    myFolders.UnBind(aFolderLabel);
+
+  static Events_ID EVENT_DISP = Events_Loop::loop()->eventByName(EVENT_OBJECT_TO_REDISPLAY);
+  ModelAPI_EventCreator::get()->sendUpdated(theFolder, EVENT_DISP);
+  // erase all attributes under the label of feature
+  aFolderLabel.ForgetAllAttributes();
+  // remove it from the references array
+  RemoveFromRefArray(featuresLabel(), aFolderLabel);
+  // event: feature is deleted
+  ModelAPI_EventCreator::get()->sendDeleted(theFolder->document(), ModelAPI_Folder::group());
+  updateHistory(ModelAPI_Folder::group());
+  updateHistory(ModelAPI_Feature::group());
+}
+
+// Returns one of the limiting features of the list
+static FeaturePtr limitingFeature(std::list<FeaturePtr>& theFeatures, const bool isLast)
+{
+  FeaturePtr aFeature;
+  if (isLast) {
+    aFeature = theFeatures.back();
+    theFeatures.pop_back();
+  } else {
+    aFeature = theFeatures.front();
+    theFeatures.pop_front();
+  }
+  return aFeature;
+}
+
+// Verify the feature is sub-element in composite feature or it is not used in the history
+static bool isSkippedFeature(FeaturePtr theFeature)
+{
+  bool isSub = ModelAPI_Tools::compositeOwner(theFeature).get() != NULL;
+  return isSub || (theFeature && !theFeature->isInHistory());
+}
+
+std::shared_ptr<ModelAPI_Folder> Model_Objects::findFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const bool theBelow)
+{
+  if (theFeatures.empty())
+    return FolderPtr(); // nothing to move
+
+  TDF_Label aFeaturesLab = featuresLabel();
+  Handle(TDataStd_ReferenceArray) aRefs;
+  if (!aFeaturesLab.FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs))
+    return FolderPtr(); // no reference array (something is wrong)
+
+  std::list<std::shared_ptr<ModelAPI_Feature> > aFeatures = theFeatures;
+  std::shared_ptr<ModelAPI_Feature> aLimitingFeature = limitingFeature(aFeatures, theBelow);
+
+  std::shared_ptr<Model_Data> aData =
+      std::static_pointer_cast<Model_Data>(aLimitingFeature->data());
+  if (!aData || !aData->isValid())
+    return FolderPtr(); // invalid feature
+
+  // label of the first feature in the list for fast searching
+  TDF_Label aFirstFeatureLabel = aData->label().Father();
+
+  // find a folder above the features and
+  // check the given features represent a sequential list of objects following the folder
+  FolderPtr aFoundFolder;
+  TDF_Label aLastFeatureInFolder;
+  int aRefIndex = aRefs->Lower();
+  for(; aRefIndex <= aRefs->Upper(); ++aRefIndex) { // iterate all existing features
+    TDF_Label aCurLabel = aRefs->Value(aRefIndex);
+    if (IsEqual(aCurLabel, aFirstFeatureLabel))
+      break; // no need to continue searching
+
+    // searching the folder below, just continue to search last feature from the list
+    if (theBelow)
+      continue;
+
+    // if feature is in sub-component, skip it
+    FeaturePtr aCurFeature = feature(aCurLabel);
+    if (isSkippedFeature(aCurFeature))
+      continue;
+
+    if (!aLastFeatureInFolder.IsNull()) {
+      if (IsEqual(aCurLabel, aLastFeatureInFolder))
+        aLastFeatureInFolder.Nullify(); // the last feature in the folder is achived
+      continue;
+    }
+
+    aFoundFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(folder(aCurLabel));
+    if (aFoundFolder) {
+      AttributeReferencePtr aLastFeatAttr =
+          aFoundFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+      if (aLastFeatAttr) {
+        // setup iterating inside a folder to find last feature
+        ObjectPtr aLastFeature = aLastFeatAttr->value();
+        if (aLastFeature) {
+          aData = std::static_pointer_cast<Model_Data>(aLastFeature->data());
+          if (aData && aData->isValid())
+            aLastFeatureInFolder = aData->label().Father();
+        }
+      }
+    }
+  }
+
+  if (theBelow && aRefIndex < aRefs->Upper()) {
+    TDF_Label aLabel;
+    // skip following features which are sub-components or not in history
+    for (int anIndex = aRefIndex + 1; anIndex <= aRefs->Upper(); ++anIndex) {
+      aLabel = aRefs->Value(anIndex);
+      FeaturePtr aCurFeature = feature(aLabel);
+      if (!isSkippedFeature(aCurFeature))
+        break;
+    }
+    // check the next object is a folder
+    aFoundFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(folder(aLabel));
+  }
+
+  if (!aLastFeatureInFolder.IsNull() || // the last feature of the folder above is not found
+      !aFoundFolder)
+    return FolderPtr();
+
+  // check the given features are sequential list
+  int aStep = theBelow ? -1 : 1;
+  for (aRefIndex += aStep;
+       !aFeatures.empty() && aRefIndex >= aRefs->Lower() && aRefIndex <= aRefs->Upper();
+       aRefIndex += aStep) {
+    TDF_Label aCurLabel = aRefs->Value(aRefIndex);
+    // if feature is in sub-component, skip it
+    FeaturePtr aCurFeature = feature(aCurLabel);
+    if (isSkippedFeature(aCurFeature))
+      continue;
+
+    aLimitingFeature = limitingFeature(aFeatures, theBelow);
+    if (!aCurFeature->data()->isEqual(aLimitingFeature->data()))
+      return FolderPtr(); // not a sequential list
+  }
+
+  return aFoundFolder;
+}
+
+bool Model_Objects::moveToFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const std::shared_ptr<ModelAPI_Folder>& theFolder)
+{
+  if (theFeatures.empty() || !theFolder)
+    return false;
+
+  // labels for the folder and last feature in the list
+  TDF_Label aFolderLabel, aLastFeatureLabel;
+  std::shared_ptr<Model_Data> aData =
+      std::static_pointer_cast<Model_Data>(theFolder->data());
+  if (aData && aData->isValid())
+    aFolderLabel = aData->label().Father();
+  aData = std::static_pointer_cast<Model_Data>(theFeatures.back()->data());
+  if (aData && aData->isValid())
+    aLastFeatureLabel = aData->label().Father();
+
+  if (aFolderLabel.IsNull() || aLastFeatureLabel.IsNull())
+    return false;
+
+  AttributeReferencePtr aFirstFeatAttr =
+      theFolder->reference(ModelAPI_Folder::FIRST_FEATURE_ID());
+  AttributeReferencePtr aLastFeatAttr =
+      theFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+  bool initFirstAttr = !aFirstFeatAttr->value().get();
+  bool initLastAttr  = !aLastFeatAttr->value().get();
+
+  // check the folder is below the list of features
+  bool isFolderBelow = false;
+  TDF_Label aFeaturesLab = featuresLabel();
+  Handle(TDataStd_ReferenceArray) aRefs;
+  if (!aFeaturesLab.FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs))
+    return false; // no reference array (something is wrong)
+  for (int aRefIndex = aRefs->Lower(); aRefIndex <= aRefs->Upper(); ++aRefIndex) {
+    TDF_Label aCurLabel = aRefs->Value(aRefIndex);
+    if (aCurLabel == aFolderLabel)
+      break; // folder is above the features
+    else if (aCurLabel == aLastFeatureLabel) {
+      isFolderBelow = true;
+      break;
+    }
+  }
+
+  if (isFolderBelow) {
+    aData = std::static_pointer_cast<Model_Data>(theFeatures.front()->data());
+    if (!aData || !aData->isValid())
+      return false;
+    TDF_Label aPrevFeatureLabel = aData->label().Father();
+    // label of the feature before the first feature in the list
+    for (int aRefIndex = aRefs->Lower(); aRefIndex <= aRefs->Upper(); ++aRefIndex)
+      if (aPrevFeatureLabel == aRefs->Value(aRefIndex)) {
+        if (aRefIndex == aRefs->Lower())
+          aPrevFeatureLabel.Nullify();
+        else
+          aPrevFeatureLabel = aRefs->Value(aRefIndex - 1);
+        break;
+      }
+
+    // move the folder in the list of references before the first feature
+    RemoveFromRefArray(aFeaturesLab, aFolderLabel);
+    AddToRefArray(aFeaturesLab, aFolderLabel, aPrevFeatureLabel);
+    // update first feature of the folder
+    initFirstAttr = true;
+  } else {
+    // update last feature of the folder
+    initLastAttr = true;
+  }
+
+  if (initFirstAttr)
+    aFirstFeatAttr->setValue(theFeatures.front());
+  if (initLastAttr)
+    aLastFeatAttr->setValue(theFeatures.back());
+
+  updateHistory(ModelAPI_Feature::group());
+  return true;
+}
+
+static FolderPtr inFolder(const FeaturePtr& theFeature, const std::string& theFolderAttr)
+{
+  const std::set<AttributePtr>& aRefs = theFeature->data()->refsToMe();
+  for (std::set<AttributePtr>::iterator anIt = aRefs.begin(); anIt != aRefs.end(); ++anIt) {
+    if ((*anIt)->id() != theFolderAttr)
+      continue;
+
+    ObjectPtr anOwner = (*anIt)->owner();
+    FolderPtr aFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(anOwner);
+    if (aFolder.get())
+      return aFolder;
+  }
+  return FolderPtr();
+}
+
+static FolderPtr isExtractionCorrect(const FolderPtr& theFirstFeatureFolder,
+                                     const FolderPtr& theLastFeatureFolder,
+                                     bool& isExtractBefore)
+{
+  if (theFirstFeatureFolder.get()) {
+    if (theLastFeatureFolder.get())
+      return theFirstFeatureFolder == theLastFeatureFolder ? theFirstFeatureFolder : FolderPtr();
+    else
+      isExtractBefore = true;
+    return theFirstFeatureFolder;
+  } else if (theLastFeatureFolder.get()) {
+    isExtractBefore = false;
+    return theLastFeatureFolder;
+  }
+  // no folder found
+  return FolderPtr();
+}
+
+bool Model_Objects::removeFromFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const bool theBefore)
+{
+  if (theFeatures.empty())
+    return false;
+
+  FolderPtr aFirstFeatureFolder =
+      inFolder(theFeatures.front(), ModelAPI_Folder::FIRST_FEATURE_ID());
+  FolderPtr aLastFeatureFolder =
+      inFolder(theFeatures.back(),  ModelAPI_Folder::LAST_FEATURE_ID());
+
+  bool isExtractBeforeFolder = theBefore;
+  FolderPtr aFoundFolder =
+      isExtractionCorrect(aFirstFeatureFolder, aLastFeatureFolder, isExtractBeforeFolder);
+  if (!aFoundFolder)
+    return false; // list of features cannot be extracted
+
+  // references of the current folder
+  ObjectPtr aFolderStartFeature;
+  ObjectPtr aFolderEndFeature;
+  if (aFirstFeatureFolder != aLastFeatureFolder) {
+    aFolderStartFeature = aFoundFolder->reference(ModelAPI_Folder::FIRST_FEATURE_ID())->value();
+    aFolderEndFeature   = aFoundFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID())->value();
+  }
+
+  FeaturePtr aFeatureToFind;
+  if (isExtractBeforeFolder) {
+    aFeatureToFind = theFeatures.back();
+    // if the last feature is composite, obtain its last sub-feature for correct positioning of
+    // the folder in the reference array when theFeatures will be extracted before folder
+    CompositeFeaturePtr aComposite =
+        std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(aFeatureToFind);
+    if (aComposite)
+      aFeatureToFind = aComposite->subFeature(aComposite->numberOfSubs() - 1);
+  } else
+    aFeatureToFind = theFeatures.front();
+  std::shared_ptr<Model_Data> aData =
+      std::static_pointer_cast<Model_Data>(aFeatureToFind->data());
+  if (!aData || !aData->isValid())
+    return false;
+  TDF_Label aLabelToFind = aData->label().Father();
+
+  // search the label in the list of references
+  TDF_Label aFeaturesLab = featuresLabel();
+  Handle(TDataStd_ReferenceArray) aRefs;
+  if (!aFeaturesLab.FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs))
+    return false; // no reference array (something is wrong)
+  int aRefIndex = aRefs->Lower();
+  for (; aRefIndex <= aRefs->Upper(); ++aRefIndex)
+    if (aRefs->Value(aRefIndex) == aLabelToFind)
+      break;
+
+  // update folder position
+  if (isExtractBeforeFolder) {
+    aData = std::dynamic_pointer_cast<Model_Data>(aFoundFolder->data());
+    TDF_Label aFolderLabel = aData->label().Father();
+    TDF_Label aPrevFeatureLabel = aRefs->Value(aRefIndex);
+    // update start reference of the folder
+    if (aFolderStartFeature.get())
+      aFolderStartFeature = feature(aRefs->Value(aRefIndex + 1));
+    // move the folder in the list of references after the last feature from the list
+    RemoveFromRefArray(aFeaturesLab, aFolderLabel);
+    AddToRefArray(aFeaturesLab, aFolderLabel, aPrevFeatureLabel);
+  } else {
+    // update end reference of the folder
+    if (aFolderEndFeature.get())
+      aFolderEndFeature = feature(aRefs->Value(aRefIndex - 1));
+  }
+
+  // update folder references
+  aFoundFolder->reference(ModelAPI_Folder::FIRST_FEATURE_ID())->setValue(aFolderStartFeature);
+  aFoundFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID())->setValue(aFolderEndFeature);
+
+  updateHistory(ModelAPI_Feature::group());
+  return true;
+}
+
+FolderPtr Model_Objects::findContainingFolder(const FeaturePtr& theFeature, int& theIndexInFolder)
+{
+  // search the label in the list of references
+  TDF_Label aFeaturesLab = featuresLabel();
+  Handle(TDataStd_ReferenceArray) aRefs;
+  if (!aFeaturesLab.FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs))
+    return FolderPtr(); // no reference array (something is wrong)
+
+  std::shared_ptr<Model_Data> aData =
+      std::static_pointer_cast<Model_Data>(theFeature->data());
+  if (!aData || !aData->isValid())
+    return FolderPtr();
+  TDF_Label aLabelToFind = aData->label().Father();
+
+  theIndexInFolder = -1;
+  FolderPtr aFoundFolder;
+  TDF_Label aLastFeatureLabel;
+
+  for (int aRefIndex = aRefs->Lower(); aRefIndex <= aRefs->Upper(); ++aRefIndex) {
+    TDF_Label aCurLabel = aRefs->Value(aRefIndex);
+    if (isSkippedFeature(feature(aCurLabel)))
+      continue;
+
+    if (aFoundFolder)
+      ++theIndexInFolder;
+
+    if (aCurLabel == aLabelToFind) // the feature is reached
+      return aFoundFolder;
+
+    if (!aFoundFolder) {
+      // if the current label refers to a folder, feel all necessary data
+      aFoundFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(folder(aCurLabel));
+      if (aFoundFolder) {
+        theIndexInFolder = -1;
+
+        AttributeReferencePtr aLastRef =
+            aFoundFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+        if (aLastRef->value()) {
+          aData = std::static_pointer_cast<Model_Data>(aLastRef->value()->data());
+          if (aData && aData->isValid())
+            aLastFeatureLabel = aData->label().Father();
+        } else // folder is empty
+          aFoundFolder = FolderPtr();
+      }
+    } else if (aLastFeatureLabel == aCurLabel) {
+      // folder is finished, clear all stored data
+      theIndexInFolder = -1;
+      aFoundFolder = FolderPtr();
+    }
+  }
+
+  // folder is not found
+  theIndexInFolder = -1;
+  return FolderPtr();
+}
+
+
 std::shared_ptr<ModelAPI_Feature> Model_Objects::feature(
     const std::shared_ptr<ModelAPI_Result>& theResult)
 {
@@ -1247,23 +1840,31 @@ ResultPtr Model_Objects::findByName(const std::string theName)
   return aResult;
 }
 
+TDF_Label Model_Objects::nextLabel(TDF_Label theCurrent, const bool theReverse)
+{
+  Handle(TDataStd_ReferenceArray) aRefs;
+  if (featuresLabel().FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) {
+    for(int a = aRefs->Lower(); a <= aRefs->Upper(); a++) { // iterate all existing features
+      TDF_Label aCurLab = aRefs->Value(a);
+      if (aCurLab.IsEqual(theCurrent)) {
+        a += theReverse ? -1 : 1;
+        if (a >= aRefs->Lower() && a <= aRefs->Upper())
+          return aRefs->Value(a);
+        break; // finish iiteration: it's last feature
+      }
+    }
+  }
+  return TDF_Label();
+}
+
 FeaturePtr Model_Objects::nextFeature(FeaturePtr theCurrent, const bool theReverse)
 {
   std::shared_ptr<Model_Data> aData = std::static_pointer_cast<Model_Data>(theCurrent->data());
   if (aData.get() && aData->isValid()) {
     TDF_Label aFeatureLabel = aData->label().Father();
-    Handle(TDataStd_ReferenceArray) aRefs;
-    if (featuresLabel().FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) {
-      for(int a = aRefs->Lower(); a <= aRefs->Upper(); a++) { // iterate all existing features
-        TDF_Label aCurLab = aRefs->Value(a);
-        if (aCurLab.IsEqual(aFeatureLabel)) {
-          a += theReverse ? -1 : 1;
-          if (a >= aRefs->Lower() && a <= aRefs->Upper())
-            return feature(aRefs->Value(a));
-          break; // finish iiteration: it's last feature
-        }
-      }
-    }
+    TDF_Label aNextLabel = nextLabel(aFeatureLabel, theReverse);
+    if (!aNextLabel.IsNull())
+      return feature(aNextLabel);
   }
   return FeaturePtr(); // not found, last, or something is wrong
 }
@@ -1311,6 +1912,22 @@ bool Model_Objects::isLater(FeaturePtr theLater, FeaturePtr theCurrent) const
   return false; // not found, or something is wrong
 }
 
+std::list<std::shared_ptr<ModelAPI_Object> > Model_Objects::allObjects()
+{
+  std::list<std::shared_ptr<ModelAPI_Object> > aResult;
+  Handle(TDataStd_ReferenceArray) aRefs;
+  if (featuresLabel().FindAttribute(TDataStd_ReferenceArray::GetID(), aRefs)) {
+    for(int a = aRefs->Lower(); a <= aRefs->Upper(); a++) {
+      ObjectPtr anObject = object(aRefs->Value(a));
+      if (!anObject.get()) // is it a folder?
+        anObject = folder(aRefs->Value(a));
+      if (anObject.get())
+        aResult.push_back(anObject);
+    }
+  }
+  return aResult;
+}
+
 std::list<std::shared_ptr<ModelAPI_Feature> > Model_Objects::allFeatures()
 {
   std::list<std::shared_ptr<ModelAPI_Feature> > aResult;
index 57e02fa3492b539851d737261a4cd394acdf4270..856d3c90b12f87cb96a40e91ada6c4dec8ac0327 100644 (file)
@@ -24,6 +24,7 @@
 #include <Model.h>
 #include <ModelAPI_Document.h>
 #include <ModelAPI_Feature.h>
+#include <ModelAPI_Folder.h>
 #include <ModelAPI_Result.h>
 #include <ModelAPI_ResultParameter.h>
 
@@ -90,16 +91,23 @@ class Model_Objects
 
   //! Returns the object index in the group. Object must be visible. Otherwise returns -1.
   //! \param theObject object of this document
+  //! \param theAllowFolder take into account grouping feature by folders
   //! \returns index started from zero, or -1 if object is invisible or belongs to another document
-  const int index(std::shared_ptr<ModelAPI_Object> theObject);
+  const int index(std::shared_ptr<ModelAPI_Object> theObject,
+                  const bool theAllowFolder = false);
 
   //! Returns the feature in the group by the index (started from zero)
   //! \param theGroupID group that contains a feature
   //! \param theIndex zero-based index of feature in the group
-  ObjectPtr object(const std::string& theGroupID, const int theIndex);
+  //! \param theAllowFolder take into account grouping feature by folders
+  ObjectPtr object(const std::string& theGroupID,
+                   const int theIndex,
+                   const bool theAllowFolder = false);
 
   //! Returns the number of features in the group
-  int size(const std::string& theGroupID);
+  //! \param theGroupID group of objects
+  //! \param theAllowFolder take into account grouping feature by folders
+  int size(const std::string& theGroupID, const bool theAllowFolder = false);
 
   //! Returns all (and disabled) results of the given type.
   //! Not fast method (iterates all features).
@@ -141,6 +149,41 @@ class Model_Objects
   std::shared_ptr<ModelAPI_Feature>
     feature(const std::shared_ptr<ModelAPI_Result>& theResult);
 
+  /// Creates a folder (group of the features in the object browser)
+  std::shared_ptr<ModelAPI_Folder> createFolder(
+      const std::shared_ptr<ModelAPI_Feature>& theBeforeThis);
+  //! Removes the folder from the document (all features in the folder will be kept).
+  void removeFolder(std::shared_ptr<ModelAPI_Folder> theFolder);
+  //! Search a folder applicable for the list of features
+  //! (it means the list of features stored in the folder should be consequential)
+  //! \param theFeatures list of features to be added to folder
+  //! \param theBelow    search the folder below the features (if \c false, search above)
+  //! \return Empty pointer if there is no applicable folder
+  std::shared_ptr<ModelAPI_Folder> findFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const bool theBelow);
+  //! Search a folder containing the given feature.
+  //! Addtionally calculates a zero-based index of the feature in this folder.
+  //! \param theFeature feature to search
+  //! \param theIndexInFolder zero-based index in the folder or -1 if the feature is top-level.
+  //! \return the folder containing the feature or empty pointer if the feature is top-level.
+  std::shared_ptr<ModelAPI_Folder> findContainingFolder(
+      const std::shared_ptr<ModelAPI_Feature>& theFeature,
+      int& theIndexInFolder);
+  //! Add a list of features to the folder. The correctness of the adding is not performed
+  //! (such checks have been done in corresponding find.. method).
+  //! \return \c true if the movement is successfull
+  bool moveToFolder(const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+                    const std::shared_ptr<ModelAPI_Folder>& theFolder);
+  //! Remove features from the folder
+  //! \param theFeatures list of features to be removed
+  //! \param theBefore   extract features before the folder (this parameter is applicable only
+  //!                    when all features in the folder are taking out,
+  //!                    in other cases the direction is taken automatically)
+  //! \return \c true if the features have been moved out
+  bool removeFromFolder(const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+                        const bool theBefore = true);
+
   //! Sets the owner of this manager
   void setOwner(DocumentPtr theDoc);
 
@@ -159,6 +202,9 @@ class Model_Objects
   //! feature type + "_" + index
   void setUniqueName(FeaturePtr theFeature);
 
+  //! Initializes the foldet with an unique name ("Folder_" + index)
+  void setUniqueName(FolderPtr theFolder);
+
   //! Synchronizes myFeatures list with the updated document
   //! \param theUpdated list of labels that are marked as modified, so featrues must be also
   //! \param theUpdateReferences causes the update of back-references
@@ -214,6 +260,11 @@ class Model_Objects
   /// Returns true if theLater is in history of features creation later than theCurrent
   bool isLater(FeaturePtr theLater, FeaturePtr theCurrent) const;
 
+  /// Returns the next or previous label
+  /// \param theCurrent given label
+  /// \param theReverse if it is true, iterates in reverced order (next becomes previous)
+  TDF_Label nextLabel(TDF_Label theCurrent, const bool theReverse = false);
+
   /// Returns the result group identifier of the given feature (for this at least one result must
   /// be created before)
   std::string featureResultGroup(FeaturePtr theFeature);
@@ -222,6 +273,10 @@ class Model_Objects
   //! history. Not very fast method, for calling once, not in big cycles.
   std::list<std::shared_ptr<ModelAPI_Feature> > allFeatures();
 
+  //! Returns all objects of the document including the hidden features which are not in
+  //! history. Not very fast method, for calling once, not in big cycles.
+  std::list<std::shared_ptr<ModelAPI_Object> > allObjects();
+
   //! synchronises back references for the given object basing on the collected data
   void synchronizeBackRefsForObject(
     const std::set<std::shared_ptr<ModelAPI_Attribute>>& theNewRefs, ObjectPtr theObject);
@@ -236,6 +291,9 @@ class Model_Objects
                      int theResultIndex,
                      std::string& theParentName) const;
 
+  /// Return object representing a folder or empty pointer
+  ObjectPtr folder(TDF_Label theLabel) const;
+
  private:
   TDF_Label myMain; ///< main label of the data storage
 
@@ -245,6 +303,9 @@ class Model_Objects
   /// For optimization mapped by labels
   NCollection_DataMap<TDF_Label, FeaturePtr> myFeatures;
 
+  /// Managed folders
+  NCollection_DataMap<TDF_Label, ObjectPtr> myFolders;
+
   /// Map from group id to the array that contains all objects located in history.
   /// Each array is updated by demand from scratch, by browing all the features in the history.
   std::map<std::string, std::vector<ObjectPtr> > myHistory;
index 092bb88a0aef868751259bae3889e33090ee3c72..aa0b87e0ce66083fec1b5cf8caf75cd63307291e 100644 (file)
@@ -51,6 +51,7 @@ SET(PROJECT_HEADERS
     ModelAPI_Expression.h
     ModelAPI_Feature.h
     ModelAPI_FeatureValidator.h
+    ModelAPI_Folder.h
     ModelAPI_IReentrant.h
     ModelAPI_Object.h
     ModelAPI_Plugin.h
@@ -94,6 +95,7 @@ SET(PROJECT_SOURCES
     ModelAPI_Expression.cpp
     ModelAPI_Feature.cpp
     ModelAPI_FeatureValidator.cpp
+    ModelAPI_Folder.cpp
     ModelAPI_IReentrant.cpp
     ModelAPI_Object.cpp
     ModelAPI_Plugin.cpp
@@ -187,4 +189,8 @@ ADD_UNIT_TESTS(TestConstants.py
                TestCustomName_Rename.py
                TestCustomName_RotateGroup.py
                TestCustomName_Translation.py
+               TestFolder_Create.py
+               TestFolder_Update.py
+               TestFolder_Remove.py
+               TestFolder_Stability.py
 )
index 7c0466cdb0152f1ac71ea99f8dd0f5af10bec3b4..61403edf66643f8052bee4eda00678c645211761 100644 (file)
@@ -51,6 +51,7 @@
 %feature("director") ModelAPI_Feature;
 %feature("director") ModelAPI_CompositeFeature;
 %feature("director") ModelAPI_Data;
+%feature("director") ModelAPI_Folder;
 
 // shared pointers
 // For ModelAPI_ResultConstruction.shape()
@@ -62,6 +63,7 @@
 %shared_ptr(ModelAPI_Feature)
 %shared_ptr(ModelAPI_CompositeFeature)
 %shared_ptr(ModelAPI_Data)
+%shared_ptr(ModelAPI_Folder)
 %shared_ptr(ModelAPI_Attribute)
 %shared_ptr(ModelAPI_AttributeDocRef)
 %shared_ptr(ModelAPI_AttributeDouble)
   }
 }
 
+%apply int& OUTPUT {int& theIndexInFolder};
+
 // all supported interfaces
 %include "ModelAPI_Entity.h"
 %include "ModelAPI_Document.h"
 %include "ModelAPI_ResultParameter.h"
 %include "ModelAPI_Tools.h"
 %include "ModelAPI_ResultCompSolid.h"
+%include "ModelAPI_Folder.h"
 
 // std::list -> []
 %template(StringList) std::list<std::string>;
@@ -167,6 +172,7 @@ template<class T1, class T2> std::shared_ptr<T1> shared_ptr_cast(std::shared_ptr
 %template(featureToCompositeFeature) shared_ptr_cast<ModelAPI_CompositeFeature, ModelAPI_Feature>;
 %template(objectToFeature) shared_ptr_cast<ModelAPI_Feature, ModelAPI_Object>;
 %template(objectToResult) shared_ptr_cast<ModelAPI_Result, ModelAPI_Object>;
+%template(objectToFolder) shared_ptr_cast<ModelAPI_Folder, ModelAPI_Object>;
 %template(compositeFeatureToFeature) shared_ptr_cast<ModelAPI_Feature, ModelAPI_CompositeFeature>;
 
 %template(modelAPI_Result) shared_ptr_cast<ModelAPI_Result, ModelAPI_Object>;
index 24d7c4c4bde7c58e1c4120108947845c2fe3f194..e234aefe5a45721b42e061dde2a01a58b0e4702b 100644 (file)
@@ -31,6 +31,7 @@
 #include <set>
 
 class ModelAPI_Feature;
+class ModelAPI_Folder;
 class ModelAPI_Object;
 class ModelAPI_Result;
 class ModelAPI_ResultConstruction;
@@ -89,8 +90,10 @@ public:
   //! Returns the object in the group by the index (started from zero)
   //! \param theGroupID group that contains an object
   //! \param theIndex zero-based index of feature in the group
+  //! \param theAllowFolder take into account grouping feature by folders
   virtual std::shared_ptr<ModelAPI_Object> object(const std::string& theGroupID,
-                                                    const int theIndex) = 0;
+                                                  const int theIndex,
+                                                  const bool theAllowFolder = false) = 0;
 
   //! Returns the first found object in the group by the object name
   //! \param theGroupID group that contains an object
@@ -101,11 +104,15 @@ public:
 
   //! Returns the object index in the group. Object must be visible. Otherwise returns -1.
   //! \param theObject object of this document
+  //! \param theAllowFolder take into account grouping feature by folders
   //! \returns index started from zero, or -1 if object is invisible or belongs to another document
-  virtual const int index(std::shared_ptr<ModelAPI_Object> theObject) = 0;
+  virtual const int index(std::shared_ptr<ModelAPI_Object> theObject,
+                          const bool theAllowFolder = false) = 0;
 
   //! Returns the number of objects in the group of objects
-  virtual int size(const std::string& theGroupID) = 0;
+  //! \param theGroupID group of objects
+  //! \param theAllowFolder take into account grouping feature by folders
+  virtual int size(const std::string& theGroupID, const bool theAllowFolder = false) = 0;
 
   //! Returns the feature that is currently edited in this document, normally
   //! this is the latest created feature
@@ -170,6 +177,50 @@ public:
   //! history. Not very fast method, for calling once, not in big cycles.
   virtual std::list<std::shared_ptr<ModelAPI_Feature> > allFeatures() = 0;
 
+  //! Returns all objects of the document including the hidden features which are not in
+  //! history. Not very fast method, for calling once, not in big cycles.
+  virtual std::list<std::shared_ptr<ModelAPI_Object> > allObjects() = 0;
+
+  //! Creates a folder (group of the features in the object browser)
+  //! \param theAddBefore a feature, the folder is added before
+  //!                     (if empty, the folder is added after the last feature)
+  virtual std::shared_ptr<ModelAPI_Folder> addFolder(
+      std::shared_ptr<ModelAPI_Feature> theAddBefore = std::shared_ptr<ModelAPI_Feature>()) = 0;
+  //! Removes the folder from the document (all features in the folder will be kept).
+  virtual void removeFolder(std::shared_ptr<ModelAPI_Folder> theFolder) = 0;
+  //! Search a folder above the list of features applicable to store them
+  //! (it means the list of features stored in the folder should be consequential)
+  //! \return Empty pointer if there is no applicable folder
+  virtual std::shared_ptr<ModelAPI_Folder> findFolderAbove(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures) = 0;
+  //! Search a folder below the list of features applicable to store them
+  //! (it means the list of features stored in the folder should be consequential)
+  //! \return Empty pointer if there is no applicable folder
+  virtual std::shared_ptr<ModelAPI_Folder> findFolderBelow(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures) = 0;
+  //! Search a folder containing the given feature.
+  //! Addtionally calculates a zero-based index of the feature in this folder.
+  //! \param theFeature feature to search
+  //! \param theIndexInFolder zero-based index in the folder or -1 if the feature is top-level.
+  //! \return the folder containing the feature or empty pointer if the feature is top-level.
+  virtual std::shared_ptr<ModelAPI_Folder> findContainingFolder(
+      const std::shared_ptr<ModelAPI_Feature>& theFeature,
+      int& theIndexInFolder) = 0;
+  //! Add a list of features to the folder. The correctness of the adding is not performed
+  //! (such checks have been done in corresponding find.. method).
+  //! \return \c true if the movement is successfull
+  virtual bool moveToFolder(const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+                            const std::shared_ptr<ModelAPI_Folder>& theFolder) = 0;
+  //! Remove features from the folder
+  //! \param theFeatures list of features to be removed
+  //! \param theBefore   extract features before the folder (this parameter is applicable only
+  //!                    when all features in the folder are taking out,
+  //!                    in other cases the direction is taken automatically)
+  //! \return \c true if the features have been moved out
+  virtual bool removeFromFolder(
+      const std::list<std::shared_ptr<ModelAPI_Feature> >& theFeatures,
+      const bool theBefore = true) = 0;
+
   //! Informs the document that it becomes active and some actions must be performed
   virtual void setActive(const bool theFlag) = 0;
   //! Returns true if this document is currently active
diff --git a/src/ModelAPI/ModelAPI_Folder.cpp b/src/ModelAPI/ModelAPI_Folder.cpp
new file mode 100644 (file)
index 0000000..1cc97cb
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+//
+
+#include "ModelAPI_Folder.h"
+
+#include <ModelAPI_AttributeReference.h>
+
+ModelAPI_Folder::ModelAPI_Folder()
+{
+}
+
+ModelAPI_Folder::~ModelAPI_Folder()
+{
+}
+
+void ModelAPI_Folder::init()
+{
+}
+
+void ModelAPI_Folder::initAttributes()
+{
+  data()->addAttribute(FIRST_FEATURE_ID(), ModelAPI_AttributeReference::typeId());
+  data()->addAttribute(LAST_FEATURE_ID(),  ModelAPI_AttributeReference::typeId());
+}
+
+void ModelAPI_Folder::execute()
+{
+}
diff --git a/src/ModelAPI/ModelAPI_Folder.h b/src/ModelAPI/ModelAPI_Folder.h
new file mode 100644 (file)
index 0000000..b89fd87
--- /dev/null
@@ -0,0 +1,105 @@
+// Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+//
+
+#ifndef ModelAPI_Folder_H_
+#define ModelAPI_Folder_H_
+
+#include <ModelAPI.h>
+#include <ModelAPI_Object.h>
+
+/**\class ModelAPI_Folder
+ * \ingroup DataModel
+ * \brief Folder feature (groups the features).
+ */
+class ModelAPI_Folder : public ModelAPI_Object
+{
+public:
+  MODELAPI_EXPORT ModelAPI_Folder();
+  /// To virtually destroy the fields of successors
+  MODELAPI_EXPORT virtual ~ModelAPI_Folder();
+
+  /// Folder feature ID
+  static const std::string& ID()
+  {
+    static const std::string MY_FOLDER_ID("Folder");
+    return MY_FOLDER_ID;
+  }
+
+  /// Returns the group identifier of all features
+  inline static std::string group()
+  {
+    static std::string MY_GROUP = "Folders";
+    return MY_GROUP;
+  }
+
+  /// Returns the group identifier of this result
+  virtual std::string groupName()
+  {
+    return group();
+  }
+
+  /// Attribute referring first feature in the folder
+  static const std::string& FIRST_FEATURE_ID()
+  {
+    static const std::string MY_FIRST_FEATURE_ID("first_feature");
+    return MY_FIRST_FEATURE_ID;
+  }
+
+  /// Attribute referring last feature in the folder
+  static const std::string& LAST_FEATURE_ID()
+  {
+    static const std::string MY_LAST_FEATURE_ID("last_feature");
+    return MY_LAST_FEATURE_ID;
+  }
+
+  /// Request for initialization of data model of the object: adding all attributes
+  MODELAPI_EXPORT virtual void initAttributes();
+
+  /// Computes or recomputes the results
+  MODELAPI_EXPORT virtual void execute();
+
+  /// Returns the feature is disabled or not.
+  virtual bool isDisabled()
+  { return false; }
+
+  //
+  // Helper methods, aliases for data()->method()
+  // -----------------------------------------------------------------------------------------------
+  /// Returns the name stored in the attribute
+  inline std::string name()
+  {
+    return data()->name();
+  }
+  /// Returns the reference attribute by the identifier
+  inline std::shared_ptr<ModelAPI_AttributeReference> reference(const std::string& theID)
+  {
+    return data()->reference(theID);
+  }
+
+protected:
+  /// This method is called just after creation of the object: it must initialize
+  /// all fields, normally initialized in the constructor
+  MODELAPI_EXPORT virtual void init();
+};
+
+//! Pointer on a folder object
+typedef std::shared_ptr<ModelAPI_Folder> FolderPtr;
+
+#endif
index f22b73c23c1ce295cf320d64d933fa12440a4992..90d41f6567a9ee53fca8e9b83598b51a62a884e8 100755 (executable)
@@ -382,6 +382,7 @@ bool removeFeaturesAndReferences(const std::set<FeaturePtr>& theFeatures,
   return ModelAPI_Tools::removeFeatures(aFeatures, false);
 }
 
+//***********************************************************************
 bool removeFeatures(const std::set<FeaturePtr>& theFeatures,
                     const bool theFlushRedisplay)
 {
@@ -406,6 +407,7 @@ bool removeFeatures(const std::set<FeaturePtr>& theFeatures,
   return true;
 }
 
+//***********************************************************************
 // Fills the references list by all references of the feature from the references map.
 // This is a recusive method to find references by next found feature in the map of references.
 // \param theFeature a feature to find references
index 470b8e3d46370fb71390c05a79a13674b1dcca3f..e9d740ce076c8dedb9bd0c6e5a909c048cd52d8f 100644 (file)
@@ -60,6 +60,7 @@
   #include "ModelAPI_ResultField.h"
   #include "ModelAPI_Tools.h"
   #include "ModelAPI_ResultCompSolid.h"
+  #include "ModelAPI_Folder.h"
 
   #include <memory>
   #include <string>
diff --git a/src/ModelAPI/Test/TestFolder_Create.py b/src/ModelAPI/Test/TestFolder_Create.py
new file mode 100644 (file)
index 0000000..36d429a
--- /dev/null
@@ -0,0 +1,108 @@
+## Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+##
+
+#=========================================================================
+# Initialization of the test
+#=========================================================================
+from ModelAPI import *
+
+__updated__ = "2017-11-22"
+
+#=========================================================================
+# Test 1. Check the Folder is created in PartSet
+#=========================================================================
+aSession = ModelAPI_Session.get()
+aPartSetDoc = aSession.activeDocument()
+
+aSession.startOperation()
+aPoint0 = aPartSetDoc.addFeature("Point")
+aPoint0Data = aPoint0.data()
+assert(aPoint0Data is not None)
+aPoint0Data.real("x").setValue(0.)
+aPoint0Data.real("y").setValue(0.)
+aPoint0Data.real("z").setValue(0.)
+aPoint0Data.string("creation_method").setValue("by_xyz")
+aSession.finishOperation()
+
+aSession.startOperation()
+aPoint1 = aPartSetDoc.addFeature("Point")
+aPoint1Data = aPoint1.data()
+assert(aPoint1Data is not None)
+aPoint1Data.real("x").setValue(0.)
+aPoint1Data.real("y").setValue(0.)
+aPoint1Data.real("z").setValue(0.)
+aPoint1Data.string("creation_method").setValue("by_xyz")
+aSession.finishOperation()
+
+assert(aPartSetDoc.size("Features") == 2), "Wrong number of features: {}".format(aPartSetDoc.size("Features"))
+
+# Folder before the feature
+aSession.startOperation()
+aFolder1 = aPartSetDoc.addFolder(aPoint1)
+aSession.finishOperation()
+
+assert(aPartSetDoc.size("Folders") == 1), "Wrong number of folders: {}".format(aPartSetDoc.size("Folders"))
+assert(aPartSetDoc.size("Features") == 3), "Wrong number of features: {}".format(aPartSetDoc.size("Features"))
+FOLDER_NAME_EXPECTED = "Folder_1"
+assert(aFolder1.name() == FOLDER_NAME_EXPECTED), "Actual name '{}', expected '{}'".format(aFolder1.name(), FOLDER_NAME_EXPECTED)
+
+## Folder at the end of features list
+#aSession.startOperation()
+#aPartSetDoc.addFolder()
+#aSession.finishOperation()
+#assert(aPartSetDoc.size("Folders") == 2)
+
+#=========================================================================
+# Test 2. Check the Folder is created in a Part
+#=========================================================================
+aSession.startOperation()
+aPart = aSession.moduleDocument().addFeature("Part")
+aSession.finishOperation()
+
+# check part is a last feature
+aFeaturesList = aSession.moduleDocument().allObjects()
+aLast = aFeaturesList[len(aFeaturesList)-1]
+assert(aLast.data().isEqual(aPart.data())), "Part is not a last object in the list"
+
+# add point and a folder before it
+aPartDoc = aSession.activeDocument()
+aSession.startOperation()
+aPoint2 = aPartDoc.addFeature("Point")
+aPoint2Data = aPoint2.data()
+assert(aPoint2Data is not None)
+aPoint2Data.real("x").setValue(0.)
+aPoint2Data.real("y").setValue(0.)
+aPoint2Data.real("z").setValue(0.)
+aPoint2Data.string("creation_method").setValue("by_xyz")
+aSession.finishOperation()
+
+assert(aPartDoc.size("Features") == 1), "Wrong number of features: {}".format(aPartDoc.size("Features"))
+
+aSession.startOperation()
+aFolder2 = aPartDoc.addFolder(aPoint2)
+aSession.finishOperation()
+
+assert(aPartDoc.size("Folders") == 1), "Wrong number of folders: {}".format(aPartDoc.size("Folders"))
+assert(aPartDoc.size("Features") == 2), "Wrong number of features: {}".format(aPartDoc.size("Features"))
+FOLDER_NAME_EXPECTED = "Folder_1"
+assert(aFolder2.name() == FOLDER_NAME_EXPECTED), "Actual name '{}', expected '{}'".format(aFolder2.name(), FOLDER_NAME_EXPECTED)
+
+from salome.shaper import model
+assert(model.checkPythonDump())
diff --git a/src/ModelAPI/Test/TestFolder_Remove.py b/src/ModelAPI/Test/TestFolder_Remove.py
new file mode 100644 (file)
index 0000000..6e78b58
--- /dev/null
@@ -0,0 +1,154 @@
+## Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+##
+
+#=========================================================================
+# Initialization of the test
+#=========================================================================
+from ModelAPI import *
+
+__updated__ = "2017-11-24"
+
+aSession = ModelAPI_Session.get()
+
+
+def newPoint(theDocument, theX, theY, theZ):
+    aSession.startOperation()
+    aPoint = theDocument.addFeature("Point")
+    aPointData = aPoint.data()
+    assert(aPointData is not None)
+    aPointData.real("x").setValue(theX)
+    aPointData.real("y").setValue(theY)
+    aPointData.real("z").setValue(theZ)
+    aPointData.string("creation_method").setValue("by_xyz")
+    aSession.finishOperation()
+    return aPoint
+
+
+#=========================================================================
+# Prepare some features and folders
+#=========================================================================
+aSession.startOperation()
+aPart = aSession.moduleDocument().addFeature("Part")
+aSession.finishOperation()
+
+# add points
+aPartDoc = aSession.activeDocument()
+aPoint1 = newPoint(aPartDoc,  0.,  0., 0.)
+aPoint2 = newPoint(aPartDoc, 10.,  0., 0.)
+aPoint3 = newPoint(aPartDoc, 10., 10., 0.)
+aPoint4 = newPoint(aPartDoc,  0., 10., 0.)
+
+# add folders
+aSession.startOperation()
+aFolder1 = aPartDoc.addFolder(aPoint2)
+aSession.finishOperation()
+aSession.startOperation()
+aFolder2 = aPartDoc.addFolder(aPoint2)
+aSession.finishOperation()
+aSession.startOperation()
+aFolder3 = aPartDoc.addFolder(aPoint3)
+aSession.finishOperation()
+
+# place points into folders
+toFolder = FeatureList()
+toFolder.append(aPoint1)
+
+aSession.startOperation()
+aFolder = aPartDoc.findFolderBelow(toFolder)
+assert(aFolder is not None)
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+
+toFolder = FeatureList()
+toFolder.append(aPoint3)
+toFolder.append(aPoint4)
+
+aSession.startOperation()
+aFolder = aPartDoc.findFolderAbove(toFolder)
+assert(aFolder is not None)
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+
+NB_FEATURES_FULL = 7
+NB_FEATURES_OUT  = 4
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aFolder1, True) == 0), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder1, True))
+assert(aPartDoc.index(aFolder2, True) == 1), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder2, True))
+assert(aPartDoc.index(aFolder3, True) == 3), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+
+assert(aPartDoc.index(aPoint1, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint1, True))
+assert(aPartDoc.index(aPoint2, True) ==  2), "Wrong index of the point: {}".format(aPartDoc.index(aPoint2, True))
+assert(aPartDoc.index(aPoint3, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint3, True))
+assert(aPartDoc.index(aPoint4, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4, True))
+
+
+#=========================================================================
+# Test 1. Remove empty folder
+#=========================================================================
+aSession.startOperation()
+aPartDoc.removeFolder(aFolder2)
+aSession.finishOperation()
+
+NB_FEATURES_FULL -= 1
+NB_FEATURES_OUT  -= 1
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aFolder1, True) == 0), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder1, True))
+assert(aPartDoc.index(aFolder2, True) == -1), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder2, True))
+assert(aPartDoc.index(aFolder3, True) == 2), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+
+assert(aPartDoc.index(aPoint1, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint1, True))
+assert(aPartDoc.index(aPoint2, True) ==  1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint2, True))
+assert(aPartDoc.index(aPoint3, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint3, True))
+assert(aPartDoc.index(aPoint4, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4, True))
+
+
+#=========================================================================
+# Test 2. Remove non-empty folder
+#=========================================================================
+aSession.startOperation()
+aPartDoc.removeFolder(aFolder3)
+aSession.finishOperation()
+
+NB_FEATURES_FULL -= 1
+NB_FEATURES_OUT  += 1
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aFolder1, True) == 0), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder1, True))
+assert(aPartDoc.index(aFolder2, True) == -1), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder2, True))
+assert(aPartDoc.index(aFolder3, True) == -1), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+
+assert(aPartDoc.index(aPoint1, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint1, True))
+assert(aPartDoc.index(aPoint2, True) ==  1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint2, True))
+assert(aPartDoc.index(aPoint3, True) ==  2), "Wrong index of the point: {}".format(aPartDoc.index(aPoint3, True))
+assert(aPartDoc.index(aPoint4, True) ==  3), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4, True))
+
+
+from salome.shaper import model
+assert(model.checkPythonDump())
diff --git a/src/ModelAPI/Test/TestFolder_Sketch.py b/src/ModelAPI/Test/TestFolder_Sketch.py
new file mode 100644 (file)
index 0000000..bf6768a
--- /dev/null
@@ -0,0 +1,187 @@
+## Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+##
+
+"""
+    Test checks adding sketch into a folder
+"""
+
+from ModelAPI import *
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+
+Sketch_0 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchLine_1 = Sketch_0.addLine(10, -10, 10, 10)
+model.do()
+
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchCircle_1 = Sketch_1.addCircle(50, 50, 25)
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_2/Face-SketchCircle_1_2f")], model.selection(), 50, 0)
+
+Sketch_2 = model.addSketch(Part_1_doc, model.standardPlane("XOY"))
+SketchCircle_2 = Sketch_2.addCircle(100, -100, 50)
+model.do()
+Extrusion_2 = model.addExtrusion(Part_1_doc, [model.selection("COMPOUND", "Sketch_3")], model.selection(), 10, 0)
+ExtrusionCut_1 = model.addExtrusionCut(Part_1_doc, [], model.selection(), 0, 10, [model.selection("SOLID", "Extrusion_2_1")])
+Sketch_3 = model.addSketch(Part_1_doc, model.selection("FACE", "Extrusion_2_1/To_Face_1"))
+SketchProjection_1 = Sketch_3.addProjection(model.selection("VERTEX", "Extrusion_2_1/Generated_Face_1&Extrusion_2_1/To_Face_1__cc"), False)
+SketchPoint_1 = SketchProjection_1.createdFeature()
+SketchCircle_3 = Sketch_3.addCircle(100, -100, 25)
+SketchConstraintCoincidence_1 = Sketch_3.setCoincident(SketchPoint_1.result(), SketchCircle_3.center())
+ExtrusionCut_1.setNestedSketch(Sketch_3)
+model.do()
+model.end()
+
+
+aSession = ModelAPI_Session.get()
+aPartDoc = aSession.activeDocument()
+
+#=========================================================================
+# Test 1. Sketch and extrusion could be added to the folder above
+#=========================================================================
+aSession.startOperation()
+Folder_1 = aPartDoc.addFolder(Sketch_1.feature())
+aSession.finishOperation()
+
+NB_FEATURES_FULL = 7
+NB_FEATURES_OUT  = 7
+# full number of features
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+# number of features outside the folder
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+toFolder = FeatureList()
+toFolder.append(Sketch_1.feature())
+toFolder.append(Extrusion_1.feature())
+
+# move features to the folder
+aSession.startOperation()
+aFolder = aPartDoc.findFolderAbove(toFolder)
+assert(aFolder is not None and aFolder.data().isEqual(Folder_1.data()))
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+NB_FEATURES_OUT -= 2
+# full number of features
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+# number of features outside the folder
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# check the index of the sketch in the folder
+aFound = aPartDoc.findContainingFolder(Sketch_1.feature())
+assert(aFound[0].data().isEqual(aFolder.data()))
+assert(aFound[1] == 0)
+# check the index of the extrusion in the folder
+aFound = aPartDoc.findContainingFolder(Extrusion_1.feature())
+assert(aFound[0].data().isEqual(aFolder.data()))
+assert(aFound[1] == 1)
+
+
+#=========================================================================
+# Test 2. Sketch could be added to the folder below
+#=========================================================================
+aSession.startOperation()
+Folder_2 = aPartDoc.addFolder(Extrusion_2.feature())
+aSession.finishOperation()
+
+NB_FEATURES_FULL += 1
+NB_FEATURES_OUT  += 1
+# full number of features
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+# number of features outside the folder
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+toFolder = FeatureList()
+toFolder.append(Sketch_2.feature())
+
+# move feature to the folder
+aSession.startOperation()
+aFolder = aPartDoc.findFolderBelow(toFolder)
+assert(aFolder is not None and aFolder.data().isEqual(Folder_2.data()))
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+NB_FEATURES_OUT -= 1
+# full number of features
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+# number of features outside the folder
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# check the index of the sketch in the folder
+aFound = aPartDoc.findContainingFolder(Sketch_2.feature())
+assert(aFound[0].data().isEqual(aFolder.data()))
+assert(aFound[1] == 0)
+
+
+#=========================================================================
+# Test 3. Sketch could be removed from the folder
+#=========================================================================
+fromFolder = FeatureList()
+fromFolder.append(Sketch_1.feature())
+
+aSession.startOperation()
+isMovedOut = aPartDoc.removeFromFolder(fromFolder)
+aSession.finishOperation()
+assert(isMovedOut)
+assert(aPartDoc.index(Sketch_1.feature(), True) == 1), "Wrong index of the {}: {}".format(Sketch_1.feature().name(), aPartDoc.index(Sketch_1.feature(), True))
+NB_FEATURES_OUT += 1
+# full number of features
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+# number of features outside the folder
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+
+#=========================================================================
+# Test 4. Add 2 sketches to the folder below
+#=========================================================================
+
+toFolder = FeatureList()
+toFolder.append(Sketch_0.feature())
+toFolder.append(Sketch_1.feature())
+
+# move features to the folder
+aSession.startOperation()
+aFolder = aPartDoc.findFolderBelow(toFolder)
+assert(aFolder is not None and aFolder.data().isEqual(Folder_1.data()))
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+NB_FEATURES_OUT -= 2
+# full number of features
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+# number of features outside the folder
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# check the index of the sketch in the folder
+aFound = aPartDoc.findContainingFolder(Sketch_0.feature())
+assert(aFound[0].data().isEqual(aFolder.data()))
+assert(aFound[1] == 0)
+# check the index of the extrusion in the folder
+aFound = aPartDoc.findContainingFolder(Sketch_1.feature())
+assert(aFound[0].data().isEqual(aFolder.data()))
+assert(aFound[1] == 1)
+
+
+from salome.shaper import model
+assert(model.checkPythonDump())
diff --git a/src/ModelAPI/Test/TestFolder_Stability.py b/src/ModelAPI/Test/TestFolder_Stability.py
new file mode 100644 (file)
index 0000000..bbd867c
--- /dev/null
@@ -0,0 +1,98 @@
+## Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+##
+
+#=========================================================================
+# Test checks stability of the foldering mechanism due to incorrect input parameters
+#=========================================================================
+from ModelAPI import *
+
+__updated__ = "2017-11-23"
+
+aSession = ModelAPI_Session.get()
+
+
+def newPoint(theDocument, theX, theY, theZ):
+    aSession.startOperation()
+    aPoint = theDocument.addFeature("Point")
+    aPointData = aPoint.data()
+    assert(aPointData is not None)
+    aPointData.real("x").setValue(theX)
+    aPointData.real("y").setValue(theY)
+    aPointData.real("z").setValue(theZ)
+    aPointData.string("creation_method").setValue("by_xyz")
+    aSession.finishOperation()
+    return aPoint
+
+
+#=========================================================================
+# Initialization of the model
+#=========================================================================
+aSession.startOperation()
+aPart = aSession.moduleDocument().addFeature("Part")
+aSession.finishOperation()
+
+# add point and a folder before it
+aPartDoc = aSession.activeDocument()
+aPoint1 = newPoint(aPartDoc, 0., 0., 0.)
+
+aSession.startOperation()
+aFolder1 = aPartDoc.addFolder(aPoint1)
+aSession.finishOperation()
+
+NB_FEATURES_FULL = 2
+NB_FEATURES_OUT  = 2
+
+assert(aPartDoc.size("Folders") == 1), "Wrong number of folders: {}".format(aPartDoc.size("Folders"))
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}".format(aPartDoc.size("Features"))
+
+#=========================================================================
+# Test 1. Check number of features out of folder
+#         and absense of the crash while getting size of incorrect group
+#=========================================================================
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+assert(aPartDoc.size("Construction", True) == 1), "Wrong size: {}".format(aPartDoc.size("Construction", True))
+
+#=========================================================================
+# Test 2. Add a feature to the folder and check number of features once again
+#=========================================================================
+toFolder = FeatureList()
+toFolder.append(aPoint1)
+
+aSession.startOperation()
+aFolder2 = aPartDoc.addFolder(aPoint1)
+aSession.finishOperation()
+
+NB_FEATURES_FULL += 1
+NB_FEATURES_OUT  += 1
+
+aSession.startOperation()
+aFolder = aPartDoc.findFolderAbove(toFolder)
+assert(aFolder is not None)
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+
+NB_FEATURES_OUT -= 1
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+assert(aPartDoc.size("Construction", True) == 1), "Wrong size: {}".format(aPartDoc.size("Construction", True))
+
+
+from salome.shaper import model
+assert(model.checkPythonDump())
diff --git a/src/ModelAPI/Test/TestFolder_Update.py b/src/ModelAPI/Test/TestFolder_Update.py
new file mode 100644 (file)
index 0000000..c16eb03
--- /dev/null
@@ -0,0 +1,355 @@
+## Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+##
+
+#=========================================================================
+# Initialization of the test
+#=========================================================================
+from ModelAPI import *
+
+__updated__ = "2017-11-23"
+
+aSession = ModelAPI_Session.get()
+
+
+def newPoint(theDocument, theX, theY, theZ):
+    aSession.startOperation()
+    aPoint = theDocument.addFeature("Point")
+    aPointData = aPoint.data()
+    assert(aPointData is not None)
+    aPointData.real("x").setValue(theX)
+    aPointData.real("y").setValue(theY)
+    aPointData.real("z").setValue(theZ)
+    aPointData.string("creation_method").setValue("by_xyz")
+    aSession.finishOperation()
+    return aPoint
+
+
+#=========================================================================
+# Test 1. Add a point into a folder above
+#=========================================================================
+aSession.startOperation()
+aPart = aSession.moduleDocument().addFeature("Part")
+aSession.finishOperation()
+
+# add point and a folder before it
+aPartDoc = aSession.activeDocument()
+aPoint1 = newPoint(aPartDoc, 0., 0., 0.)
+
+aSession.startOperation()
+aFolder1 = aPartDoc.addFolder(aPoint1)
+aSession.finishOperation()
+
+NB_FEATURES_FULL = 2
+NB_FEATURES_OUT  = 2
+
+assert(aPartDoc.size("Folders") == 1), "Wrong number of folders: {}".format(aPartDoc.size("Folders"))
+assert(aPartDoc.size("Features") == 2), "Wrong number of features: {}".format(aPartDoc.size("Features"))
+FOLDER_NAME_EXPECTED = "Folder_1"
+assert(aFolder1.name() == FOLDER_NAME_EXPECTED), "Actual name '{}', expected '{}'".format(aFolder1.name(), FOLDER_NAME_EXPECTED)
+
+toFolder = FeatureList()
+toFolder.append(aPoint1)
+
+# move point to the folder
+aSession.startOperation()
+aFolder = aPartDoc.findFolderAbove(toFolder)
+assert(aFolder is not None)
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+NB_FEATURES_OUT -= 1
+# full number of features
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+# number of features outside the folder
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# check the index of the point in the folder
+aFound = aPartDoc.findContainingFolder(aPoint1)
+assert(aFound[0].data().isEqual(aFolder1.data()))
+assert(aFound[1] == 0)
+
+#=========================================================================
+# Test 2. Add a point into a folder below
+#=========================================================================
+aPoint2 = newPoint(aPartDoc, 10., 0., 0.)
+aPoint3 = newPoint(aPartDoc, 10., 10., 0.)
+
+NB_FEATURES_FULL += 2
+NB_FEATURES_OUT += 2
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# add a folder
+aSession.startOperation()
+aFolder2 = aPartDoc.addFolder(aPoint3)
+aSession.finishOperation()
+
+NB_FEATURES_FULL += 1
+NB_FEATURES_OUT += 1
+
+assert(aPartDoc.size("Folders") == 2), "Wrong number of folders: {}".format(aPartDoc.size("Folders"))
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+toFolder = FeatureList()
+toFolder.append(aPoint2)
+
+# move point to the folder
+aSession.startOperation()
+aFolder = aPartDoc.findFolderBelow(toFolder)
+assert(aFolder is not None)
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+
+NB_FEATURES_OUT -= 1
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# check the index of the point in the folder
+aFound = aPartDoc.findContainingFolder(aPoint2)
+assert(aFound[0].data().isEqual(aFolder2.data()))
+assert(aFound[1] == 0)
+aFound = aPartDoc.findContainingFolder(aPoint3)
+assert(aFound == -1)
+
+#=========================================================================
+# Test 3. Add several points into a folder
+#=========================================================================
+aPoint4 = newPoint(aPartDoc, 0., 10., 0.)
+
+NB_FEATURES_FULL += 1
+NB_FEATURES_OUT += 1
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# index without respect to foldering
+assert(aPartDoc.index(aPoint4) == 5), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4))
+# index according to folders
+assert(aPartDoc.index(aPoint4, True) == 3), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4, True))
+
+# add a folder
+aSession.startOperation()
+aFolder3 = aPartDoc.addFolder(aPoint3)
+aSession.finishOperation()
+
+NB_FEATURES_FULL += 1
+NB_FEATURES_OUT += 1
+
+assert(aPartDoc.size("Folders") == 3), "Wrong number of folders: {}".format(aPartDoc.size("Folders"))
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# index without respect to foldering
+assert(aPartDoc.index(aFolder3) == 4), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3))
+assert(aPartDoc.index(aPoint4) == 6), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4))
+# index according to folders
+assert(aPartDoc.index(aFolder3, True) == 2), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+assert(aPartDoc.index(aPoint4, True) == 4), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4, True))
+
+toFolder = FeatureList()
+toFolder.append(aPoint3)
+toFolder.append(aPoint4)
+
+# move point to the folder
+aSession.startOperation()
+aFolder = aPartDoc.findFolderAbove(toFolder)
+assert(aFolder is not None)
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+
+NB_FEATURES_OUT -= 2
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+# index without respect to foldering
+assert(aPartDoc.index(aFolder3) == 4), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3))
+assert(aPartDoc.index(aPoint3) == 5), "Wrong index of the point: {}".format(aPartDoc.index(aPoint3))
+assert(aPartDoc.index(aPoint4) == 6), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4))
+# index according to folders
+assert(aPartDoc.index(aFolder3, True) == 2), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+assert(aPartDoc.index(aPoint3, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint3, True))
+assert(aPartDoc.index(aPoint4, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4, True))
+
+# check the index of the point in the folder
+aFound = aPartDoc.findContainingFolder(aPoint3)
+assert(aFound[0].data().isEqual(aFolder3.data()))
+assert(aFound[1] == 0)
+aFound = aPartDoc.findContainingFolder(aPoint4)
+assert(aFound[0].data().isEqual(aFolder3.data()))
+assert(aFound[1] == 1)
+
+
+# add more points to the folder to move them out
+aPoint5 = newPoint(aPartDoc, 0., 0., 10.)
+aPoint6 = newPoint(aPartDoc, 10., 0., 10.)
+aPoint7 = newPoint(aPartDoc, 10., 10., 10.)
+aPoint8 = newPoint(aPartDoc, 0., 10., 10.)
+
+toFolder = FeatureList()
+toFolder.append(aPoint5)
+toFolder.append(aPoint6)
+toFolder.append(aPoint7)
+toFolder.append(aPoint8)
+
+# move point to the folder
+aSession.startOperation()
+aFolder = aPartDoc.findFolderAbove(toFolder)
+assert(aFolder is not None)
+isAdded = aPartDoc.moveToFolder(toFolder, aFolder)
+aSession.finishOperation()
+assert(isAdded)
+
+NB_FEATURES_FULL += 4
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aPoint5, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint5, True))
+assert(aPartDoc.index(aPoint6, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint6, True))
+assert(aPartDoc.index(aPoint7, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint7, True))
+assert(aPartDoc.index(aPoint8, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint8, True))
+
+assert(aFolder3.reference("first_feature").value() is not None)
+assert(aFolder3.reference("last_feature").value() is not None)
+
+#=========================================================================
+# Test 4. Remove a point from a folder before it
+#=========================================================================
+fromFolder = FeatureList()
+fromFolder.append(aPoint3)
+
+aSession.startOperation()
+isMovedOut = aPartDoc.removeFromFolder(fromFolder)
+aSession.finishOperation()
+assert(isMovedOut)
+
+NB_FEATURES_OUT += 1
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aPoint3, True) == 2), "Wrong index of the point: {}".format(aPartDoc.index(aPoint3, True))
+assert(aPartDoc.index(aFolder3, True) == 3), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+
+assert(aFolder3.reference("first_feature").value() is not None)
+assert(aFolder3.reference("last_feature").value() is not None)
+
+#=========================================================================
+# Test 5. Remove a point from a folder after it
+#=========================================================================
+fromFolder = FeatureList()
+fromFolder.append(aPoint8)
+
+aSession.startOperation()
+isMovedOut = aPartDoc.removeFromFolder(fromFolder)
+aSession.finishOperation()
+assert(isMovedOut)
+
+NB_FEATURES_OUT += 1
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aPoint8, True) == 4), "Wrong index of the point: {}".format(aPartDoc.index(aPoint8, True))
+assert(aPartDoc.index(aFolder3, True) == 3), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+
+assert(aFolder3.reference("first_feature").value() is not None)
+assert(aFolder3.reference("last_feature").value() is not None)
+
+#=========================================================================
+# Test 6. Try to remove several points which are not start nor end in a folder
+#=========================================================================
+fromFolder = FeatureList()
+fromFolder.append(aPoint5)
+fromFolder.append(aPoint6)
+
+aSession.startOperation()
+isMovedOut = aPartDoc.removeFromFolder(fromFolder)
+aSession.finishOperation()
+assert(isMovedOut is False)
+
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aFolder3, True) == 3), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+assert(aPartDoc.index(aPoint5, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint5, True))
+assert(aPartDoc.index(aPoint6, True) == -1), "Wrong index of the point: {}".format(aPartDoc.index(aPoint6, True))
+assert(aPartDoc.index(aPoint8, True) == 4), "Wrong index of the point: {}".format(aPartDoc.index(aPoint8, True))
+
+assert(aFolder3.reference("first_feature").value() is not None)
+assert(aFolder3.reference("last_feature").value() is not None)
+
+#=========================================================================
+# Test 7. Remove several points from a folder after it
+#=========================================================================
+fromFolder = FeatureList()
+fromFolder.append(aPoint6)
+fromFolder.append(aPoint7)
+
+aSession.startOperation()
+isMovedOut = aPartDoc.removeFromFolder(fromFolder)
+aSession.finishOperation()
+assert(isMovedOut)
+
+NB_FEATURES_OUT += 2
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aFolder3, True) == 3), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+assert(aPartDoc.index(aPoint6, True) == 4), "Wrong index of the point: {}".format(aPartDoc.index(aPoint6, True))
+assert(aPartDoc.index(aPoint7, True) == 5), "Wrong index of the point: {}".format(aPartDoc.index(aPoint7, True))
+assert(aPartDoc.index(aPoint8, True) == 6), "Wrong index of the point: {}".format(aPartDoc.index(aPoint8, True))
+
+assert(aFolder3.reference("first_feature").value() is not None)
+assert(aFolder3.reference("last_feature").value() is not None)
+
+#=========================================================================
+# Test 8. Remove all remaining points from a folder after it
+#=========================================================================
+fromFolder = FeatureList()
+fromFolder.append(aPoint4)
+fromFolder.append(aPoint5)
+
+aSession.startOperation()
+isMovedOut = aPartDoc.removeFromFolder(fromFolder, False)
+aSession.finishOperation()
+assert(isMovedOut)
+
+NB_FEATURES_OUT += 2
+assert(aPartDoc.size("Features") == NB_FEATURES_FULL), "Wrong number of features: {}, expected: {}".format(aPartDoc.size("Features"), NB_FEATURES_FULL)
+assert(aPartDoc.size("Features", True) == NB_FEATURES_OUT), "Wrong number of features outside a folder: {}, expected: {}".format(aPartDoc.size("Features", True), NB_FEATURES_OUT)
+
+assert(aPartDoc.index(aFolder3, True) == 3), "Wrong index of the folder: {}".format(aPartDoc.index(aFolder3, True))
+assert(aPartDoc.index(aPoint4, True) == 4), "Wrong index of the point: {}".format(aPartDoc.index(aPoint4, True))
+assert(aPartDoc.index(aPoint5, True) == 5), "Wrong index of the point: {}".format(aPartDoc.index(aPoint5, True))
+assert(aPartDoc.index(aPoint6, True) == 6), "Wrong index of the point: {}".format(aPartDoc.index(aPoint6, True))
+assert(aPartDoc.index(aPoint7, True) == 7), "Wrong index of the point: {}".format(aPartDoc.index(aPoint7, True))
+assert(aPartDoc.index(aPoint8, True) == 8), "Wrong index of the point: {}".format(aPartDoc.index(aPoint8, True))
+
+# folder is empty
+assert(aFolder3.reference("first_feature").value() is None)
+assert(aFolder3.reference("last_feature").value() is None)
+
+
+from salome.shaper import model
+assert(model.checkPythonDump())
index f33878dcfc911ac0280a69afbd78c9b747784af4..9c0cfb10c7dabc5f3fe155efa7986b9f92d606f8 100644 (file)
@@ -33,6 +33,7 @@ SET(PROJECT_HEADERS
   ModelHighAPI_Services.h
   ModelHighAPI_Tools.h
   ModelHighAPI_FeatureStore.h
+  ModelHighAPI_Folder.h
 )
 
 SET(PROJECT_SOURCES
@@ -46,6 +47,7 @@ SET(PROJECT_SOURCES
   ModelHighAPI_Services.cpp
   ModelHighAPI_Tools.cpp
   ModelHighAPI_FeatureStore.cpp
+  ModelHighAPI_Folder.cpp
 )
 
 SET(PROJECT_LIBRARIES
index 7010d848143a09e50c63e061a6426e09b2fa5984..0b49cf05c46f9696caef19d8b3a333fd61b969ad 100644 (file)
@@ -57,6 +57,7 @@
 
 // shared pointers
 %shared_ptr(ModelHighAPI_Interface)
+%shared_ptr(ModelHighAPI_Folder)
 
 // typemaps
 
 %include "ModelHighAPI_Dumper.h"
 %include "ModelHighAPI_Integer.h"
 %include "ModelHighAPI_Interface.h"
+%include "ModelHighAPI_Folder.h"
 %include "ModelHighAPI_RefAttr.h"
 %include "ModelHighAPI_Reference.h"
 %include "ModelHighAPI_Selection.h"
index b7211a4c76e21747ceb3fac65e280cb85ce70762..8ada778b1a9b540b519f473c8b50abc0e2ea6942 100644 (file)
@@ -46,6 +46,7 @@
 #include <ModelAPI_Document.h>
 #include <ModelAPI_Entity.h>
 #include <ModelAPI_Feature.h>
+#include <ModelAPI_Folder.h>
 #include <ModelAPI_Result.h>
 #include <ModelAPI_ResultBody.h>
 #include <ModelAPI_ResultCompSolid.h>
@@ -124,14 +125,27 @@ const std::string& ModelHighAPI_Dumper::name(const EntityPtr& theEntity,
     return aFound->second.myCurrentName;
 
   // entity is not found, store it
-  std::string aName;
+  std::string aName, aKind;
   bool isDefaultName = false;
+  bool isSaveNotDumped = theSaveNotDumped;
   std::ostringstream aDefaultName;
   FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theEntity);
   if (aFeature) {
     aName = aFeature->name();
-    const std::string& aKind = aFeature->getKind();
-    DocumentPtr aDoc = aFeature->document();
+    aKind = aFeature->getKind();
+  } else {
+    FolderPtr aFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(theEntity);
+    if (aFolder) {
+      aName = aFolder->data()->name();
+      aKind = ModelAPI_Folder::ID();
+      isSaveNotDumped = false;
+      myNotDumpedFolders.insert(aFolder);
+    }
+  }
+
+  ObjectPtr anObject = std::dynamic_pointer_cast<ModelAPI_Object>(theEntity);
+  if (anObject) {
+    DocumentPtr aDoc = anObject->document();
     int& aNbFeatures = myFeatureCount[aDoc][aKind];
     aNbFeatures += 1;
 
@@ -164,7 +178,7 @@ const std::string& ModelHighAPI_Dumper::name(const EntityPtr& theEntity,
   }
 
   myNames[theEntity] = EntityName(aDefaultName.str(), aName, isDefaultName);
-  if (theSaveNotDumped)
+  if (isSaveNotDumped)
     myNotDumpedEntities.insert(theEntity);
 
   // store names of results
@@ -238,18 +252,30 @@ bool ModelHighAPI_Dumper::process(const std::shared_ptr<ModelAPI_Document>& theD
 bool ModelHighAPI_Dumper::process(const std::shared_ptr<ModelAPI_Document>& theDoc)
 {
   bool isOk = true;
-  std::list<FeaturePtr> aFeatures = theDoc->allFeatures();
-  std::list<FeaturePtr>::const_iterator aFeatIt = aFeatures.begin();
+  std::list<ObjectPtr> anObjects = theDoc->allObjects();
+  std::list<ObjectPtr>::const_iterator anObjIt = anObjects.begin();
   // firstly, dump all parameters
-  for (; aFeatIt != aFeatures.end(); ++ aFeatIt)
-    dumpParameter(*aFeatIt);
+  for (; anObjIt != anObjects.end(); ++ anObjIt) {
+    FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(*anObjIt);
+    if (aFeature)
+      dumpParameter(aFeature);
+  }
   // dump all other features
-  for (aFeatIt = aFeatures.begin(); aFeatIt != aFeatures.end(); ++aFeatIt) {
-    CompositeFeaturePtr aCompFeat = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(*aFeatIt);
+  for (anObjIt = anObjects.begin(); anObjIt != anObjects.end(); ++anObjIt) {
+    CompositeFeaturePtr aCompFeat = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(*anObjIt);
     if (aCompFeat) // iteratively process composite features
       isOk = process(aCompFeat) && isOk;
-    else if (!isDumped(*aFeatIt)) // dump common feature
-      dumpFeature(*aFeatIt);
+    else if (!isDumped(*anObjIt)) {
+      // dump folder
+      FolderPtr aFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(*anObjIt);
+      if (aFolder)
+        dumpFolder(aFolder);
+      else {
+        FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(*anObjIt);
+        if (aFeature) // dump common feature
+          dumpFeature(aFeature);
+      }
+    }
   }
   return isOk;
 }
@@ -335,9 +361,20 @@ bool ModelHighAPI_Dumper::processSubs(
   // dump "setName" for composite feature
   if (isDumpSetName)
     dumpEntitySetName();
+  // dump folders if any
+  dumpFolders();
   return isOk;
 }
 
+void ModelHighAPI_Dumper::dumpFolders()
+{
+  std::set<FolderPtr>::const_iterator aFolderIt = myNotDumpedFolders.begin();
+  while (aFolderIt != myNotDumpedFolders.end()) {
+    FolderPtr aFolder = *aFolderIt++;
+    dumpFolder(aFolder);
+  }
+}
+
 void ModelHighAPI_Dumper::dumpSubFeatureNameAndColor(const std::string theSubFeatureGet,
                                                      const FeaturePtr& theSubFeature)
 {
@@ -472,7 +509,8 @@ bool ModelHighAPI_Dumper::isDumped(const EntityPtr& theEntity) const
 {
   EntityNameMap::const_iterator aFound = myNames.find(theEntity);
   FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theEntity);
-  return aFound != myNames.end() || myFeaturesToSkip.find(aFeature) != myFeaturesToSkip.end();
+  return (aFound != myNames.end() && aFound->second.myIsDumped) ||
+         myFeaturesToSkip.find(aFeature) != myFeaturesToSkip.end();
 }
 
 bool ModelHighAPI_Dumper::isDefaultColor(const ResultPtr& theResult) const
@@ -659,6 +697,13 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
   return *this;
 }
 
+ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const FolderPtr& theFolder)
+{
+  myDumpBuffer << name(theFolder);
+  myNotDumpedFolders.erase(theFolder);
+  return *this;
+}
+
 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(const FeaturePtr& theEntity)
 {
   myDumpBuffer << name(theEntity);
@@ -986,5 +1031,8 @@ ModelHighAPI_Dumper& operator<<(ModelHighAPI_Dumper& theDumper,
   // then store currently dumped string
   theDumper.myFullDump << aBufCopy;
 
+  // now, store all not dumped folders
+  theDumper.dumpFolders();
+
   return theDumper;
 }
index c2fe0cd54de1bb6734d24f182989a7007e7ac565..e029335983ea3c6e71ef5e769b3c6ba73a3ec85f 100644 (file)
@@ -54,12 +54,14 @@ class ModelAPI_CompositeFeature;
 class ModelAPI_Document;
 class ModelAPI_Entity;
 class ModelAPI_Feature;
+class ModelAPI_Folder;
 class ModelAPI_Object;
 class ModelAPI_Result;
 
 typedef std::shared_ptr<ModelAPI_Document> DocumentPtr;
 typedef std::shared_ptr<ModelAPI_Entity>   EntityPtr;
 typedef std::shared_ptr<ModelAPI_Feature>  FeaturePtr;
+typedef std::shared_ptr<ModelAPI_Folder>   FolderPtr;
 typedef std::shared_ptr<ModelAPI_Result>   ResultPtr;
 
 /**\class ModelHighAPI_Dumper
@@ -116,6 +118,8 @@ public:
   virtual void dumpParameter(const FeaturePtr& theFeature) = 0;
   /// Dump given feature
   virtual void dumpFeature(const FeaturePtr& theFeature, const bool theForce = false) = 0;
+  /// Dump folder
+  virtual void dumpFolder(const FolderPtr& theFolder) = 0;
 
   /// Set a feature that should not be dumped anyway
   MODELHIGHAPI_EXPORT
@@ -203,6 +207,10 @@ public:
   MODELHIGHAPI_EXPORT
   ModelHighAPI_Dumper& operator<<(const FeaturePtr& theEntity);
 
+  /// Dump folder
+  MODELHIGHAPI_EXPORT
+  ModelHighAPI_Dumper& operator<<(const FolderPtr& theFolder);
+
   /// Dump result
   MODELHIGHAPI_EXPORT
   ModelHighAPI_Dumper& operator<<(const ResultPtr& theResult);
@@ -281,6 +289,9 @@ private:
   /// Check the result feature has default transparency
   bool isDefaultTransparency(const ResultPtr& theResult) const;
 
+  /// Dump stored folders if possible
+  void dumpFolders();
+
 private:
   struct EntityName {
     std::string myCurrentName; ///< default name of current feature
@@ -330,10 +341,13 @@ private:
   std::set<FeaturePtr> myFeaturesToSkip;
 
 protected:
-   /// list of entities, used by other features but not dumped yet
+  /// list of entities, used by other features but not dumped yet
   std::set<EntityPtr> myNotDumpedEntities;
+  /// list of folders which do not dumped yet
+  std::set<FolderPtr> myNotDumpedFolders;
 
   friend class SketchAPI_Sketch;
+  friend class ModelHighAPI_Folder;
 };
 
 #endif
index 97f65b4777012ade15dbb4499fd0e116e2667b39..995cb344f47ea8d4cd6c9fef688a929d0ac63bec 100644 (file)
 #define PRECISION 6
 #define TOLERANCE (1.e-7)
 
-ModelHighAPI_FeatureStore::ModelHighAPI_FeatureStore(FeaturePtr theFeature) {
-  storeData(theFeature->data(), myAttrs);
-  // iterate results to store
-  std::list<ResultPtr> allResults;
-  ModelAPI_Tools::allResults(theFeature, allResults);
-  std::list<ResultPtr>::iterator aRes = allResults.begin();
-  for(; aRes != allResults.end(); aRes++) {
-    std::map<std::string, std::string> aResDump;
-    storeData((*aRes)->data(), aResDump);
-    myRes.push_back(aResDump);
+ModelHighAPI_FeatureStore::ModelHighAPI_FeatureStore(ObjectPtr theObject) {
+  storeData(theObject->data(), myAttrs);
+
+  FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theObject);
+  if (aFeature) {
+    // iterate results to store
+    std::list<ResultPtr> allResults;
+    ModelAPI_Tools::allResults(aFeature, allResults);
+    std::list<ResultPtr>::iterator aRes = allResults.begin();
+    for(; aRes != allResults.end(); aRes++) {
+      std::map<std::string, std::string> aResDump;
+      storeData((*aRes)->data(), aResDump);
+      myRes.push_back(aResDump);
+    }
   }
 }
 
-std::string ModelHighAPI_FeatureStore::compare(FeaturePtr theFeature) {
-  std::string anError = compareData(theFeature->data(), myAttrs);
+std::string ModelHighAPI_FeatureStore::compare(ObjectPtr theObject) {
+  std::string anError = compareData(theObject->data(), myAttrs);
   if (!anError.empty()) {
-    return "Features '" + theFeature->name() + "' differ:" + anError;
-  }
-  std::list<ResultPtr> allResults;
-  ModelAPI_Tools::allResults(theFeature, allResults);
-  std::list<ResultPtr>::iterator aRes = allResults.begin();
-  std::list<std::map<std::string, std::string> >::iterator aResIter = myRes.begin();
-  for(; aRes != allResults.end() && aResIter != myRes.end(); aRes++, aResIter++) {
-    anError = compareData((*aRes)->data(), *aResIter);
-    if (!anError.empty())
-      return "Results of feature '" + theFeature->name() + "' '" + (*aRes)->data()->name() +
-      "' differ:" + anError;
-  }
-  if (aRes != allResults.end()) {
-    return "Current model has more results '" + (*aRes)->data()->name() + "'";
+    return "Features '" + theObject->data()->name() + "' differ:" + anError;
   }
-  if (aResIter != myRes.end()) {
-    return "Original model had more results '" + (*aResIter)["__name__"] + "'";
+
+  FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theObject);
+  if (aFeature) {
+    std::list<ResultPtr> allResults;
+    ModelAPI_Tools::allResults(aFeature, allResults);
+    std::list<ResultPtr>::iterator aRes = allResults.begin();
+    std::list<std::map<std::string, std::string> >::iterator aResIter = myRes.begin();
+    for(; aRes != allResults.end() && aResIter != myRes.end(); aRes++, aResIter++) {
+      anError = compareData((*aRes)->data(), *aResIter);
+      if (!anError.empty())
+        return "Results of feature '" + aFeature->name() + "' '" + (*aRes)->data()->name() +
+        "' differ:" + anError;
+    }
+    if (aRes != allResults.end()) {
+      return "Current model has more results '" + (*aRes)->data()->name() + "'";
+    }
+    if (aResIter != myRes.end()) {
+      return "Original model had more results '" + (*aResIter)["__name__"] + "'";
+    }
   }
   return ""; // ok
 }
index 7970b179984db722d38c75effb7ce89f7edd3d6d..32bea8a3bcb5553f6bb49913ad5de3f190f82311 100644 (file)
 #include <string>
 #include <memory>
 
-class ModelAPI_Feature;
+class ModelAPI_Object;
 class ModelAPI_Data;
 class GeomAPI_Shape;
 class ModelAPI_Attribute;
 
-typedef std::shared_ptr<ModelAPI_Feature>  FeaturePtr;
-typedef std::shared_ptr<ModelAPI_Attribute>  AttributePtr;
+typedef std::shared_ptr<ModelAPI_Object>    ObjectPtr;
+typedef std::shared_ptr<ModelAPI_Attribute> AttributePtr;
 
 /**\class ModelHighAPI_FeatureStore
  * \ingroup CPPHighAPI
@@ -49,9 +49,9 @@ public:
   // unused constructor for the map container needs
   ModelHighAPI_FeatureStore() {}
   // constructor that initializes this object by feature to store
-  ModelHighAPI_FeatureStore(FeaturePtr theFeature);
+  ModelHighAPI_FeatureStore(ObjectPtr theObject);
   // compares the stored feature information with the given feature
-  std::string compare(FeaturePtr theFeature);
+  std::string compare(ObjectPtr theObject);
 
 private:
   /// stores the information about all attributes of data in map
diff --git a/src/ModelHighAPI/ModelHighAPI_Folder.cpp b/src/ModelHighAPI/ModelHighAPI_Folder.cpp
new file mode 100644 (file)
index 0000000..0478814
--- /dev/null
@@ -0,0 +1,101 @@
+// Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+//
+
+#include "ModelHighAPI_Folder.h"
+#include <ModelHighAPI_Dumper.h>
+#include <ModelHighAPI_Reference.h>
+#include <ModelHighAPI_Tools.h>
+
+#include <ModelAPI_AttributeReference.h>
+#include <ModelAPI_Document.h>
+
+//--------------------------------------------------------------------------------------
+
+ModelHighAPI_Folder::ModelHighAPI_Folder(const std::shared_ptr<ModelAPI_Folder> & theFolder)
+  : ModelHighAPI_Interface(FeaturePtr()),
+    myFolder(theFolder)
+{
+  initialize();
+}
+
+ModelHighAPI_Folder::~ModelHighAPI_Folder()
+{
+}
+
+bool ModelHighAPI_Folder::initialize()
+{
+  if (!myFolder) {
+    throwException(ID() + " exception: The folder is NULL.");
+    return false;
+  }
+
+  myFirstFeature = myFolder->reference(ModelAPI_Folder::FIRST_FEATURE_ID());
+  if (!myFirstFeature)
+    return false;
+  myAttrGetter[ModelAPI_Folder::FIRST_FEATURE_ID()] = "firstFeature";
+
+  myLastFeature = myFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+  if (!myLastFeature)
+    return false;
+  myAttrGetter[ModelAPI_Folder::LAST_FEATURE_ID()] = "lastFeature";
+
+  return true;
+}
+
+void ModelHighAPI_Folder::dump(ModelHighAPI_Dumper& theDumper) const
+{
+  const std::string& aDocName = theDumper.name(myFolder->document());
+
+  AttributeReferencePtr aStartRef = myFolder->reference(ModelAPI_Folder::FIRST_FEATURE_ID());
+  AttributeReferencePtr aEndRef   = myFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+
+  // Dump folder if it is empty or when its features have been already dumped.
+  // Otherwise, just store the name of the folder.
+  if (!aEndRef->value())
+    theDumper << myFolder << " = model.addFolder(" << aDocName << ")" << std::endl;
+  else if (theDumper.isDumped(aEndRef->value()))
+    theDumper << myFolder << " = model.addFolder(" << aDocName << ", "
+              << aStartRef << ", " << aEndRef << ")" << std::endl;
+  else
+    theDumper.name(myFolder);
+}
+
+//--------------------------------------------------------------------------------------
+
+std::shared_ptr<ModelHighAPI_Folder> addFolder(const std::shared_ptr<ModelAPI_Document>& theDoc)
+{
+  std::shared_ptr<ModelAPI_Folder> aFolder = theDoc->addFolder();
+  return std::shared_ptr<ModelHighAPI_Folder>(new ModelHighAPI_Folder(aFolder));
+}
+
+std::shared_ptr<ModelHighAPI_Folder> addFolder(const std::shared_ptr<ModelAPI_Document>& theDoc,
+                                               const ModelHighAPI_Reference& theFirstFeature,
+                                               const ModelHighAPI_Reference& theLastFeature)
+{
+  std::shared_ptr<ModelAPI_Folder> aFolder = theDoc->addFolder(theFirstFeature.feature());
+
+  AttributeReferencePtr aFirstFeatAttr = aFolder->reference(ModelAPI_Folder::FIRST_FEATURE_ID());
+  fillAttribute(theFirstFeature.feature(), aFirstFeatAttr);
+
+  AttributeReferencePtr aLastFeatAttr = aFolder->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+  fillAttribute(theLastFeature.feature(), aLastFeatAttr);
+
+  return std::shared_ptr<ModelHighAPI_Folder>(new ModelHighAPI_Folder(aFolder));
+}
diff --git a/src/ModelHighAPI/ModelHighAPI_Folder.h b/src/ModelHighAPI/ModelHighAPI_Folder.h
new file mode 100644 (file)
index 0000000..8128df0
--- /dev/null
@@ -0,0 +1,87 @@
+// Copyright (C) 2014-2017  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<mailto:webmaster.salome@opencascade.com>
+//
+
+#ifndef SRC_MODELHIGHAPI_MODELHIGHAPI_FOLDER_H_
+#define SRC_MODELHIGHAPI_MODELHIGHAPI_FOLDER_H_
+
+//--------------------------------------------------------------------------------------
+#include <ModelHighAPI_Interface.h>
+
+#include <ModelAPI_Folder.h>
+
+#include <memory>
+//--------------------------------------------------------------------------------------
+class ModelAPI_AttributeReference;
+class ModelAPI_Document;
+class ModelHighAPI_Reference;
+//--------------------------------------------------------------------------------------
+/**\class ModelHighAPI_Folder
+ * \ingroup CPPHighAPI
+ * \brief Class for filling ModelAPI_Folder
+ */
+class ModelHighAPI_Folder : public ModelHighAPI_Interface
+{
+public:
+  /// Constructor for a folder
+  MODELHIGHAPI_EXPORT
+  explicit ModelHighAPI_Folder(const std::shared_ptr<ModelAPI_Folder>& theFolder);
+  /// Destructor
+  MODELHIGHAPI_EXPORT virtual ~ModelHighAPI_Folder();
+
+  static std::string ID() { return ModelAPI_Folder::ID(); }
+  virtual std::string getID() { return ID(); }
+
+  /// First feature reference
+  std::shared_ptr<ModelAPI_AttributeReference> firstFeature() const
+  { return myFirstFeature; }
+
+  /// Last feature reference
+  std::shared_ptr<ModelAPI_AttributeReference> lastFeature() const
+  { return myLastFeature; }
+
+  /// Dump wrapped feature
+  MODELHIGHAPI_EXPORT virtual void dump(ModelHighAPI_Dumper& theDumper) const;
+
+protected:
+  bool initialize();
+
+private:
+  std::shared_ptr<ModelAPI_Folder> myFolder;
+
+  std::shared_ptr<ModelAPI_AttributeReference> myFirstFeature;
+  std::shared_ptr<ModelAPI_AttributeReference> myLastFeature;
+};
+
+//--------------------------------------------------------------------------------------
+/**\ingroup CPPHighAPI
+ * \brief Create empty Folder feature
+ */
+MODELHIGHAPI_EXPORT
+std::shared_ptr<ModelHighAPI_Folder> addFolder(const std::shared_ptr<ModelAPI_Document>& theDoc);
+
+/**\ingroup CPPHighAPI
+ * \brief Create Folder feature
+ */
+MODELHIGHAPI_EXPORT
+std::shared_ptr<ModelHighAPI_Folder> addFolder(const std::shared_ptr<ModelAPI_Document>& theDoc,
+                                               const ModelHighAPI_Reference& theFirstFeature,
+                                               const ModelHighAPI_Reference& theLastFeature);
+//--------------------------------------------------------------------------------------
+#endif /* SRC_MODELHIGHAPI_MODELHIGHAPI_FOLDER_H_ */
index d03130b59b2f0b01c2b934f22457d97eb10ea545..b8415681e9ae09efc9b040f3be52023a74e8b32c 100644 (file)
@@ -338,47 +338,54 @@ std::string storeFeatures(const std::string& theDocName, DocumentPtr theDoc,
      }
   }
   // store the model features information: iterate all features
-  int aFeaturesCount = 0; // stores the number of compared features for this document to compate
+  int anObjectsCount = 0; // stores the number of compared features for this document to compate
   std::set<std::string> aProcessed; // processed features names (that are in the current document)
-  std::list<FeaturePtr> allFeatures = theDoc->allFeatures();
-  std::list<FeaturePtr>::iterator allIter = allFeatures.begin();
-  for(; allIter != allFeatures.end(); allIter++) {
-    FeaturePtr aFeat = *allIter;
+
+  // process all objects (features and folders)
+  std::list<ObjectPtr> allObjects = theDoc->allObjects();
+  std::list<ObjectPtr>::iterator allIter = allObjects.begin();
+  for(; allIter != allObjects.end(); allIter++) {
+    ObjectPtr anObject = *allIter;
     if (theCompare) {
       std::map<std::string, ModelHighAPI_FeatureStore>::iterator
-        aFeatFind = aDocFind->second.find(aFeat->name());
-      if (aFeatFind == aDocFind->second.end()) {
-        return "Document '" + theDocName + "' feature '" + aFeat->name() + "' not found";
+        anObjFind = aDocFind->second.find(anObject->data()->name());
+      if (anObjFind == aDocFind->second.end()) {
+        return "Document '" + theDocName + "' feature '" + anObject->data()->name() + "' not found";
       }
-      std::string anError = aFeatFind->second.compare(aFeat);
+      std::string anError = anObjFind->second.compare(anObject);
       if (!anError.empty()) {
         anError = "Document " + theDocName + " " + anError;
         return anError;
       }
-      aFeaturesCount++;
-      aProcessed.insert(aFeat->name());
+      anObjectsCount++;
+      aProcessed.insert(anObject->data()->name());
     } else {
-      theStore[theDocName][aFeat->name()] = ModelHighAPI_FeatureStore(aFeat);
+      theStore[theDocName][anObject->data()->name()] = ModelHighAPI_FeatureStore(anObject);
     }
-    // iterate all results of this feature
-    std::list<ResultPtr> allResults;
-    ModelAPI_Tools::allResults(aFeat, allResults);
-    std::list<ResultPtr>::iterator aRes = allResults.begin();
-    for(; aRes != allResults.end(); aRes++) {
-      // recoursively store features of sub-documents
-      if ((*aRes)->groupName() == ModelAPI_ResultPart::group()) {
-        DocumentPtr aDoc = std::dynamic_pointer_cast<ModelAPI_ResultPart>(*aRes)->partDoc();
-        if (aDoc.get()) {
-          std::string anError = storeFeatures((*aRes)->data()->name(), aDoc, theStore, theCompare);
-          if (!anError.empty())
-            return anError;
+
+    FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(anObject);
+    if (aFeature) {
+      // iterate all results of this feature
+      std::list<ResultPtr> allResults;
+      ModelAPI_Tools::allResults(aFeature, allResults);
+      std::list<ResultPtr>::iterator aRes = allResults.begin();
+      for(; aRes != allResults.end(); aRes++) {
+        // recoursively store features of sub-documents
+        if ((*aRes)->groupName() == ModelAPI_ResultPart::group()) {
+          DocumentPtr aDoc = std::dynamic_pointer_cast<ModelAPI_ResultPart>(*aRes)->partDoc();
+          if (aDoc.get()) {
+            std::string anError =
+                storeFeatures((*aRes)->data()->name(), aDoc, theStore, theCompare);
+            if (!anError.empty())
+              return anError;
+          }
         }
       }
     }
   }
   // checks the number of compared features
   if (theCompare) {
-    if (aDocFind->second.size() != aFeaturesCount) {
+    if (aDocFind->second.size() != anObjectsCount) {
       // search for disappeared feature
       std::string aLostName;
       std::map<std::string, ModelHighAPI_FeatureStore>::iterator aLostIter;
index 4a0bf71da8fb2f312f8107b8bcea6aeb054911fb..fb7b26c8fda35d6e85f860929692cdbf2cd54cf9 100644 (file)
@@ -28,6 +28,7 @@
   #include "ModelHighAPI.h"
   #include "ModelHighAPI_Double.h"
   #include "ModelHighAPI_Dumper.h"
+  #include "ModelHighAPI_Folder.h"
   #include "ModelHighAPI_Integer.h"
   #include "ModelHighAPI_Interface.h"
   #include "ModelHighAPI_Macro.h"
index dbf0f252b4d8461e7697f871ab242ea101f22b11..32d61570b082efd575f8e1afc995cec6c33cb87d 100755 (executable)
@@ -46,6 +46,7 @@
 #include <ModelAPI_Tools.h>
 #include <ModelAPI_Session.h>
 #include <ModelAPI_Events.h>
+#include <ModelAPI_Folder.h>
 
 #include <ModelGeomAlgo_Point2D.h>
 
@@ -372,20 +373,24 @@ int shapeType(const QString& theType)
 }
 
 void checkObjects(const QObjectPtrList& theObjects, bool& hasResult, bool& hasFeature,
-                  bool& hasParameter, bool& hasCompositeOwner, bool& hasResultInHistory)
+                  bool& hasParameter, bool& hasCompositeOwner, bool& hasResultInHistory,
+                  bool& hasFolder)
 {
   hasResult = false;
   hasFeature = false;
   hasParameter = false;
   hasCompositeOwner = false;
   hasResultInHistory = false;
+  hasFolder = false;
   foreach(ObjectPtr aObj, theObjects) {
     FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aObj);
     ResultPtr aResult = std::dynamic_pointer_cast<ModelAPI_Result>(aObj);
+    FolderPtr aFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(aObj);
     ResultParameterPtr aConstruction = std::dynamic_pointer_cast<ModelAPI_ResultParameter>(aResult);
 
     hasResult |= (aResult.get() != NULL);
     hasFeature |= (aFeature.get() != NULL);
+    hasFolder |= (aFolder.get() != NULL);
     hasParameter |= (aConstruction.get() != NULL);
     if (hasFeature)
       hasCompositeOwner |= (ModelAPI_Tools::compositeOwner(aFeature) != NULL);
@@ -395,7 +400,7 @@ void checkObjects(const QObjectPtrList& theObjects, bool& hasResult, bool& hasFe
       hasResultInHistory = aFeature.get() && aFeature->isInHistory();
     }
 
-    if (hasFeature && hasResult  && hasParameter && hasCompositeOwner)
+    if (hasFeature && hasResult  && hasParameter && hasCompositeOwner && hasFeature)
       break;
   }
 }
@@ -1039,10 +1044,26 @@ void convertToFeatures(const QObjectPtrList& theObjects, std::set<FeaturePtr>& t
     if (!aFeature.get() && anObject->groupName() == ModelAPI_ResultParameter::group()) {
       aFeature = ModelAPI_Feature::feature(anObject);
     }
-    theFeatures.insert(aFeature);
+    if (aFeature.get())
+      theFeatures.insert(aFeature);
+  }
+}
+
+//**************************************************************
+void convertToFolders(const QObjectPtrList& theObjects,
+                                         std::set<FolderPtr>& theFolders)
+{
+  QObjectPtrList::const_iterator anIt = theObjects.begin(), aLast = theObjects.end();
+  for(; anIt != aLast; anIt++) {
+    ObjectPtr anObject = *anIt;
+    FolderPtr aFeature = std::dynamic_pointer_cast<ModelAPI_Folder>(anObject);
+    if (aFeature.get())
+      theFolders.insert(aFeature);
   }
 }
 
+
+//**************************************************************
 QString translate(const Events_InfoMessage& theMessage)
 {
   QString aMessage;
index 0c46f52dcea7bd3ea01ae2ca95829d1571146226..8a179c25adf1bc17ca09c83cd33f9a8590b34041 100755 (executable)
@@ -26,6 +26,7 @@
 
 #include <ModelAPI_Attribute.h>
 #include <ModelAPI_Feature.h>
+#include <ModelAPI_Folder.h>
 
 #include <TopAbs_ShapeEnum.hxx>
 #include <TopoDS_Shape.hxx>
@@ -176,9 +177,10 @@ MODULEBASE_EXPORT bool isSubResult(ObjectPtr theObject);
 /// \param hasParameter will be set to true if list contains Parameter objects
 /// \param hasCompositeOwner will be set to true if list contains Sub-Feature objects
 /// \param hasResultInHistory will be set to true if one of result is in history
+/// \param hasFolder will be set to true if one of folder is in the list
 MODULEBASE_EXPORT void checkObjects(const QObjectPtrList& theObjects, bool& hasResult,
                            bool& hasFeature, bool& hasParameter, bool& hasCompositeOwner,
-                           bool& hasResultInHistory);
+                           bool& hasResultInHistory, bool& hasFolder);
 
 /// Sets the default coeffient into the driver calculated accordingly the shape type.
 /// It provides 1.e-4 for results of construction type
@@ -331,6 +333,13 @@ void MODULEBASE_EXPORT convertToFeatures(const QObjectPtrList& theObjects,
                                          std::set<FeaturePtr>& theFeatures);
 
 
+/// Converts a list of objects to set of folders.
+/// \param theObjects a list of objects
+/// \param theFeatures an out conteiner of features
+void MODULEBASE_EXPORT convertToFolders(const QObjectPtrList& theObjects,
+                                         std::set<FolderPtr>& theFolders);
+
+
 /// Returns translation from the given data.
 /// If translation is not exists then it returns a string
 /// from the info data without translation
index a4ec9046ec616feadceae2148caa2ef1629215ee..f6131c3fe37b61a13c8aa19504a8b202efcfa33f 100755 (executable)
@@ -1198,8 +1198,9 @@ void PartSet_Module::addObjectBrowserMenu(QMenu* theMenu) const
   bool hasParameter = false;
   bool hasCompositeOwner = false;
   bool hasResultInHistory = false;
+  bool hasFolder = false;
   ModuleBase_Tools::checkObjects(aObjects, hasResult, hasFeature, hasParameter,
-                                  hasCompositeOwner, hasResultInHistory);
+                                  hasCompositeOwner, hasResultInHistory, hasFolder);
 
   ModuleBase_Operation* aCurrentOp = myWorkshop->currentOperation();
   if (aSelected == 1) {
index 486d361e01cdb6d30461f5fd587858973c176306..1a8beb6703ecde71c7df961411e7d564707e934b 100755 (executable)
@@ -256,7 +256,7 @@ public:
     std::set<AttributePtr>::const_iterator anIt;
     for (anIt = aRefsList.cbegin(); anIt != aRefsList.cend(); ++anIt) {
       FeaturePtr aRefFeature = std::dynamic_pointer_cast<ModelAPI_Feature>((*anIt)->owner());
-      if (aRefFeature->getKind() == theFeatureId)
+      if (aRefFeature && aRefFeature->getKind() == theFeatureId)
         return aRefFeature;
     }
     return FeaturePtr();
index f56860b041d968cb95d73b4ab6735ba97c92081a..0cdebfdfefe8b2e29eaf0b65026fc2cd59e74297 100644 (file)
@@ -72,6 +72,11 @@ class DumpAssistant(ModelHighAPI.ModelHighAPI_Dumper):
             # In case of theFeature is not a constraint, it will not be dumped.
             self.myFeatures[SketchAPI.SketchAPI_Constraint.ID()](theFeature).dump(self)
 
+    ## Create wrapper for a folder and dump it
+    def dumpFolder(self, theFolder):
+        if theFolder.ID() in self.myFeatures:
+            self.myFeatures[theFolder.ID()](theFolder).dump(self)
+
     ## Dump all parameters
     def dumpParameter(self, theFeature):
         aFeatureKind = theFeature.getKind()
index 8c15e89ecaf19521c50982e8cf42c9c133bbacd0..db5279d84ec6c7995b930b2522c2602f434b5636 100644 (file)
@@ -8,5 +8,6 @@ from ModelHighAPI import apply as do
 from ModelHighAPI import updateFeatures
 from ModelHighAPI import undo, redo
 from ModelHighAPI import reset
+from ModelHighAPI import addFolder
 from ModelHighAPI import ModelHighAPI_Selection as selection
 from ModelHighAPI import checkPythonDump as checkPythonDump
index 1a986b377ff86155065408fccb672b9f0b999a34..4d97e46d0dd67020cd2760875188e42cbb54a1b2 100644 (file)
@@ -47,6 +47,8 @@
 #include <ModelAPI_ResultBody.h>
 #include <ModelAPI_Tools.h>
 #include <ModelAPI_ResultField.h>
+#include <ModelAPI_Folder.h>
+#include <ModelAPI_AttributeReference.h>
 
 #include <Config_DataModelReader.h>
 
@@ -66,7 +68,7 @@
 XGUI_ContextMenuMgr::XGUI_ContextMenuMgr(XGUI_Workshop* theParent)
     : QObject(theParent),
       myWorkshop(theParent),
-      mySeparator(0)
+      mySeparator1(0), mySeparator2(0), mySeparator3(0)
 {
 }
 
@@ -135,8 +137,14 @@ void XGUI_ContextMenuMgr::createActions()
                                            aDesktop);
   addAction("WIREFRAME_CMD", aAction);
 
-  mySeparator = ModuleBase_Tools::createAction(QIcon(), "", aDesktop);
-  mySeparator->setSeparator(true);
+  mySeparator1 = ModuleBase_Tools::createAction(QIcon(), "", aDesktop);
+  mySeparator1->setSeparator(true);
+
+  mySeparator2 = ModuleBase_Tools::createAction(QIcon(), "", aDesktop);
+  mySeparator2->setSeparator(true);
+
+  mySeparator3 = ModuleBase_Tools::createAction(QIcon(), "", aDesktop);
+  mySeparator3->setSeparator(true);
 
   //mySelectActions = new QActionGroup(this);
   //mySelectActions->setExclusive(true);
@@ -180,6 +188,27 @@ void XGUI_ContextMenuMgr::createActions()
   addAction("TINSPECTOR_VIEW", aAction);
 #endif
 
+  // Features folders actions
+  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/create_folder.png"),
+                                           tr("Insert a folder before"), aDesktop);
+  addAction("INSERT_FOLDER_CMD", aAction);
+
+  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/insert_folder_before.png"),
+                                           tr("Move into the previous folder"), aDesktop);
+  addAction("ADD_TO_FOLDER_BEFORE_CMD", aAction);
+
+  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/insert_folder_after.png"),
+                                           tr("Move into the next folder"), aDesktop);
+  addAction("ADD_TO_FOLDER_AFTER_CMD", aAction);
+
+  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/move_out_before.png"),
+                                           tr("Move out before the folder"), aDesktop);
+  addAction("ADD_OUT_FOLDER_BEFORE_CMD", aAction);
+
+  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/move_out_after.png"),
+                                           tr("Move out after the folder"), aDesktop);
+  addAction("ADD_OUT_FOLDER_AFTER_CMD", aAction);
+
   buildObjBrowserMenu();
   buildViewerMenu();
 }
@@ -263,10 +292,11 @@ void XGUI_ContextMenuMgr::updateObjectBrowserMenu()
     bool hasParameter = false;
     bool hasCompositeOwner = false;
     bool hasResultInHistory = false;
+    bool hasFolder = false;
     ModuleBase_Tools::checkObjects(aObjects, hasResult, hasFeature, hasParameter,
-                                   hasCompositeOwner, hasResultInHistory);
+                                   hasCompositeOwner, hasResultInHistory, hasFolder);
     //Process Feature
-    if (aSelected == 1) {
+    if (aSelected == 1) { // single selection
       ObjectPtr aObject = aObjects.first();
       if (aObject) {
         if (hasResult && myWorkshop->canBeShaded(aObject)) {
@@ -307,7 +337,8 @@ void XGUI_ContextMenuMgr::updateObjectBrowserMenu()
                                                   (hasFeature || hasParameter));
         }
       }
-    } else {
+      // end single selection
+    } else { // multiselection
       // parameter is commented because the actions are not in the list of result parameter actions
       if (hasResult /*&& (!hasParameter)*/) {
         action("SHOW_CMD")->setEnabled(true);
@@ -316,7 +347,98 @@ void XGUI_ContextMenuMgr::updateObjectBrowserMenu()
         action("SHADING_CMD")->setEnabled(true);
         action("WIREFRAME_CMD")->setEnabled(true);
       }
-    }
+    } // end multiselection
+
+    // Check folder management commands state if only features are selected
+    if ((!hasResult) && hasFeature && (!hasParameter) && (!hasCompositeOwner) &&
+      (!hasResultInHistory) && (!hasFolder)) {
+      std::list<FeaturePtr> aFeatures = aSelMgr->getSelectedFeatures();
+      if (aFeatures.size() > 0) { // Check that features do not include Parts
+        QModelIndexList aIndexes = aSelMgr->selection()->selectedIndexes();
+        QModelIndex aFirstIdx = aIndexes.first();
+        QModelIndex aLastIdx = aIndexes.last();
+        QModelIndex aParentIdx = aFirstIdx.parent();
+
+        // if all selected are from the same level
+        bool isSameParent = true;
+        foreach(QModelIndex aIdx, aIndexes) {
+          if (aIdx.parent() != aParentIdx) {
+            isSameParent = false;
+            break;
+          }
+        }
+        if (isSameParent) {
+          // Check is selection continuous
+          XGUI_DataModel* aModel = myWorkshop->objectBrowser()->dataModel();
+          DocumentPtr aDoc = aMgr->activeDocument();
+          std::list<FeaturePtr> aFeatures = aSelMgr->getSelectedFeatures();
+
+          bool isContinuos = true;
+          if (aSelected > 1) {
+            int aId = -1;
+            foreach(FeaturePtr aF, aFeatures) {
+              if (aId == -1)
+                aId = aDoc->index(aF);
+              else {
+                aId++;
+                if (aId != aDoc->index(aF)) {
+                  isContinuos = false;
+                  break;
+                }
+              }
+            }
+          }
+          if (isContinuos) {
+            ObjectPtr aDataObj = aModel->object(aParentIdx);
+
+            ObjectPtr aPrevObj;
+            if (aFirstIdx.row() > 0) {
+              QModelIndex aPrevIdx = aFirstIdx.sibling(aFirstIdx.row() - 1, 0);
+              aPrevObj = aModel->object(aPrevIdx);
+            }
+
+            ObjectPtr aNextObj;
+            if (aLastIdx.row() < (aModel->rowCount(aParentIdx) - 1)) {
+              QModelIndex aNextIdx = aFirstIdx.sibling(aLastIdx.row() + 1, 0);
+              aNextObj = aModel->object(aNextIdx);
+            }
+
+            bool isPrevFolder = (aPrevObj.get() &&
+              (aPrevObj->groupName() == ModelAPI_Folder::group()));
+            bool isNextFolder = (aNextObj.get() &&
+              (aNextObj->groupName() == ModelAPI_Folder::group()));
+            bool isInFolder = (aDataObj.get() &&
+              (aDataObj->groupName() == ModelAPI_Folder::group()));
+            bool isOutsideFolder = !isInFolder;
+
+            bool hasFirst = false;
+            bool hasLast = false;
+            if (isInFolder) {
+              FolderPtr aFolder = std::dynamic_pointer_cast<ModelAPI_Folder>(aDataObj);
+              FeaturePtr aFirstFeatureInFolder;
+              AttributeReferencePtr aFirstFeatAttr =
+                  aFolder->data()->reference(ModelAPI_Folder::FIRST_FEATURE_ID());
+              if (aFirstFeatAttr)
+                aFirstFeatureInFolder = ModelAPI_Feature::feature(aFirstFeatAttr->value());
+              hasFirst = (aFirstFeatureInFolder == aFeatures.front());
+
+              FeaturePtr aLastFeatureInFolder;
+              AttributeReferencePtr aLastFeatAttr =
+                  aFolder->data()->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+              if (aLastFeatAttr)
+                aLastFeatureInFolder = ModelAPI_Feature::feature(aLastFeatAttr->value());
+              hasLast = (aLastFeatureInFolder == aFeatures.back());
+            }
+            action("INSERT_FOLDER_CMD")->setEnabled(isOutsideFolder);
+            action("ADD_TO_FOLDER_BEFORE_CMD")->setEnabled(isOutsideFolder && isPrevFolder);
+            action("ADD_TO_FOLDER_AFTER_CMD")->setEnabled(isOutsideFolder && isNextFolder);
+            action("ADD_OUT_FOLDER_BEFORE_CMD")->setEnabled(isInFolder && hasFirst);
+            action("ADD_OUT_FOLDER_AFTER_CMD")->setEnabled(isInFolder && hasLast);
+          }
+        }
+      }
+    } // end folder management commands
+
     bool allActive = true;
     foreach( ObjectPtr aObject, aObjects )
       if( aMgr->activeDocument() != aObject->document() )  {
@@ -332,7 +454,7 @@ void XGUI_ContextMenuMgr::updateObjectBrowserMenu()
 
     action("SHOW_RESULTS_CMD")->setEnabled(hasFeature);
     action("SHOW_FEATURE_CMD")->setEnabled(hasResult && hasResultInHistory);
-  }
+  } // end selection processing
 
   // Show/Hide command has to be disabled for objects from non active document
   bool aDeactivate = false;
@@ -492,7 +614,7 @@ void XGUI_ContextMenuMgr::buildObjBrowserMenu()
   aList.append(action("SHOW_CMD"));
   aList.append(action("HIDE_CMD"));
   aList.append(action("SHOW_ONLY_CMD"));
-  aList.append(mySeparator);
+  aList.append(mySeparator1);
   aList.append(action("RENAME_CMD"));
   aList.append(action("COLOR_CMD"));
   aList.append(action("DEFLECTION_CMD"));
@@ -507,12 +629,12 @@ void XGUI_ContextMenuMgr::buildObjBrowserMenu()
   aList.clear();
   aList.append(action("WIREFRAME_CMD"));
   aList.append(action("SHADING_CMD"));
-  aList.append(mySeparator); // this separator is not shown as this action is added after show only
+  aList.append(mySeparator1); // this separator is not shown as this action is added after show only
   // qt list container contains only one instance of the same action
   aList.append(action("SHOW_CMD"));
   aList.append(action("HIDE_CMD"));
   aList.append(action("SHOW_ONLY_CMD"));
-  aList.append(mySeparator);
+  aList.append(mySeparator2);
   aList.append(action("RENAME_CMD"));
   aList.append(action("COLOR_CMD"));
   aList.append(action("DEFLECTION_CMD"));
@@ -532,18 +654,30 @@ void XGUI_ContextMenuMgr::buildObjBrowserMenu()
   aList.append(action("RENAME_CMD"));
   aList.append(action("SHOW_RESULTS_CMD"));
   aList.append(action("MOVE_CMD"));
-  aList.append(mySeparator);
+  aList.append(mySeparator1);
+  aList.append(action("INSERT_FOLDER_CMD"));
+  aList.append(action("ADD_TO_FOLDER_BEFORE_CMD"));
+  aList.append(action("ADD_TO_FOLDER_AFTER_CMD"));
+  aList.append(mySeparator2);
+  aList.append(action("ADD_OUT_FOLDER_BEFORE_CMD"));
+  aList.append(action("ADD_OUT_FOLDER_AFTER_CMD"));
+  aList.append(mySeparator3);
   aList.append(action("CLEAN_HISTORY_CMD"));
   aList.append(action("DELETE_CMD"));
   myObjBrowserMenus[ModelAPI_Feature::group()] = aList;
 
   aList.clear();
   aList.append(action("RENAME_CMD"));
-  aList.append(mySeparator);
+  aList.append(mySeparator1);
   aList.append(action("CLEAN_HISTORY_CMD"));
   aList.append(action("DELETE_CMD"));
   myObjBrowserMenus[ModelAPI_ResultParameter::group()] = aList;
   //-------------------------------------
+
+  aList.clear();
+  aList.append(action("DELETE_CMD"));
+  myObjBrowserMenus[ModelAPI_Folder::group()] = aList;
+
 }
 
 void XGUI_ContextMenuMgr::buildViewerMenu()
@@ -552,7 +686,7 @@ void XGUI_ContextMenuMgr::buildViewerMenu()
   // Result construction menu
   aList.append(action("HIDE_CMD"));
   aList.append(action("SHOW_ONLY_CMD"));
-  aList.append(mySeparator);
+  aList.append(mySeparator1);
   aList.append(action("COLOR_CMD"));
   aList.append(action("DEFLECTION_CMD"));
 #ifdef USE_TRANSPARENCY
@@ -566,10 +700,10 @@ void XGUI_ContextMenuMgr::buildViewerMenu()
   aList.clear();
   aList.append(action("WIREFRAME_CMD"));
   aList.append(action("SHADING_CMD"));
-  aList.append(mySeparator);
+  aList.append(mySeparator1);
   aList.append(action("HIDE_CMD"));
   aList.append(action("SHOW_ONLY_CMD"));
-  aList.append(mySeparator);
+  aList.append(mySeparator2);
   aList.append(action("COLOR_CMD"));
   aList.append(action("DEFLECTION_CMD"));
 #ifdef USE_TRANSPARENCY
@@ -603,11 +737,16 @@ void XGUI_ContextMenuMgr::addObjBrowserMenu(QMenu* theMenu) const
   } else if (aSelected > 1) {
       aActions.append(action("WIREFRAME_CMD"));
       aActions.append(action("SHADING_CMD"));
-      aActions.append(mySeparator);
+      aActions.append(mySeparator1);
       aActions.append(action("SHOW_CMD"));
       aActions.append(action("HIDE_CMD"));
       aActions.append(action("SHOW_ONLY_CMD"));
-      aActions.append(mySeparator);
+      aActions.append(mySeparator2);
+      aActions.append(action("ADD_TO_FOLDER_BEFORE_CMD"));
+      aActions.append(action("ADD_TO_FOLDER_AFTER_CMD"));
+      aActions.append(action("ADD_OUT_FOLDER_BEFORE_CMD"));
+      aActions.append(action("ADD_OUT_FOLDER_AFTER_CMD"));
+      aActions.append(mySeparator3);
       //aActions.append(action("MOVE_CMD"));
       aActions.append(action("COLOR_CMD"));
       aActions.append(action("DEFLECTION_CMD"));
index 7513d5e25199d57206bb7c3e2eeafc29a003f6a4..97a8584dfe0b8e4e03f6f2048cb313eb5906e714 100644 (file)
@@ -142,7 +142,9 @@ signals:
 
   //QActionGroup* mySelectActions;
 
-  QAction* mySeparator;
+  QAction* mySeparator1;
+  QAction* mySeparator2;
+  QAction* mySeparator3;
 };
 
 #endif
index 6efc56250afdb3fc9b1899c889cf3770d7c90bc8..d428a2c501986816457ec569efff57f10207b880 100644 (file)
@@ -35,6 +35,8 @@
 #include <ModelAPI_ResultCompSolid.h>
 #include <ModelAPI_ResultField.h>
 #include <ModelAPI_Tools.h>
+#include <ModelAPI_Folder.h>
+#include <ModelAPI_AttributeReference.h>
 
 #include <Config_FeatureMessage.h>
 #include <Config_DataModelReader.h>
@@ -73,7 +75,10 @@ ResultPartPtr getPartResult(ModelAPI_Object* theObj)
 /// Returns pointer on document if the given object is document object
 ModelAPI_Document* getSubDocument(void* theObj)
 {
-  ModelAPI_Document* aDoc = dynamic_cast<ModelAPI_Document*>((ModelAPI_Entity*)theObj);
+  ModelAPI_Document* aDoc = 0;
+  try {
+    aDoc = dynamic_cast<ModelAPI_Document*>((ModelAPI_Entity*)theObj);
+  } catch(...) {}
   return aDoc;
 }
 
@@ -141,7 +146,7 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
         // Insert new object
         int aRow = aRootDoc->size(aObjType) - 1;
         if (aRow != -1) {
-          if (aObjType == aRootType) {
+          if ((aObjType == aRootType) || (aObjType == ModelAPI_Folder::group())) {
             insertRow(aRow + aNbFolders + 1);
           } else {
             int aFolderId = myXMLReader->rootFolderId(aObjType);
@@ -165,10 +170,10 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
               }
             }
          }
-          int aRow = aDoc->index(aObject);
+          int aRow = aDoc->index(aObject, true);
           if (aRow != -1) {
             int aNbSubFolders = foldersCount(aDoc.get());
-            if (aObjType == aSubType) {
+            if ((aObjType == aSubType) || (aObjType == ModelAPI_Folder::group())) {
               // List of objects under document root
               insertRow(aRow + aNbSubFolders, aDocRoot);
             } else {
@@ -215,8 +220,8 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
     for (aIt = aGroups.begin(); aIt != aGroups.end(); ++aIt) {
       std::string aGroup = (*aIt);
       if (aDoc == aRootDoc) {  // If root objects
-        int aRow = aRootDoc->size(aGroup);
-        if (aGroup == aRootType) {
+        int aRow = aRootDoc->size(aGroup, true);
+        if ((aGroup == aRootType) || (aGroup == ModelAPI_Folder::group())) {
           // Process root folder
           removeRow(aRow + aNbFolders);
           rebuildBranch(aNbFolders, aRow);
@@ -232,7 +237,7 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
         // Check that some folders could erased
         QStringList aNotEmptyFolders = listOfShowNotEmptyFolders();
         foreach (QString aNotEmptyFolder, aNotEmptyFolders) {
-          if ((aNotEmptyFolder.toStdString() == aGroup) && (aRootDoc->size(aGroup) == 0)) {
+          if ((aNotEmptyFolder.toStdString() == aGroup) && (aRootDoc->size(aGroup, true) == 0)) {
             // Appears first object in folder which can not be shown empty
             removeRow(myXMLReader->rootFolderId(aGroup));
             removeShownFolder(aRootDoc, aNotEmptyFolder);
@@ -244,9 +249,9 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
         // Remove row for sub-document
         QModelIndex aDocRoot = findDocumentRootIndex(aDoc.get(), 0);
         if (aDocRoot.isValid()) {
-          int aRow = aDoc->size(aGroup);
+          int aRow = aDoc->size(aGroup, true);
           int aNbSubFolders = foldersCount(aDoc.get());
-          if (aGroup == aSubType) {
+          if ((aGroup == aSubType) || (aGroup == ModelAPI_Folder::group())) {
             // List of objects under document root
             removeRow(aRow + aNbSubFolders, aDocRoot);
             rebuildBranch(aNbSubFolders, aRow, aDocRoot);
@@ -261,7 +266,7 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
           }
           // Check that some folders could disappear
           QStringList aNotEmptyFolders = listOfShowNotEmptyFolders(false);
-          int aSize = aDoc->size(aGroup);
+          int aSize = aDoc->size(aGroup, true);
           foreach (QString aNotEmptyFolder, aNotEmptyFolders) {
             if ((aNotEmptyFolder.toStdString() == aGroup) && (aSize == 0)) {
               // Appears first object in folder which can not be shown empty
@@ -283,7 +288,6 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
     std::set<ObjectPtr> aObjects = aUpdMsg->objects();
 
     std::set<ObjectPtr>::const_iterator aIt;
-    std::string aObjType;
     for (aIt = aObjects.begin(); aIt != aObjects.end(); ++aIt) {
       ObjectPtr aObject = (*aIt);
       if (aObject->data()->isValid()) {
@@ -295,9 +299,13 @@ void XGUI_DataModel::processEvent(const std::shared_ptr<Events_Message>& theMess
             QModelIndex aIndex = objectIndex(aResult, 0);
             removeRows(0, aResult->stepsSize(), aIndex);
         } else {
-          QModelIndex aIndex = objectIndex(aObject, 0);
-          if (aIndex.isValid()) {
-            emit dataChanged(aIndex, aIndex);
+          if (aObject->groupName() == ModelAPI_Folder::group()) {
+            rebuildDataTree();
+          } else {
+            QModelIndex aIndex = objectIndex(aObject, 0);
+            if (aIndex.isValid()) {
+              emit dataChanged(aIndex, aIndex);
+            }
           }
         }
       } else {
@@ -383,7 +391,7 @@ QModelIndex XGUI_DataModel::objectIndex(const ObjectPtr theObject, int theColumn
 {
   std::string aType = theObject->groupName();
   DocumentPtr aDoc = theObject->document();
-  int aRow = aDoc->index(theObject);
+  int aRow = aDoc->index(theObject, true);
   if (aRow == -1) {
     // it could be a part of complex object
     FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(theObject);
@@ -397,6 +405,10 @@ QModelIndex XGUI_DataModel::objectIndex(const ObjectPtr theObject, int theColumn
           }
         }
       }
+      int aFRow = -1;
+      FolderPtr aFolder = aDoc->findContainingFolder(aFeature, aFRow);
+      if (aFolder.get())
+        aRow = aFRow;
     } else {
       ResultPtr aResult = std::dynamic_pointer_cast<ModelAPI_Result>(theObject);
       if (aResult.get()) {
@@ -413,10 +425,11 @@ QModelIndex XGUI_DataModel::objectIndex(const ObjectPtr theObject, int theColumn
   }
   SessionPtr aSession = ModelAPI_Session::get();
   DocumentPtr aRootDoc = aSession->moduleDocument();
-  if (aDoc == aRootDoc && myXMLReader->rootType() == aType) {
+  if (aDoc == aRootDoc &&
+    ((myXMLReader->rootType() == aType) || (aType == ModelAPI_Folder::group()))) {
     // The object from root document
     aRow += foldersCount();
-  } else if (myXMLReader->subType() == aType) {
+  } else if ((myXMLReader->subType() == aType) || (aType == ModelAPI_Folder::group())) {
     // The object from sub document
     aRow += foldersCount(aDoc.get());
   }
@@ -497,14 +510,13 @@ QVariant XGUI_DataModel::data(const QModelIndex& theIndex, int theRole) const
         }
       }
     } else {
-      ModelAPI_Object* aObj =
-        dynamic_cast<ModelAPI_Object*>((ModelAPI_Entity*)theIndex.internalPointer());
+      ObjectPtr aObj = object(theIndex);
       if (aObj) {
         switch (theRole) {
         case Qt::DisplayRole:
           {
             if (aObj->groupName() == ModelAPI_ResultParameter::group()) {
-              ModelAPI_ResultParameter* aParam = dynamic_cast<ModelAPI_ResultParameter*>(aObj);
+              ResultParameterPtr aParam = std::dynamic_pointer_cast<ModelAPI_ResultParameter>(aObj);
               AttributeDoublePtr aValueAttribute =
                 aParam->data()->real(ModelAPI_ResultParameter::VALUE());
               QString aVal = QString::number(aValueAttribute->value());
@@ -513,7 +525,7 @@ QVariant XGUI_DataModel::data(const QModelIndex& theIndex, int theRole) const
             }
             QString aSuffix;
             if (aObj->groupName() == myXMLReader->subType()) {
-              ResultPartPtr aPartRes = getPartResult(aObj);
+              ResultPartPtr aPartRes = getPartResult(aObj.get());
               if (aPartRes.get()) {
                 if (aPartRes->partDoc().get() == NULL)
                   aSuffix = " (Not loaded)";
@@ -522,7 +534,12 @@ QVariant XGUI_DataModel::data(const QModelIndex& theIndex, int theRole) const
             return aObj->data()->name().c_str() + aSuffix;
           }
         case Qt::DecorationRole:
-          return ModuleBase_IconFactory::get()->getIcon(object(theIndex));
+          {
+            if (aObj->groupName() == ModelAPI_Folder::group())
+              return QIcon(":pictures/features_folder.png");
+            else
+              return ModuleBase_IconFactory::get()->getIcon(aObj);
+          }
         }
       } else {
         switch (theRole) {
@@ -564,7 +581,7 @@ int XGUI_DataModel::rowCount(const QModelIndex& theParent) const
     int aNbItems = 0;
     std::string aType = myXMLReader->rootType();
     if (!aType.empty())
-      aNbItems = aRootDoc->size(aType);
+      aNbItems = aRootDoc->size(aType, true);
     return aNbFolders + aNbItems;
   }
 
@@ -601,7 +618,7 @@ int XGUI_DataModel::rowCount(const QModelIndex& theParent) const
         int aNbSubItems = 0;
         std::string aSubType = myXMLReader->subType();
         if (!aSubType.empty())
-          aNbSubItems = aSubDoc->size(aSubType);
+          aNbSubItems = aSubDoc->size(aSubType, true);
         return aNbSubItems + aNbSubFolders;
       } else {
         // Check for composite object
@@ -614,6 +631,9 @@ int XGUI_DataModel::rowCount(const QModelIndex& theParent) const
         ModelAPI_ResultField* aFieldRes = dynamic_cast<ModelAPI_ResultField*>(aObj);
         if (aFieldRes)
           return aFieldRes->stepsSize();
+        ModelAPI_Folder* aFolder = dynamic_cast<ModelAPI_Folder*>(aObj);
+        if (aFolder)
+          return getNumberOfFolderItems(aFolder);
       }
     }
   }
@@ -641,8 +661,8 @@ QModelIndex XGUI_DataModel::index(int theRow, int theColumn, const QModelIndex &
     else { // return object under root index
       std::string aType = myXMLReader->rootType();
       int aObjId = theRow - aNbFolders;
-      if (aObjId < aRootDoc->size(aType)) {
-        ObjectPtr aObj = aRootDoc->object(aType, aObjId);
+      if (aObjId < aRootDoc->size(aType, true)) {
+        ObjectPtr aObj = aRootDoc->object(aType, aObjId, true);
         aIndex = objectIndex(aObj, theColumn);
       }
     }
@@ -652,7 +672,7 @@ QModelIndex XGUI_DataModel::index(int theRow, int theColumn, const QModelIndex &
     if (aId == 0) { // return object index inside of first level of folders
       std::string aType = myXMLReader->rootFolderType(aParentPos);
       if (theRow < aRootDoc->size(aType)) {
-        ObjectPtr aObj = aRootDoc->object(aType, theRow);
+        ObjectPtr aObj = aRootDoc->object(aType, theRow, true);
         aIndex = objectIndex(aObj, theColumn);
       }
     } else {
@@ -685,7 +705,7 @@ QModelIndex XGUI_DataModel::index(int theRow, int theColumn, const QModelIndex &
           } else {
             // this is an object under sub document root
             std::string aType = myXMLReader->subType();
-            ObjectPtr aObj = aSubDoc->object(aType, theRow - aNbSubFolders);
+            ObjectPtr aObj = aSubDoc->object(aType, theRow - aNbSubFolders, true);
             aIndex = objectIndex(aObj, theColumn);
           }
         } else {
@@ -704,6 +724,11 @@ QModelIndex XGUI_DataModel::index(int theRow, int theColumn, const QModelIndex &
                 dynamic_cast<ModelAPI_ResultField*>(aParentObj);
               if (aFieldRes) {
                 aIndex = createIndex(theRow, theColumn, aFieldRes->step(theRow));
+              } else {
+                ModelAPI_Folder* aFolder = dynamic_cast<ModelAPI_Folder*>(aParentObj);
+                ObjectPtr aObj = getObjectInFolder(aFolder, theRow);
+                if (aObj.get())
+                  aIndex = objectIndex(aObj, theColumn);
               }
             }
           }
@@ -734,7 +759,7 @@ QModelIndex XGUI_DataModel::parent(const QModelIndex& theIndex) const
     }
     ObjectPtr aObj = object(theIndex);
     if (!aObj.get()) {
-      // It can b e a step of a field
+      // It can be a step of a field
       ModelAPI_ResultField::ModelAPI_FieldStep* aStep =
         dynamic_cast<ModelAPI_ResultField::ModelAPI_FieldStep*>
         ((ModelAPI_Entity*)theIndex.internalPointer());
@@ -760,6 +785,11 @@ QModelIndex XGUI_DataModel::parent(const QModelIndex& theIndex) const
       if (aCompFea.get()) {
         return objectIndex(aCompFea);
       }
+      DocumentPtr aDoc = aFeature->document();
+      int aRow;
+      FolderPtr aFolder = aDoc->findContainingFolder(aFeature, aRow);
+      if (aFolder.get())
+        return objectIndex(aFolder);
     }
     ResultPtr aResult = std::dynamic_pointer_cast<ModelAPI_Result>(aObj);
     if (aResult.get()) {
@@ -773,7 +803,7 @@ QModelIndex XGUI_DataModel::parent(const QModelIndex& theIndex) const
     DocumentPtr aRootDoc = aSession->moduleDocument();
     DocumentPtr aSubDoc = aObj->document();
     if (aSubDoc == aRootDoc) {
-      if (aType == myXMLReader->rootType())
+      if ((aType == myXMLReader->rootType()) || (aType == ModelAPI_Folder::group()))
         return QModelIndex();
       else {
         // return first level of folder index
@@ -782,7 +812,7 @@ QModelIndex XGUI_DataModel::parent(const QModelIndex& theIndex) const
         return createIndex(aFolderId, 1, (void*)Q_NULLPTR);
       }
     } else {
-      if (aType == myXMLReader->subType())
+      if ((aType == myXMLReader->subType()) || (aType == ModelAPI_Folder::group()))
         return findDocumentRootIndex(aSubDoc.get());
       else {
         // return first level of folder index
@@ -912,11 +942,11 @@ QModelIndex
       }
     }
   } else { // If document is attached to feature
-    int aNb = aRootDoc->size(ModelAPI_Feature::group());
+    int aNb = aRootDoc->size(ModelAPI_Feature::group(), true);
     ObjectPtr aObj;
     ResultPartPtr aPartRes;
     for (int i = 0; i < aNb; i++) {
-      aObj = aRootDoc->object(ModelAPI_Feature::group(), i);
+      aObj = aRootDoc->object(ModelAPI_Feature::group(), i, true);
       aPartRes = getPartResult(aObj.get());
       if (aPartRes.get() && (aPartRes->partDoc().get() == theDoc)) {
         int aRow = i;
@@ -1119,3 +1149,45 @@ XGUI_DataModel::VisibilityState
   }
   return NoneState;
 }
+
+
+int XGUI_DataModel::getNumberOfFolderItems(const ModelAPI_Folder* theFolder) const
+{
+  DocumentPtr aDoc = theFolder->document();
+
+  FeaturePtr aFirstFeatureInFolder;
+  AttributeReferencePtr aFirstFeatAttr =
+      theFolder->data()->reference(ModelAPI_Folder::FIRST_FEATURE_ID());
+  if (aFirstFeatAttr)
+    aFirstFeatureInFolder = ModelAPI_Feature::feature(aFirstFeatAttr->value());
+  if (!aFirstFeatureInFolder.get())
+    return 0;
+
+  FeaturePtr aLastFeatureInFolder;
+  AttributeReferencePtr aLastFeatAttr =
+      theFolder->data()->reference(ModelAPI_Folder::LAST_FEATURE_ID());
+  if (aLastFeatAttr)
+    aLastFeatureInFolder = ModelAPI_Feature::feature(aLastFeatAttr->value());
+  if (!aLastFeatureInFolder.get())
+    return 0;
+
+  int aFirst = aDoc->index(aFirstFeatureInFolder);
+  int aLast = aDoc->index(aLastFeatureInFolder);
+  return aLast - aFirst + 1;
+}
+
+ObjectPtr XGUI_DataModel::getObjectInFolder(const ModelAPI_Folder* theFolder, int theId) const
+{
+  DocumentPtr aDoc = theFolder->document();
+
+  FeaturePtr aFirstFeatureInFolder;
+  AttributeReferencePtr aFirstFeatAttr =
+      theFolder->data()->reference(ModelAPI_Folder::FIRST_FEATURE_ID());
+  if (aFirstFeatAttr)
+    aFirstFeatureInFolder = ModelAPI_Feature::feature(aFirstFeatAttr->value());
+  if (!aFirstFeatureInFolder.get())
+    return ObjectPtr();
+
+  int aFirst = aDoc->index(aFirstFeatureInFolder);
+  return aDoc->object(ModelAPI_Feature::group(), aFirst + theId);
+}
index 53e9109801667ffbe392733c4636fb5dbbcd619d..d20679f7983742f681b4309247321b1331292581 100644 (file)
@@ -182,6 +182,9 @@ private:
   /// \param fromRoot - root document flag
   QStringList listOfShowNotEmptyFolders(bool fromRoot = true) const;
 
+  int getNumberOfFolderItems(const ModelAPI_Folder* theFolder) const;
+  ObjectPtr getObjectInFolder(const ModelAPI_Folder* theFolder, int theId) const;
+
   VisibilityState getVisibilityState(const QModelIndex& theIndex) const;
 
   void addShownFolder(DocumentPtr theDoc, QString theFolder)
index eebb23d221e1983816e69be758bfcede126ac176..ed7e5e23330e21ab0925e668d5cb580c0f4a8e13 100755 (executable)
@@ -220,3 +220,23 @@ void XGUI_SelectionMgr::convertToObjectBrowserSelection(
     }
   }
 }
+
+std::list<FeaturePtr> XGUI_SelectionMgr::getSelectedFeatures()
+{
+  std::list<FeaturePtr> aFeatures;
+  QObjectPtrList aObjects = selection()->selectedObjects();
+  if (aObjects.isEmpty())
+    return aFeatures;
+
+  bool isPart = false;
+  foreach(ObjectPtr aObj, aObjects) {
+    FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aObj);
+    if (aFeature.get()) {
+      ResultPtr aRes = aFeature->firstResult();
+      isPart = (aRes.get() && (aRes->groupName() == ModelAPI_ResultPart::group()));
+      if (!isPart)
+        aFeatures.push_back(aFeature);
+    }
+  }
+  return aFeatures;
+}
\ No newline at end of file
index 25bb95d85b774d0452595435a47041cdc1f8bf53..06e6fe54e5d0f19274413f0a56504b9db9722940 100644 (file)
@@ -24,6 +24,7 @@
 #include "XGUI.h"
 #include <ModuleBase_Definitions.h>
 #include <ModuleBase_ISelection.h>
+#include <ModelAPI_Feature.h>
 #include <QObject>
 #include <QModelIndexList>
 
@@ -77,6 +78,9 @@ Q_OBJECT
   /// \param thePlace a widget where selection has happened.
   void updateSelectionBy(const ModuleBase_ISelection::SelectionPlace& thePlace);
 
+  /// Returns list of selected features (ignores other selected objects and parts)
+  std::list<FeaturePtr> getSelectedFeatures();
+
 signals:
   //! Emited when selection in a one of viewers was changed
   void selectionChanged();
index 17be4e5309218cd94261291618e6ae54b8dc33ff..5112ff24ccbbfe703fcaa1763a46c9c50d8d787f 100755 (executable)
@@ -1452,6 +1452,16 @@ void XGUI_Workshop::onContextMenuCommand(const QString& theId, bool isChecked)
     setViewerSelectionMode(TopAbs_EDGE);
   } else if (theId == "SELECT_FACE_CMD") {
     setViewerSelectionMode(TopAbs_FACE);
+  } else if (theId == "INSERT_FOLDER_CMD") {
+    insertFeatureFolder();
+  } else if (theId == "ADD_TO_FOLDER_BEFORE_CMD") {
+    insertToFolder(true);
+  } else if (theId == "ADD_TO_FOLDER_AFTER_CMD") {
+    insertToFolder(false);
+  } else if (theId == "ADD_OUT_FOLDER_BEFORE_CMD") {
+    moveOutFolder(true);
+  } else if (theId == "ADD_OUT_FOLDER_AFTER_CMD") {
+    moveOutFolder(false);
   } else if (theId == "SELECT_RESULT_CMD") {
     //setViewerSelectionMode(-1);
     //IMP: an attempt to use result selection with other selection modes
@@ -1546,9 +1556,10 @@ void XGUI_Workshop::deleteObjects()
   bool hasParameter = false;
   bool hasCompositeOwner = false;
   bool hasResultInHistory = false;
+  bool hasFolder = false;
   ModuleBase_Tools::checkObjects(anObjects, hasResult, hasFeature, hasParameter, hasCompositeOwner,
-                                 hasResultInHistory);
-  if (!(hasFeature || hasParameter))
+                                 hasResultInHistory, hasFolder);
+  if (!(hasFeature || hasParameter || hasFolder))
     return;
 
   // delete objects
@@ -1557,6 +1568,9 @@ void XGUI_Workshop::deleteObjects()
   ModuleBase_Tools::convertToFeatures(anObjects, aFeatures);
   ModelAPI_Tools::findAllReferences(aFeatures, aReferences);
 
+  std::set<FolderPtr> aFolders;
+  ModuleBase_Tools::convertToFolders(anObjects, aFolders);
+
   bool aDone = false;
   QString aDescription = contextMenuMgr()->action("DELETE_CMD")->text() + " %1";
   aDescription = aDescription.arg(XGUI_Tools::unionOfObjectNames(anObjects, ", "));
@@ -1575,6 +1589,18 @@ void XGUI_Workshop::deleteObjects()
       aFeatures.insert(aFeatureRefsToDelete.begin(), aFeatureRefsToDelete.end());
     aDone = ModelAPI_Tools::removeFeatures(aFeatures, false);
   }
+  if (aFolders.size() > 0) {
+    std::set<FolderPtr>::const_iterator anIt = aFolders.begin(),
+                                         aLast = aFolders.end();
+    for (; anIt != aLast; anIt++) {
+      FolderPtr aFolder = *anIt;
+      if (aFolder.get()) {
+        DocumentPtr aDoc = aFolder->document();
+        aDoc->removeFolder(aFolder);
+      }
+    }
+  }
+
   if (aDone)
     operationMgr()->commitOperation();
   else
@@ -2465,3 +2491,54 @@ void XGUI_Workshop::highlightFeature(const QObjectPtrList& theObjects)
     objectBrowser()->blockSignals(aBlocked);
   }
 }
+
+void XGUI_Workshop::insertFeatureFolder()
+{
+  QObjectPtrList aObjects = mySelector->selection()->selectedObjects();
+  if (aObjects.isEmpty())
+    return;
+  ObjectPtr aObj = aObjects.first();
+  FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aObj);
+  if (aFeature.get() == NULL)
+    return;
+  SessionPtr aMgr = ModelAPI_Session::get();
+  DocumentPtr aDoc = aMgr->activeDocument();
+
+  aMgr->startOperation();
+  aDoc->addFolder(aFeature);
+  aMgr->finishOperation();
+}
+
+
+void XGUI_Workshop::insertToFolder(bool isBefore)
+{
+  std::list<FeaturePtr> aFeatures = mySelector->getSelectedFeatures();
+  if (aFeatures.empty())
+    return;
+
+  SessionPtr aMgr = ModelAPI_Session::get();
+  DocumentPtr aDoc = aMgr->activeDocument();
+
+  FolderPtr aFolder = isBefore? aDoc->findFolderAbove(aFeatures):
+                                aDoc->findFolderBelow(aFeatures);
+  if (!aFolder.get())
+    return;
+
+  aMgr->startOperation();
+  aDoc->moveToFolder(aFeatures, aFolder);
+  aMgr->finishOperation();
+}
+
+void XGUI_Workshop::moveOutFolder(bool isBefore)
+{
+  std::list<FeaturePtr> aFeatures = mySelector->getSelectedFeatures();
+  if (aFeatures.empty())
+    return;
+
+  SessionPtr aMgr = ModelAPI_Session::get();
+  DocumentPtr aDoc = aMgr->activeDocument();
+
+  aMgr->startOperation();
+  aDoc->removeFromFolder(aFeatures, isBefore);
+  aMgr->finishOperation();
+}
\ No newline at end of file
index 871e2df122e9c1792de47a06f0aaf3b9beaa24f7..8a96cfa803fd838891e70dad003e90cf6974a787 100755 (executable)
@@ -448,6 +448,15 @@ private:
   /// Display results from a group
   void displayGroupResults(DocumentPtr theDoc, std::string theGroup);
 
+  /// Insert folder object before currently selected feature
+  void insertFeatureFolder();
+
+  /// Insert an object to a folder above or below
+  void insertToFolder(bool isBefore);
+
+  /// Insert an object to a folder above or below
+  void moveOutFolder(bool isBefore);
+
  private slots:
   /// SLOT, that is called after the operation is started. Update workshop state according to
   /// the started operation, e.g. visualizes the property panel and connect to it.
index d2f9f767e9b3b78a12f0140004ff3f5c5856298f..cc92ee6fb0955fdc58ca6f119adfa1a2e819d53d 100644 (file)
      <file>pictures/eyeopen.png</file>
 
      <file>pictures/transparency.png</file>
+     <file>pictures/features_folder.png</file>
+     <file>pictures/create_folder.png</file>
+     <file>pictures/insert_folder_after.png</file>
+     <file>pictures/insert_folder_before.png</file>
+     <file>pictures/move_out_after.png</file>
+     <file>pictures/move_out_before.png</file>
  </qresource>
  </RCC>
diff --git a/src/XGUI/pictures/create_folder.png b/src/XGUI/pictures/create_folder.png
new file mode 100644 (file)
index 0000000..371d86d
Binary files /dev/null and b/src/XGUI/pictures/create_folder.png differ
diff --git a/src/XGUI/pictures/features_folder.png b/src/XGUI/pictures/features_folder.png
new file mode 100644 (file)
index 0000000..055992b
Binary files /dev/null and b/src/XGUI/pictures/features_folder.png differ
diff --git a/src/XGUI/pictures/insert_folder_after.png b/src/XGUI/pictures/insert_folder_after.png
new file mode 100644 (file)
index 0000000..f10a6c9
Binary files /dev/null and b/src/XGUI/pictures/insert_folder_after.png differ
diff --git a/src/XGUI/pictures/insert_folder_before.png b/src/XGUI/pictures/insert_folder_before.png
new file mode 100644 (file)
index 0000000..2563478
Binary files /dev/null and b/src/XGUI/pictures/insert_folder_before.png differ
diff --git a/src/XGUI/pictures/move_out_after.png b/src/XGUI/pictures/move_out_after.png
new file mode 100644 (file)
index 0000000..6adcf64
Binary files /dev/null and b/src/XGUI/pictures/move_out_after.png differ
diff --git a/src/XGUI/pictures/move_out_before.png b/src/XGUI/pictures/move_out_before.png
new file mode 100644 (file)
index 0000000..cf248a6
Binary files /dev/null and b/src/XGUI/pictures/move_out_before.png differ