Salome HOME
Task #3059 implementation: When “move to the end” of a group, propose to the user...
authormpv <mpv@opencascade.com>
Wed, 4 Dec 2019 06:35:18 +0000 (09:35 +0300)
committermpv <mpv@opencascade.com>
Wed, 4 Dec 2019 06:35:18 +0000 (09:35 +0300)
20 files changed:
doc/gui/Introduction.rst
doc/gui/images/popup_menu_object_browser_feature.png
src/CollectionPlugin/CMakeLists.txt
src/CollectionPlugin/CollectionPlugin_Group.cpp
src/CollectionPlugin/CollectionPlugin_Group.h
src/CollectionPlugin/Test/TestGroupMoveAndSplit1.py [new file with mode: 0644]
src/CollectionPlugin/Test/TestGroupMoveAndSplit2.py [new file with mode: 0644]
src/CollectionPlugin/Test/TestGroupMoveAndSplit3.py [new file with mode: 0644]
src/Model/Model_Document.cpp
src/Model/Model_Document.h
src/ModelAPI/ModelAPI_Document.h
src/PartSet/PartSet_Module.cpp
src/SketcherPrs/SketcherPrs_SymbolPrs.cpp
src/XGUI/XGUI_ContextMenuMgr.cpp
src/XGUI/XGUI_Workshop.cpp
src/XGUI/XGUI_Workshop.h
src/XGUI/XGUI_msg_fr.ts
src/XGUI/XGUI_pictures.qrc
src/XGUI/pictures/move_to_end.png [new file with mode: 0644]
src/XGUI/pictures/move_to_end_split.png [new file with mode: 0644]

index d78aba3b930526f643ac00cd2225e28baf0a7c59..c35c806f64609bfa65a0e587bdb6d1e64a8e1fd1 100644 (file)
@@ -241,7 +241,7 @@ Each feature, result, construction, group, field, parameter can be renamed using
 .. centered::\r
    Construction pop-up menu\r
 \r
-The order of features can be changed using *Move to the end* pop-up menu command. It works only for Group features. The selected group will be moved to the end of features list.\r
+The order of features can be changed using *Move to the end* and *Move to the end and split* pop-up menu commands. They work only for Group features. The selected group or several groups will be moved to the end of features list. The *Move to the end and split* also splits the resulting group in several groups: one group per one selection.\r
 \r
 Folders can be used to arrange long Tree View for features.\r
 \r
index 30e40564de322526f602705c080f79103c65dbd7..ed32e2f4cfcf22a0645cfd4f37baf54fee8ec086 100755 (executable)
Binary files a/doc/gui/images/popup_menu_object_browser_feature.png and b/doc/gui/images/popup_menu_object_browser_feature.png differ
index a02f2aa6642d39c5d04f2a7f60cb5dc3c46f5b48..a8770cb7c7059cf0a694b4f22f6fc9b1433f060e 100644 (file)
@@ -160,4 +160,7 @@ ADD_UNIT_TESTS(
                Test3031.py
                TestGroupWholeFeature1.py
                TestGroupWholeFeature2.py
+               TestGroupMoveAndSplit1.py
+               TestGroupMoveAndSplit2.py
+               TestGroupMoveAndSplit3.py
 )
index 82926ab21921946ac76ac567b84f900ec9eb16bb..c2110a64ee9ed21c8b99c8b9bd83212a79790eae 100644 (file)
@@ -24,7 +24,9 @@
 #include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_AttributeString.h>
 #include <ModelAPI_AttributeSelectionList.h>
+#include <ModelAPI_AttributeIntArray.h>
 #include <ModelAPI_ResultGroup.h>
+#include <sstream>
 
 CollectionPlugin_Group::CollectionPlugin_Group()
 {
@@ -44,3 +46,107 @@ void CollectionPlugin_Group::execute()
     setResult(aGroup);
   }
 }
+
+// returns name with suffix, not existing in the existing set
+static std::string findName(
+  const std::string theOrigin, int& theSuffix, std::set<std::string>& theExisting)
+{
+  std::string aRes;
+  do {
+    std::ostringstream aName;
+    aName<<theOrigin<<"_"<<theSuffix;
+    aRes = aName.str();
+    theSuffix++;
+  } while(theExisting.count(aRes));
+  theExisting.insert(aRes);
+  return aRes;
+
+}
+
+bool CollectionPlugin_Group::customAction(const std::string& theActionId)
+{
+  if (theActionId == "split") {
+    DocumentPtr aDoc = document();
+    // collect all existing names of features to give unique names
+    std::set<std::string> aFeatNames, aResNames;
+    std::list<FeaturePtr> allFeat = aDoc->allFeatures();
+    std::list<FeaturePtr>::iterator allFeatIter = allFeat.begin();
+    for(; allFeatIter != allFeat.end(); allFeatIter++) {
+      FeaturePtr aFeat = *allFeatIter;
+      if (aFeat->data().get() && aFeat->data()->isValid()) {
+        aFeatNames.insert(aFeat->name());
+        if (aFeat->getKind() == ID() && aFeat->data().get() && aFeat->data()->isValid()) {
+           std::list<ResultPtr>::const_iterator aRess = aFeat->results().cbegin();
+           for(; aRess != aFeat->results().cend(); aRess++) {
+             ResultPtr aRes = *aRess;
+             if (aRes->data().get() && aRes->data()->isValid()) {
+               aResNames.insert(aRes->data()->name());
+             }
+           }
+        }
+      }
+    }
+
+    AttributeSelectionListPtr aList = selectionList(LIST_ID());
+    std::set<int> aRemoved;
+    bool aStay = false; // to indicate that the good attribute found stays in the list
+    int anIndex = 1; // index of the name assigned to group-feature and result
+    // added in the order: 3 2 1 orig=0, so, keep the results to give names later
+    std::list<ObjectPtr> aResults;
+    for(int aNext = aList->size() - 1; aNext >= 0; aNext--) {
+      AttributeSelectionPtr anOldAttr = aList->value(aNext);
+      if (anOldAttr->isInvalid() || !anOldAttr->context().get()) {// remove invalids
+        aRemoved.insert(aNext);
+        continue;
+      }
+      if (!aStay) {
+        aStay = true;
+        continue;
+      }
+      aRemoved.insert(aNext);
+      FeaturePtr aNew = aDoc->addFeature(ID(), false);
+      AttributeSelectionListPtr aNewList = aNew->selectionList(LIST_ID());
+      aNewList->setSelectionType(aList->selectionType());
+      aNewList->append(anOldAttr->contextObject(), anOldAttr->value());
+      aResults.push_front(aNew); // to keep the order
+    }
+    aResults.push_back(data()->owner());
+    // remove all selections except the first
+    aList->remove(aRemoved);
+    // set names
+    if (aResults.size() > 1) { // rename if there are new groups appeared only
+      std::list<ObjectPtr>::iterator aResIter = aResults.begin();
+      for(int aSuffix = 1; aResIter != aResults.end(); aResIter++) {
+        FeaturePtr aFeat = std::dynamic_pointer_cast<ModelAPI_Feature>(*aResIter);
+        aFeat->data()->setName(findName(name(), aSuffix, aFeatNames));
+        if (!aFeat->results().empty() && !results().empty()) {
+          int aResSuf = aSuffix - 1;
+          std::string aResName = findName(firstResult()->data()->name(), aResSuf, aResNames);
+          aFeat->firstResult()->data()->setName(aResName);
+          // set the same color of result as in origin
+          if (firstResult()->data()->attribute(ModelAPI_Result::COLOR_ID()).get()) {
+            AttributeIntArrayPtr aSourceColor =
+              firstResult()->data()->intArray(ModelAPI_Result::COLOR_ID());
+            if (aSourceColor.get() && aSourceColor->size()) {
+              AttributeIntArrayPtr aDestColor =
+                aFeat->firstResult()->data()->intArray(ModelAPI_Result::COLOR_ID());
+              aDestColor->setSize(aSourceColor->size());
+              for(int a = 0; a < aSourceColor->size(); a++)
+                aDestColor->setValue(a, aSourceColor->value(a));
+            }
+          }
+        }
+      }
+      // remove also filters if split performed
+      FiltersFeaturePtr aFilters = aList->filters();
+      if (aFilters.get()) {
+        std::list<std::string> aFiltersList = aFilters->filters();
+        std::list<std::string>::iterator aFilterName = aFiltersList.begin();
+        for(; aFilterName != aFiltersList.end(); aFilterName++) {
+          aFilters->removeFilter(*aFilterName);
+        }
+      }
+    }
+  }
+  return true;
+}
index 0d61046e3b5dfe003c37fbe7b5b8e93c36a510d1..3c1dd0d165a3a020314170a8ee3119868c96f03b 100644 (file)
@@ -66,6 +66,8 @@ class CollectionPlugin_Group : public ModelAPI_Feature
   /// Use plugin manager for features creation
   CollectionPlugin_Group();
 
+  /// Used for the split action of the group (Move to the end and split)
+  COLLECTIONPLUGIN_EXPORT virtual bool customAction(const std::string& theActionId);
 };
 
 #endif
diff --git a/src/CollectionPlugin/Test/TestGroupMoveAndSplit1.py b/src/CollectionPlugin/Test/TestGroupMoveAndSplit1.py
new file mode 100644 (file)
index 0000000..d468c4d
--- /dev/null
@@ -0,0 +1,63 @@
+# Copyright (C) 2014-2019  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+# Check the specification case of move to the end and split (#3059)
+
+from salome.shaper import model
+from ModelAPI import *
+from GeomAPI import *
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [], model.selection(), 12, 0)
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchCircle_1 = Sketch_1.addCircle(33.32502963835739, 19.24021483244179, 5)
+SketchConstraintRadius_1 = Sketch_1.setRadius(SketchCircle_1.results()[1], 5)
+SketchLine_1 = Sketch_1.addLine(0, 0, 33.32502963835739, 19.24021483244179)
+SketchLine_1.setAuxiliary(True)
+SketchProjection_1 = Sketch_1.addProjection(model.selection("VERTEX", "PartSet/Origin"), False)
+SketchPoint_1 = SketchProjection_1.createdFeature()
+SketchConstraintCoincidence_1 = Sketch_1.setCoincident(SketchLine_1.startPoint(), SketchPoint_1.result())
+SketchConstraintCoincidence_2 = Sketch_1.setCoincident(SketchCircle_1.center(), SketchLine_1.endPoint())
+SketchProjection_2 = Sketch_1.addProjection(model.selection("EDGE", "PartSet/OX"), False)
+SketchLine_2 = SketchProjection_2.createdFeature()
+SketchConstraintAngle_1 = Sketch_1.setAngle(SketchLine_2.result(), SketchLine_1.result(), 30)
+Extrusion_1.setNestedSketch(Sketch_1)
+Group_1 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Extrusion_1_1/To_Face")])
+AngularCopy_1 = model.addMultiRotation(Part_1_doc, [model.selection("SOLID", "Extrusion_1_1")], model.selection("EDGE", "PartSet/OZ"), 12)
+model.do()
+Part_1_doc.moveFeature(Group_1.feature(), AngularCopy_1.feature(), True)
+model.end()
+
+# must be created 12 groups of faces, 12 results
+assert(Part_1_doc.size("Groups") == 12)
+
+for i in range(12):
+  resShape = modelAPI_Result(Part_1_doc.object("Groups", i)).shape()
+  assert(not resShape.isNull())
+  # the group result is a compund, check that this is a compound of one face
+  aShapeExplorer = GeomAPI_ShapeExplorer(resShape, GeomAPI_Shape.FACE)
+  assert(aShapeExplorer.more())
+  assert(aShapeExplorer.current().isFace())
+  aShapeExplorer.next()
+  assert(not aShapeExplorer.more())
+
+assert(model.checkPythonDump())
diff --git a/src/CollectionPlugin/Test/TestGroupMoveAndSplit2.py b/src/CollectionPlugin/Test/TestGroupMoveAndSplit2.py
new file mode 100644 (file)
index 0000000..c39ae45
--- /dev/null
@@ -0,0 +1,95 @@
+# Copyright (C) 2014-2019  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+# Check the movement to the end and split: move to intermediate position, no duplicates appeared
+
+from SketchAPI import *
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchProjection_1 = Sketch_1.addProjection(model.selection("VERTEX", "PartSet/Origin"), False)
+SketchPoint_1 = SketchProjection_1.createdFeature()
+SketchCircle_1 = Sketch_1.addCircle(0, 0, 10)
+SketchConstraintCoincidence_1 = Sketch_1.setCoincident(SketchPoint_1.result(), SketchCircle_1.center())
+SketchConstraintRadius_1 = Sketch_1.setRadius(SketchCircle_1.results()[1], 10)
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchCircle_1_2f")], model.selection(), 10, 0)
+Group_1 = model.addGroup(Part_1_doc, "Edges", [model.selection("EDGE", "[Extrusion_1_1/Generated_Face&Sketch_1/SketchCircle_1_2][Extrusion_1_1/To_Face]")])
+ExtrusionCut_1 = model.addExtrusionCut(Part_1_doc, [], model.selection(), 0, 5, [model.selection("SOLID", "Extrusion_1_1")])
+Sketch_2 = model.addSketch(Part_1_doc, model.selection("FACE", "Extrusion_1_1/To_Face"))
+SketchProjection_2 = Sketch_2.addProjection(model.selection("EDGE", "[Extrusion_1_1/Generated_Face&Sketch_1/SketchCircle_1_2][Extrusion_1_1/To_Face]"), False)
+SketchCircle_2 = SketchProjection_2.createdFeature()
+SketchCircle_3 = Sketch_2.addCircle(0, 10, 3)
+SketchConstraintCoincidence_2 = Sketch_2.setCoincident(SketchCircle_2.results()[1], SketchCircle_3.center())
+SketchCircle_4 = Sketch_2.addCircle(0, -10, 3)
+SketchConstraintCoincidence_3 = Sketch_2.setCoincident(SketchCircle_2.results()[1], SketchCircle_4.center())
+SketchProjection_3 = Sketch_2.addProjection(model.selection("EDGE", "PartSet/OY"), True)
+SketchLine_1 = SketchProjection_3.createdFeature()
+SketchConstraintCoincidence_4 = Sketch_2.setCoincident(SketchCircle_3.center(), SketchLine_1.result())
+SketchConstraintCoincidence_5 = Sketch_2.setCoincident(SketchCircle_4.center(), SketchLine_1.result())
+SketchConstraintRadius_2 = Sketch_2.setRadius(SketchCircle_3.results()[1], 3)
+SketchConstraintEqual_1 = Sketch_2.setEqual(SketchCircle_3.results()[1], SketchCircle_4.results()[1])
+ExtrusionCut_1.setNestedSketch(Sketch_2)
+
+ExtrusionCut_2 = model.addExtrusionCut(Part_1_doc, [], model.selection(), 0, 3, [model.selection("SOLID", "ExtrusionCut_1_1")])
+Sketch_3 = model.addSketch(Part_1_doc, model.selection("FACE", "ExtrusionCut_1_1/Modified_Face&Extrusion_1_1/To_Face"))
+SketchLine_2 = Sketch_3.addLine(10, 2, -10, 2)
+SketchLine_3 = Sketch_3.addLine(-10, 2, -10, -2)
+SketchLine_4 = Sketch_3.addLine(-10, -2, 10, -2)
+SketchLine_5 = Sketch_3.addLine(10, -2, 10, 2)
+SketchConstraintCoincidence_6 = Sketch_3.setCoincident(SketchLine_5.endPoint(), SketchLine_2.startPoint())
+SketchConstraintCoincidence_7 = Sketch_3.setCoincident(SketchLine_2.endPoint(), SketchLine_3.startPoint())
+SketchConstraintCoincidence_8 = Sketch_3.setCoincident(SketchLine_3.endPoint(), SketchLine_4.startPoint())
+SketchConstraintCoincidence_9 = Sketch_3.setCoincident(SketchLine_4.endPoint(), SketchLine_5.startPoint())
+SketchConstraintHorizontal_1 = Sketch_3.setHorizontal(SketchLine_2.result())
+SketchConstraintVertical_1 = Sketch_3.setVertical(SketchLine_3.result())
+SketchConstraintHorizontal_2 = Sketch_3.setHorizontal(SketchLine_4.result())
+SketchConstraintVertical_2 = Sketch_3.setVertical(SketchLine_5.result())
+SketchProjection_4 = Sketch_3.addProjection(model.selection("EDGE", "([ExtrusionCut_1_1/Modified_Face&Sketch_1/SketchCircle_1_2][Extrusion_1_1/From_Face])2(ExtrusionCut_1_1/Generated_Edge&ExtrusionCut_1_1/From_Face_1)2([ExtrusionCut_1_1/Modified_Face&Extrusion_1_1/To_Face][ExtrusionCut_1_1/Generated_Face&Sketch_2/SketchCircle_4_2])2"), False)
+SketchArc_1 = SketchProjection_4.createdFeature()
+SketchConstraintTangent_1 = Sketch_3.setTangent(SketchLine_5.result(), SketchArc_1.results()[1])
+SketchProjection_5 = Sketch_3.addProjection(model.selection("EDGE", "([ExtrusionCut_1_1/Modified_Face&Extrusion_1_1/To_Face][ExtrusionCut_1_1/Generated_Face&Sketch_2/SketchCircle_4_2])(ExtrusionCut_1_1/Generated_Edge&ExtrusionCut_1_1/From_Face_3)2(ExtrusionCut_1_1/Generated_Edge&ExtrusionCut_1_1/From_Face_2)2([ExtrusionCut_1_1/Generated_Face&Sketch_2/SketchCircle_4_2][ExtrusionCut_1_1/Modified_Face&ExtrusionCut_1_1/From_Face_3])2"), False)
+SketchArc_2 = SketchProjection_5.createdFeature()
+SketchConstraintTangent_2 = Sketch_3.setTangent(SketchArc_2.results()[1], SketchLine_3.result())
+SketchConstraintDistanceVertical_1 = Sketch_3.setVerticalDistance(SketchAPI_Arc(SketchArc_1).center(), SketchLine_2.startPoint(), 2)
+SketchConstraintDistanceVertical_2 = Sketch_3.setVerticalDistance(SketchAPI_Arc(SketchArc_1).center(), SketchLine_4.endPoint(), 2)
+ExtrusionCut_2.setNestedSketch(Sketch_3)
+model.do()
+# move only after the first extrusion-cut
+Part_1_doc.setCurrentFeature(ExtrusionCut_1.feature(), True)
+model.do()
+Part_1_doc.moveFeature(Group_1.feature(), ExtrusionCut_1.feature(), True)
+model.end()
+assert(Part_1_doc.size("Groups") == 3) # 3 edges in groups results
+
+# check that simple move to the end provides 4 edges (no duplicates)
+model.undo()
+model.undo()
+
+model.begin()
+Part_1_doc.moveFeature(Group_1.feature(), ExtrusionCut_2.feature(), True)
+model.end()
+assert(Part_1_doc.size("Groups") == 4) # 4 edges in groups results
+
+assert(model.checkPythonDump())
diff --git a/src/CollectionPlugin/Test/TestGroupMoveAndSplit3.py b/src/CollectionPlugin/Test/TestGroupMoveAndSplit3.py
new file mode 100644 (file)
index 0000000..0472376
--- /dev/null
@@ -0,0 +1,76 @@
+# Copyright (C) 2014-2019  CEA/DEN, EDF R&D
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+# Check the movement to the end and split: whole results, check names of splitted results and groups
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [], model.selection(), 10, 0)
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchLine_1 = Sketch_1.addLine(-8.333743842364534, 20.52339901477833, -20.15024630541872, 20.52339901477833)
+SketchLine_2 = Sketch_1.addLine(-20.15024630541872, 20.52339901477833, -20.15024630541872, 3.980295566502462)
+SketchLine_3 = Sketch_1.addLine(-20.15024630541872, 3.980295566502462, -8.333743842364534, 3.980295566502462)
+SketchLine_4 = Sketch_1.addLine(-8.333743842364534, 3.980295566502462, -8.333743842364534, 20.52339901477833)
+SketchConstraintCoincidence_1 = Sketch_1.setCoincident(SketchLine_4.endPoint(), SketchLine_1.startPoint())
+SketchConstraintCoincidence_2 = Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint())
+SketchConstraintCoincidence_3 = Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchLine_3.startPoint())
+SketchConstraintCoincidence_4 = Sketch_1.setCoincident(SketchLine_3.endPoint(), SketchLine_4.startPoint())
+SketchConstraintHorizontal_1 = Sketch_1.setHorizontal(SketchLine_1.result())
+SketchConstraintVertical_1 = Sketch_1.setVertical(SketchLine_2.result())
+SketchConstraintHorizontal_2 = Sketch_1.setHorizontal(SketchLine_3.result())
+SketchConstraintVertical_2 = Sketch_1.setVertical(SketchLine_4.result())
+SketchCircle_1 = Sketch_1.addCircle(5.721674876847291, 12.81157635467982, 6.421166795138789)
+Extrusion_1.setNestedSketch(Sketch_1)
+Group_1 = model.addGroup(Part_1_doc, "Faces", [model.selection("SOLID", "Extrusion_1_1"), model.selection("SOLID", "Extrusion_1_2")])
+Group_1.setName("GroupResult")
+Group_1.result().setName("GroupResult")
+Sketch_2 = model.addSketch(Part_1_doc, model.selection("FACE", "Extrusion_1_1/To_Face"))
+SketchLine_5 = Sketch_2.addLine(3.924377723198604, 15.23693857548147, -14.36967929032953, 15.23693857548147)
+SketchLine_6 = Sketch_2.addLine(-14.36967929032953, 15.23693857548147, -14.36967929032953, 11.61585476914922)
+SketchLine_7 = Sketch_2.addLine(-14.36967929032953, 11.61585476914922, 3.924377723198604, 11.61585476914922)
+SketchLine_8 = Sketch_2.addLine(3.924377723198604, 11.61585476914922, 3.924377723198604, 15.23693857548147)
+SketchConstraintCoincidence_5 = Sketch_2.setCoincident(SketchLine_8.endPoint(), SketchLine_5.startPoint())
+SketchConstraintCoincidence_6 = Sketch_2.setCoincident(SketchLine_5.endPoint(), SketchLine_6.startPoint())
+SketchConstraintCoincidence_7 = Sketch_2.setCoincident(SketchLine_6.endPoint(), SketchLine_7.startPoint())
+SketchConstraintCoincidence_8 = Sketch_2.setCoincident(SketchLine_7.endPoint(), SketchLine_8.startPoint())
+SketchConstraintHorizontal_3 = Sketch_2.setHorizontal(SketchLine_5.result())
+SketchConstraintVertical_3 = Sketch_2.setVertical(SketchLine_6.result())
+SketchConstraintHorizontal_4 = Sketch_2.setHorizontal(SketchLine_7.result())
+SketchConstraintVertical_4 = Sketch_2.setVertical(SketchLine_8.result())
+model.do()
+Extrusion_2 = model.addExtrusion(Part_1_doc, [model.selection("WIRE", "Sketch_2/Face-SketchLine_5r-SketchLine_6f-SketchLine_7f-SketchLine_8f_wire")], model.selection(), 2, 5)
+Fuse_1 = model.addFuse(Part_1_doc, [model.selection("SOLID", "Extrusion_1_1"), model.selection("SOLID", "Extrusion_2_1")], keepSubResults = True)
+model.do()
+Part_1_doc.moveFeature(Group_1.feature(), Fuse_1.feature(), True)
+model.end()
+
+assert(Part_1_doc.size("Groups") == 2) # 2 results because initially 2 results were selected
+
+# check names of results
+from ModelAPI import *
+res1 = modelAPI_Result(Part_1_doc.object("Groups", 0))
+assert(res1.data().name() == "GroupResult_1")
+res2 = modelAPI_Result(Part_1_doc.object("Groups", 1))
+assert(res2.data().name() == "GroupResult_2")
+
+assert(model.checkPythonDump())
index b1efb51d0fbf3a1829c41fc17ad9887985bac883..79162a77945b2302e00d7ddc8818358b7cd60457 100644 (file)
@@ -1239,7 +1239,7 @@ static bool isSub(const CompositeFeaturePtr theMain, const FeaturePtr theSub) {
   return isSub(theMain, aParent);
 }
 
-void Model_Document::moveFeature(FeaturePtr theMoved, FeaturePtr theAfterThis)
+void Model_Document::moveFeature(FeaturePtr theMoved, FeaturePtr theAfterThis, const bool theSplit)
 {
   bool aCurrentUp = theMoved == currentFeature(false);
   if (aCurrentUp) {
@@ -1264,12 +1264,21 @@ void Model_Document::moveFeature(FeaturePtr theMoved, FeaturePtr theAfterThis)
   }
 
   myObjs->moveFeature(theMoved, anAfterThisSub);
+
+  if (theSplit) { // split the group into sub-features
+    theMoved->customAction("split");
+  }
+
   if (aCurrentUp) { // make the moved feature enabled or disabled due to the real status
     setCurrentFeature(currentFeature(false), false);
   } else if (theAfterThis == currentFeature(false) || anAfterThisSub == currentFeature(false)) {
     // must be after move to make enabled all features which are before theMoved
     setCurrentFeature(theMoved, true);
   }
+
+  if (theSplit) { // split the group into sub-features
+    theMoved->customAction("split");
+  }
 }
 
 void Model_Document::updateHistory(const std::shared_ptr<ModelAPI_Object> theObject)
index 1615ffce436b3876abf285df3b019d9f744cbffb..277c822ba629730adc39a796a6a78192f72afcc2 100644 (file)
@@ -127,7 +127,8 @@ class Model_Document : public ModelAPI_Document
   MODEL_EXPORT virtual void removeFeature(FeaturePtr theFeature);
 
   //! Moves the feature to make it after the given one in the history.
-  MODEL_EXPORT virtual void moveFeature(FeaturePtr theMoved, FeaturePtr theAfterThis);
+  MODEL_EXPORT virtual void moveFeature(
+    FeaturePtr theMoved, FeaturePtr theAfterThis, const bool theSplit = false);
 
   //! Returns the first found object in the group by the object name
   //! \param theGroupID group that contains an object
index 5f51a3376418c40a60d9da42b643a19debb07355..602c4e8033280f0b9eeba6ca31d78b677220f75e 100644 (file)
@@ -81,7 +81,8 @@ public:
 
   //! Moves the feature to make it after the given one in the history.
   virtual void moveFeature(std::shared_ptr<ModelAPI_Feature> theMoved,
-                           std::shared_ptr<ModelAPI_Feature> theAfterThis) = 0;
+                           std::shared_ptr<ModelAPI_Feature> theAfterThis,
+                           const bool theSplit = false) = 0;
 
   ///! Returns the id of the document
   virtual const int id() const = 0;
index 342134c7a56a353dc290ba7f118e4d8d4e25214a..f3e9a285618d37d6655bfae5944201595ffc54a9 100644 (file)
@@ -553,7 +553,7 @@ bool PartSet_Module::canRedo() const
 bool PartSet_Module::canApplyAction(const ObjectPtr& theObject, const QString& theActionId) const
 {
   bool aValid = true;
-  if (theActionId == "MOVE_CMD") {
+  if (theActionId == "MOVE_CMD" || theActionId == "MOVE_SPLIT_CMD") {
     FeaturePtr aFeature = ModelAPI_Feature::feature(theObject);
     if (aFeature) {
       ResultPtr aResult = ModuleBase_Tools::firstResult(aFeature);
index 5f79c5fbaec5d90b5dca3abe513ef441be0c4bf3..e10434cf9e3f024f5ee28610480786d44dc8e377 100644 (file)
@@ -185,7 +185,7 @@ Handle(Image_AlienPixMap) SketcherPrs_SymbolPrs::icon()
       aSizedMap->InitTrash(aPixMap->Format(), aWidth, aHeigh);
       for (Standard_Size i = 0; i < aWidth; i++) {
         for (Standard_Size j = 0; j < aHeigh; j++) {
-          aSizedMap->SetPixelColor(i, j, aPixMap->PixelColor(i / aRatio, j / aRatio));
+          aSizedMap->SetPixelColor(int(i), int(j), aPixMap->PixelColor(i / aRatio, j / aRatio));
         }
       }
       aPixMap = aSizedMap;
index 60db4013ccbd13c7982158734a7c1c0743faff2f..a174bea87b737f8ae17402cf8e0e725739ea0687 100644 (file)
@@ -96,10 +96,14 @@ void XGUI_ContextMenuMgr::createActions()
                                            aDesktop, this, SLOT(onRename()));
   addAction("RENAME_CMD", aAction);
 
-  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/move.png"),
+  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/move_to_end.png"),
                                            XGUI_Workshop::MOVE_TO_END_COMMAND, this);
   addAction("MOVE_CMD", aAction);
 
+  aAction = ModuleBase_Tools::createAction(QIcon(":pictures/move_to_end_split.png"),
+    XGUI_Workshop::MOVE_TO_END_SPLIT_COMMAND, this);
+  addAction("MOVE_SPLIT_CMD", aAction);
+
   aAction = ModuleBase_Tools::createAction(QIcon(":pictures/clean_history.png"),
                                            tr("Clean history"), aDesktop);
   addAction("CLEAN_HISTORY_CMD", aAction);
@@ -326,8 +330,10 @@ void XGUI_ContextMenuMgr::updateObjectBrowserMenu()
           if (!(hasParameter || hasFeature))
             action("SHOW_ONLY_CMD")->setEnabled(true);
         }
-        else if (hasFeature && myWorkshop->canMoveFeature())
+        else if (hasFeature && myWorkshop->canMoveFeature()) {
           action("MOVE_CMD")->setEnabled(true);
+          action("MOVE_SPLIT_CMD")->setEnabled(true);
+        }
 
         if( aMgr->activeDocument() == aObject->document() )
         {
@@ -347,8 +353,10 @@ void XGUI_ContextMenuMgr::updateObjectBrowserMenu()
         action("SHADING_CMD")->setEnabled(true);
         action("WIREFRAME_CMD")->setEnabled(true);
       }
-      if (hasFeature && myWorkshop->canMoveFeature())
+      if (hasFeature && myWorkshop->canMoveFeature()) {
         action("MOVE_CMD")->setEnabled(true);
+        action("MOVE_SPLIT_CMD")->setEnabled(true);
+      }
     } // end multi-selection
 
     // Check folder management commands state if only features are selected
@@ -662,6 +670,7 @@ void XGUI_ContextMenuMgr::buildObjBrowserMenu()
   aList.append(action("RENAME_CMD"));
   aList.append(action("SHOW_RESULTS_CMD"));
   aList.append(action("MOVE_CMD"));
+  aList.append(action("MOVE_SPLIT_CMD"));
   aList.append(mySeparator1);
   aList.append(action("INSERT_FOLDER_CMD"));
   aList.append(action("ADD_TO_FOLDER_BEFORE_CMD"));
@@ -770,6 +779,7 @@ void XGUI_ContextMenuMgr::addObjBrowserMenu(QMenu* theMenu) const
       aActions.append(action("ADD_OUT_FOLDER_AFTER_CMD"));
       aActions.append(mySeparator3);
       aActions.append(action("MOVE_CMD"));
+      aActions.append(action("MOVE_SPLIT_CMD"));
       aActions.append(action("COLOR_CMD"));
       aActions.append(action("DEFLECTION_CMD"));
       aActions.append(action("TRANSPARENCY_CMD"));
index aa66b48967f44f4061d8371635c19abb1bda06d6..fcee5e38ad8f2cfd77b6046f1885e560bc77bd74 100644 (file)
@@ -170,6 +170,7 @@ static Handle(VInspector_CallBack) MyVCallBack;
 //#define DEBUG_WITH_MESSAGE_REPORT
 
 QString XGUI_Workshop::MOVE_TO_END_COMMAND = QObject::tr("Move to the end");
+QString XGUI_Workshop::MOVE_TO_END_SPLIT_COMMAND = QObject::tr("Move to the end and split");
 
 //#define DEBUG_DELETE
 //#define DEBUG_FEATURE_NAME
@@ -1215,7 +1216,8 @@ void XGUI_Workshop::processUndoRedo(const ModuleBase_ActionType theActionType, i
     else
       aMgr->redo();
 
-    if (QString((*aIt).c_str()) == MOVE_TO_END_COMMAND)
+    if (QString((*aIt).c_str()) == MOVE_TO_END_COMMAND ||
+        QString((*aIt).c_str()) == MOVE_TO_END_SPLIT_COMMAND)
       myObjectBrowser->rebuildDataTree();
   }
   operationMgr()->updateApplyOfOperations();
@@ -1636,8 +1638,8 @@ void XGUI_Workshop::onContextMenuCommand(const QString& theId, bool isChecked)
     deleteObjects();
   else if (theId == "CLEAN_HISTORY_CMD")
     cleanHistory();
-  else if (theId == "MOVE_CMD")
-    moveObjects();
+  else if (theId == "MOVE_CMD" || theId == "MOVE_SPLIT_CMD")
+    moveObjects(theId == "MOVE_SPLIT_CMD");
   else if (theId == "COLOR_CMD")
     changeColor(aObjects);
   else if (theId == "DEFLECTION_CMD")
@@ -2095,7 +2097,7 @@ bool compareFeature(const FeaturePtr& theF1, const FeaturePtr& theF2) {
   DocumentPtr aDoc = theF1->document();
   return aDoc->index(theF1) < aDoc->index(theF2);
 }
-void XGUI_Workshop::moveObjects()
+void XGUI_Workshop::moveObjects(const bool theSplit)
 {
   if (!abortAllOperations())
     return;
@@ -2112,7 +2114,7 @@ void XGUI_Workshop::moveObjects()
   if (!XGUI_Tools::canRemoveOrRename(desktop(), aFeatures))
     return;
 
-  QString anActionId = "MOVE_CMD";
+  QString anActionId = theSplit ? "MOVE_CMD" : "MOVE_SPLIT_CMD";
   QString aDescription = contextMenuMgr()->action(anActionId)->text();
   aMgr->startOperation(aDescription.toStdString());
 
@@ -2128,7 +2130,7 @@ void XGUI_Workshop::moveObjects()
     if (!aFeature.get() || !myModule->canApplyAction(aFeature, anActionId))
       continue;
 
-    anActiveDocument->moveFeature(aFeature, aCurrentFeature);
+    anActiveDocument->moveFeature(aFeature, aCurrentFeature, theSplit);
     aCurrentFeature = anActiveDocument->currentFeature(true);
   }
   aMgr->finishOperation();
index 4b06dac3f54ce95e4804a063b59957d5e081708c..37ae6a8a2be07ea5265ba5463d38f4670c141f45 100644 (file)
@@ -186,7 +186,7 @@ Q_OBJECT
   bool canMoveFeature();
 
   /// Move selected features to be after the current feature
-  void moveObjects();
+  void moveObjects(const bool theSplit);
 
   /// Returns true if the object can be shaded. If the object is a compsolid result, the method
   /// checks subobjects of the result
@@ -316,6 +316,9 @@ Q_OBJECT
   /// A constant string used for "Move to end" command definition
   /// It is used for specific processing of Undo/Redo for this command.
   static QString MOVE_TO_END_COMMAND;
+  /// A constant string used for "Move to end and split" command definition
+  /// It is used for specific processing of Undo/Redo for this command.
+  static QString MOVE_TO_END_SPLIT_COMMAND;
 
   /// Closes all in the current session and load the directory
   /// \param theDirectory a path to directory
index 9b519926b28ecb646266fe2fc45f7b34f19b0a90..ebe3b8867122a019377dd0f3feda279c4cd28c1f 100644 (file)
         <source>Move to the end</source>
         <translation>Aller à la fin</translation>
     </message>
+    <message>
+        <source>Move to the end and split</source>
+        <translation>Aller à la fin et diviser</translation>
+    </message>
     <message>
         <source>SHAPER files (*.shaper *.cadbld)</source>
         <translation>Fichiers SHAPER (*.shaper *.cadbld)</translation>
index acb22325c73f3f8beae5b76342a4f898c265da88..9b958d9e758cde5347d49338cc273f11ccf7af60 100644 (file)
@@ -90,5 +90,7 @@
      <file>pictures/color.png</file>
      <file>pictures/normal-view-inversed.png</file>
      <file>pictures/normal-view.png</file>
+     <file>pictures/move_to_end.png</file>
+     <file>pictures/move_to_end_split.png</file>
  </qresource>
  </RCC>
diff --git a/src/XGUI/pictures/move_to_end.png b/src/XGUI/pictures/move_to_end.png
new file mode 100644 (file)
index 0000000..a80ce7f
Binary files /dev/null and b/src/XGUI/pictures/move_to_end.png differ
diff --git a/src/XGUI/pictures/move_to_end_split.png b/src/XGUI/pictures/move_to_end_split.png
new file mode 100644 (file)
index 0000000..bcc017d
Binary files /dev/null and b/src/XGUI/pictures/move_to_end_split.png differ