Salome HOME
#29481: Show parts in study tree (parts drag and drop)
authormpv <mpv@opencascade.com>
Wed, 25 May 2022 12:07:42 +0000 (15:07 +0300)
committervsr <vsr@opencascade.com>
Thu, 9 Jun 2022 07:39:51 +0000 (10:39 +0300)
15 files changed:
src/BuildPlugin/BuildPlugin_Interpolation.cpp
src/ModelAPI/ModelAPI_Tools.cpp
src/ModelAPI/ModelAPI_Tools.h
src/ModelAPI/Test/TestMovePart1.py [new file with mode: 0644]
src/ModelAPI/Test/TestMovePart2.py [new file with mode: 0644]
src/ModelAPI/tests.set
src/ModuleBase/ModuleBase_Tools.cpp
src/ModuleBase/ModuleBase_WidgetLabel.cpp
src/ModuleBase/ModuleBase_WidgetSwitch.cpp
src/PartSetPlugin/doc/PartSetPlugin.rst
src/XGUI/XGUI_DataModel.cpp
src/XGUI/XGUI_DataModel.h
src/XGUI/XGUI_MenuMgr.cpp
src/XGUI/XGUI_ObjectsBrowser.cpp
src/XGUI/XGUI_msg_fr.ts

index 13cdab609d97d0266b4440ce39f64b89bbffdcaf..57b10ea304e9adb885b5193f4198da92c1e34112 100644 (file)
@@ -121,7 +121,6 @@ void BuildPlugin_Interpolation::attributeChanged(const std::string& theID)
 //=================================================================================================
 void BuildPlugin_Interpolation::updateCoordinates()
 {
-    std::wstring exp;
     double aMint = real(MINT_ID())->value();
     double aMaxt = real(MAXT_ID())->value();
     int aNbrStep = integer(NUMSTEP_ID())->value();
index dda65e007eb6440f843479ccee8b2efdbb8d70d8..7034040690eb6cfe492ffd152aea248116ce1c68 100644 (file)
@@ -1441,4 +1441,127 @@ void findRandomColor(std::vector<int>& theValues, bool theReset)
 
 // LCOV_EXCL_STOP
 
+/// Returns name of the higher level feature (Part or feature of PartSet).
+static FeaturePtr topOwner(const FeaturePtr& theFeature)
+{
+  FeaturePtr anOwner = theFeature;
+  while (anOwner.get())
+  {
+    FeaturePtr aNextOwner = compositeOwner(anOwner);
+    if (aNextOwner.get())
+      anOwner = aNextOwner;
+    else
+      break;
+  }
+  if (anOwner->document() != ModelAPI_Session::get()->moduleDocument()) // the part-owner name
+    anOwner = findPartFeature(ModelAPI_Session::get()->moduleDocument(), anOwner->document());
+  return anOwner;
+}
+
+std::wstring validateMovement(const FeaturePtr& theAfter, const std::list<FeaturePtr> theMoved)
+{
+  std::wstring aResult;
+  if (theMoved.empty())
+    return aResult; // nothing to move, nothing to check, ok
+  DocumentPtr aDoc = theAfter.get() ? theAfter->document() : (*(theMoved.cbegin()))->document();
+  std::set<FeaturePtr> aMoved(theMoved.begin(), theMoved.end()); // fast access to moved
+  std::set<FeaturePtr> aPassed, aPassedMoved; // all features and all moved before the current one
+  std::set<FeaturePtr> aPassedAfter; // all passed features after theAfter
+  bool anAfterIsPassed = theAfter.get() == 0; // flag that iterator already passed theAfter
+  std::list<FeaturePtr> allFeat = aDoc->allFeatures();
+  for (std::list<FeaturePtr>::iterator aFeat = allFeat.begin(); aFeat != allFeat.end(); aFeat++)
+  {
+    if (!anAfterIsPassed)
+    {
+      if (aMoved.count(*aFeat))
+        aPassedMoved.insert(*aFeat);
+      else // check aPassedMoved are not referenced by the current feature
+        aPassed.insert(*aFeat);
+
+      anAfterIsPassed = *aFeat == theAfter;
+      if (anAfterIsPassed && !aPassedMoved.empty())
+      { // check dependencies of moved relatively to the passed
+        std::map<FeaturePtr, std::set<FeaturePtr> > aReferences;
+        findAllReferences(aPassedMoved, aReferences);
+        std::map<FeaturePtr, std::set<FeaturePtr> >::iterator aRefIter = aReferences.begin();
+        for (; aRefIter != aReferences.end(); aRefIter++)
+        {
+          if (aPassed.count(aRefIter->first))
+          {
+            aResult += topOwner(aRefIter->first)->name() + L" -> ";
+            // iterate all passed moved to check is it referenced by described feature or not
+            std::set<FeaturePtr>::iterator aMovedIter = aPassedMoved.begin();
+            for (; aMovedIter != aPassedMoved.end(); aMovedIter++)
+            {
+              std::map<FeaturePtr, std::set<FeaturePtr> > aPassedRefs;
+              std::set<FeaturePtr> aMovedOne;
+              aMovedOne.insert(*aMovedIter);
+              findAllReferences(aMovedOne, aPassedRefs);
+              if (aPassedRefs.count(aRefIter->first))
+                aResult += topOwner(*aMovedIter)->name() + L" ";
+            }
+            aResult += L"\n";
+          }
+        }
+      }
+    }
+    else // iteration after theAfter
+    {
+      if (aMoved.count(*aFeat)) { // check dependencies of moved relatively to ones after theAfter
+        std::map<FeaturePtr, std::set<FeaturePtr> > aReferences;
+        findAllReferences(aPassedAfter, aReferences);
+        bool aFoundRef = aReferences.find(*aFeat) != aReferences.end();
+        if (!aFoundRef && !(*aFeat)->results().empty()) // reference may be a feature in moved part
+        {
+          ResultPartPtr aFeatPart =
+            std::dynamic_pointer_cast<ModelAPI_ResultPart>((*aFeat)->firstResult());
+          if (aFeatPart.get() && aFeatPart->partDoc().get())
+          {
+            std::map<FeaturePtr, std::set<FeaturePtr> >::iterator aRef = aReferences.begin();
+            for (; aRef != aReferences.end() && !aFoundRef; aRef++)
+              aFoundRef = aRef->first->document() == aFeatPart->partDoc();
+          }
+        }
+
+        if (aFoundRef)
+        {
+          aResult += topOwner(*aFeat)->name() + L" -> ";
+          std::set<FeaturePtr> aReferencedCount; // to avoid duplicates in the displayed references
+          // iterate all passed after theAfter to check refers it described feature or not
+          FeaturePtr aFeatTop = topOwner(*aFeat);
+          std::set<FeaturePtr>::iterator aPassedIter = aPassedAfter.begin();
+          for (; aPassedIter != aPassedAfter.end(); aPassedIter++)
+          {
+            FeaturePtr aPassedTop = topOwner(*aPassedIter);
+            if (aReferencedCount.count(aPassedTop))
+              continue;
+            std::map<FeaturePtr, std::set<FeaturePtr> > aPassedRefs;
+            std::set<FeaturePtr> aPassedOne;
+            aPassedOne.insert(*aPassedIter);
+            findAllReferences(aPassedOne, aPassedRefs);
+            std::map<FeaturePtr, std::set<FeaturePtr> >::iterator aPRIter = aPassedRefs.begin();
+            for (; aPRIter != aPassedRefs.end(); aPRIter++)
+            {
+              FeaturePtr aPRTop = topOwner(aPRIter->first);
+              if (aPRIter->first == *aFeat || aPRIter->first == aFeatTop ||
+                  aPRTop == *aFeat || aPRTop == aFeatTop)
+              {
+                aResult += aPassedTop->name() + L" ";
+                aReferencedCount.insert(aPassedTop);
+                break;
+              }
+            }
+          }
+          aResult += L"\n";
+        }
+      }
+      else {
+        aPassedAfter.insert(*aFeat);
+      }
+    }
+
+  }
+  return aResult;
+}
+
 } // namespace ModelAPI_Tools
index 6f61423e8e666c5b512c97c26e8164ad8cd0fcc6..0ad6923412cf3eb5551368159c05110b17a95d84 100644 (file)
@@ -356,6 +356,16 @@ MODELAPI_EXPORT bool isInResults(AttributeSelectionListPtr theSelection,
 */
 MODELAPI_EXPORT void findRandomColor(std::vector<int>& theValues, bool theReset = false);
 
+/*!
+* Checks the movement of features possibility. The feature cannot appear before the feature
+* depended on it. Used in drag and drop part features.
+* \param theAfter feature after which the moved features are placed, or null for the first place
+* \param theMoved ordered list of the moved features
+* \returns string with error text, dependencies that do not allow make movement or empty string
+*/
+MODELAPI_EXPORT std::wstring validateMovement(
+  const FeaturePtr& theAfter, const std::list<FeaturePtr> theMoved);
+
 }
 
 #endif
diff --git a/src/ModelAPI/Test/TestMovePart1.py b/src/ModelAPI/Test/TestMovePart1.py
new file mode 100644 (file)
index 0000000..f05d0e9
--- /dev/null
@@ -0,0 +1,125 @@
+# Copyright (C) 2014-2021  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
+#
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+
+### Create Sketch_1
+Sketch_1 = model.addSketch(partSet, model.defaultPlane("XOY"))
+SketchProjection_1 = Sketch_1.addProjection(model.selection("VERTEX", "Origin"), False)
+SketchPoint_1 = SketchProjection_1.createdFeature()
+SketchCircle_1 = Sketch_1.addCircle(0, 0, 50)
+Sketch_1.setCoincident(SketchPoint_1.result(), SketchCircle_1.center())
+model.do()
+
+### Create Sketch_2 with reference to Sketch_1 circle
+Sketch_2 = model.addSketch(partSet, model.defaultPlane("XOY"))
+SketchProjection_2 = Sketch_2.addProjection(model.selection("EDGE", "Sketch_1/SketchCircle_1_2"), False)
+SketchCircle_2 = SketchProjection_2.createdFeature()
+SketchCircle_3 = Sketch_2.addCircle(34.92852513931564, 34.57180354201876, 20)
+Sketch_2.setCoincident(SketchCircle_2.results()[1], SketchCircle_3.center())
+model.do()
+
+### Create Part_1: Extrusion of Sketch_1
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "PartSet/Sketch_1/Face-SketchCircle_1_2f")], model.selection(), 10, 0, "Faces|Wires")
+model.do()
+
+### Create Part_2: Extrusion of Sketch_2
+Part_2 = model.addPart(partSet)
+Part_2_doc = Part_2.document()
+Extrusion_2 = model.addExtrusion(Part_2_doc, [model.selection("FACE", "PartSet/Sketch_2/Face-SketchCircle_3_2f")], model.selection(), 20, 0, "Faces|Wires")
+model.do()
+
+### Create Placement of Part_2 over a Part_1
+Placement_1 = model.addPlacement(partSet, [model.selection("COMPOUND", "Part_2/")], model.selection("FACE", "Part_2/Extrusion_1_1/From_Face"), model.selection("FACE", "Part_1/Extrusion_1_1/To_Face"), centering = True, keepSubResults = True)
+
+### Create independent Part_3
+Part_3 = model.addPart(partSet)
+Part_3_doc = Part_3.document()
+Sketch_3 = model.addSketch(Part_3_doc, model.defaultPlane("XOY"))
+SketchCircle_4 = Sketch_3.addCircle(-60, 65, 10)
+model.do()
+Extrusion_3 = model.addExtrusion(Part_3_doc, [model.selection("FACE", "Sketch_1/Face-SketchCircle_1_2r")], model.selection(), 10, 0, "Faces|Wires")
+model.do()
+
+model.end()
+
+# test the movement of parts ability and produced errors
+
+# checking ability to move theWhat to position after theTo and produced error theError
+def checkMovement(theWhat, theTo, theError):
+  aWhatList = model.FeatureList()
+  aWhatList.append(theWhat.feature())
+  if not theTo is None and not isinstance(theTo, model.ModelAPI_Object):
+    theTo = theTo.feature()
+  anError = model.validateMovement(theTo, aWhatList).strip()
+  aListCheck = theError.split('\n')
+  aListGet = anError.split('\n')
+  assert(len(aListCheck) == len(aListGet))
+  for anErr in aListCheck:
+    #print("Looking for " + anErr + " in " + str(aListGet))
+    assert(aListGet.count(anErr) == 1)
+
+# movement to very first location actually means movement after the first invisible objects, coordinate system hidden ones
+aLastHidden = None
+for aFeat in partSet.allObjects():
+  if aFeat.isInHistory():
+    break
+  aLastHidden = model.objectToFeature(aFeat)
+
+checkMovement(Part_1, None, 'Part_1 -> Origin Sketch_1')
+checkMovement(Part_1, aLastHidden, 'Part_1 -> Sketch_1')
+checkMovement(Part_1, Sketch_1, 'Part_1 -> Sketch_1')
+checkMovement(Part_1, Sketch_2, '')
+checkMovement(Part_1, Part_2, '')
+checkMovement(Part_1, Placement_1, 'Placement_1 -> Part_1')
+checkMovement(Part_1, Part_3, 'Placement_1 -> Part_1')
+
+checkMovement(Part_2, None, 'Part_2 -> Origin Sketch_1 Sketch_2')
+checkMovement(Part_2, aLastHidden, 'Part_2 -> Sketch_1 Sketch_2')
+checkMovement(Part_2, Sketch_1, 'Part_2 -> Sketch_2 Sketch_1') # in GUI this is not the case, because it drops after invisible sketch components
+checkMovement(Part_2, Sketch_2, 'Part_2 -> Sketch_2') # in GUI this is not the case, because it drops after invisible sketch components
+checkMovement(Part_2, Part_1, '')
+checkMovement(Part_2, Placement_1, 'Placement_1 -> Part_2')
+checkMovement(Part_2, Part_3, 'Placement_1 -> Part_2')
+
+checkMovement(Part_3, None, '')
+checkMovement(Part_3, aLastHidden, '')
+checkMovement(Part_3, Sketch_1, '')
+checkMovement(Part_3, Sketch_2, '')
+checkMovement(Part_3, Part_1, '')
+checkMovement(Part_3, Part_2, '')
+checkMovement(Part_3, Placement_1, '')
+
+# do movement Part_2 after Sketch_2
+anAfterSketch_2 = partSet.nextFeature(Part_1.feature(), True)
+model.begin()
+partSet.moveFeature(Part_2.feature(), anAfterSketch_2)
+model.end()
+# check the movement is performed
+anAfter = partSet.nextFeature(anAfterSketch_2)
+assert(anAfter.name() == 'Part_2')
+anAfter = partSet.nextFeature(anAfter)
+assert(anAfter.name() == 'Part_1')
+anAfter = partSet.nextFeature(anAfter)
+assert(anAfter.name() == 'Placement_1')
diff --git a/src/ModelAPI/Test/TestMovePart2.py b/src/ModelAPI/Test/TestMovePart2.py
new file mode 100644 (file)
index 0000000..cc4cb74
--- /dev/null
@@ -0,0 +1,113 @@
+# Copyright (C) 2014-2021  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
+#
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+
+### Create Part_1 : biggest cylinder
+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, 50)
+Sketch_1.setCoincident(SketchPoint_1.result(), SketchCircle_1.center())
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchCircle_1_2f")], model.selection(), 10, 0, "Faces|Wires")
+model.do()
+
+### Create Plane_4 and Sketch_2, independent
+Plane_4 = model.addPlane(partSet, model.selection("FACE", "XOY"), 10, False)
+Sketch_2 = model.addSketch(partSet, model.selection("FACE", "Plane_4"))
+SketchCircle_2 = Sketch_2.addCircle(0, 0, 20)
+model.do()
+
+### Create Part_2, an edge on Sketch_2
+Part_2 = model.addPart(partSet)
+Part_2_doc = Part_2.document()
+Edge_1 = model.addEdge(Part_2_doc, [model.selection("EDGE", "PartSet/Sketch_1/SketchCircle_1_2")], False)
+model.do()
+
+### Create Part_3, small cylinder on a Sketch_2
+Part_3 = model.addPart(partSet)
+Part_3_doc = Part_3.document()
+Extrusion_2 = model.addExtrusion(Part_3_doc, [model.selection("FACE", "PartSet/Sketch_1/Face-SketchCircle_1_2r")], model.selection(), 10, 0, "Faces|Wires")
+model.do()
+
+### Create Part_4, a box, independent
+Part_4 = model.addPart(partSet)
+Part_4_doc = Part_4.document()
+Sketch_3 = model.addSketch(Part_4_doc, model.defaultPlane("XOY"))
+SketchLine_1 = Sketch_3.addLine(70, 60, 20, 60)
+SketchLine_2 = Sketch_3.addLine(20, 60, 20, 30)
+SketchLine_3 = Sketch_3.addLine(20, 30, 70, 30)
+SketchLine_4 = Sketch_3.addLine(70, 30, 70, 60)
+Sketch_3.setCoincident(SketchLine_4.endPoint(), SketchLine_1.startPoint())
+Sketch_3.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint())
+Sketch_3.setCoincident(SketchLine_2.endPoint(), SketchLine_3.startPoint())
+Sketch_3.setCoincident(SketchLine_3.endPoint(), SketchLine_4.startPoint())
+Sketch_3.setHorizontal(SketchLine_1.result())
+Sketch_3.setVertical(SketchLine_2.result())
+Sketch_3.setHorizontal(SketchLine_3.result())
+Sketch_3.setVertical(SketchLine_4.result())
+model.do()
+Extrusion_3 = model.addExtrusion(Part_4_doc, [model.selection("FACE", "Sketch_1/Face-SketchLine_1r-SketchLine_2f-SketchLine_3f-SketchLine_4f")], model.selection(), 10, 0, "Faces|Wires")
+model.do()
+
+### Create Placement of Part_4 on the top face of Part_1
+Placement_1 = model.addPlacement(partSet, [model.selection("COMPOUND", "Part_4/")], model.selection("FACE", "Part_4/Extrusion_1_1/From_Face"), model.selection("FACE", "Part_1/Extrusion_1_1/To_Face"), keepSubResults = True)
+
+### Create Part_5, revolution
+Part_5 = model.addPart(partSet)
+Part_5_doc = Part_5.document()
+Sketch_4 = model.addSketch(Part_5_doc, model.defaultPlane("XOY"))
+SketchCircle_3 = Sketch_4.addCircle(71.54809194299482, -51.10206832217438, 4.892982649360998)
+model.do()
+Revolution_1 = model.addRevolution(Part_5_doc, [model.selection("FACE", "Sketch_1/Face-SketchCircle_1_2r")], model.selection("EDGE", "PartSet/OX"), -30, 0)
+model.do()
+
+### Create Placement_2 of Part_5 revolution to Part_3 to face
+Placement_2 = model.addPlacement(partSet, [model.selection("COMPOUND", "Part_5/")], model.selection("FACE", "Part_5/Revolution_1_1/From_Face"), model.selection("FACE", "Part_3/Extrusion_1_1/To_Face"), centering = True, keepSubResults = True)
+model.end()
+
+# Check multiple placement of Part_1, Part_3 and Part_5 after theSketch_2
+aWhatList = model.FeatureList()
+aWhatList.append(Part_1.feature())
+aWhatList.append(Part_3.feature())
+aWhatList.append(Part_5.feature())
+anError = model.validateMovement(SketchCircle_2.feature(), aWhatList).strip()
+assert(len(anError) == 0)
+# check also that they cannot be moved after Placement_1
+anError = model.validateMovement(Placement_1.feature(), aWhatList).strip()
+assert(anError == 'Placement_1 -> Part_1')
+# do movement
+model.do()
+partSet.moveFeature(Part_1.feature(), SketchCircle_2.feature())
+partSet.moveFeature(Part_3.feature(), Part_1.feature())
+partSet.moveFeature(Part_5.feature(), Part_3.feature())
+model.end()
+
+# check the resulting list of features
+aNames = ['Plane_4', 'Sketch_1', 'Part_1', 'Part_3', 'Part_5', 'Part_2', 'Part_4', 'Placement_1', 'Placement_2']
+
+for aNameIndex in range(9):
+  aFeatureName = model.objectToFeature(partSet.object("Features", aNameIndex)).name()
+  assert(aNames[aNameIndex] == aFeatureName)
index 5d666347fedc929fd8a8552e6ed56296fec8a986..13777dfa1f31fc703423efcc053ada67d964d88e 100644 (file)
@@ -121,4 +121,6 @@ SET(TEST_NAMES
                Test19932.py
                Test19989.py
                Test20170.py
+               TestMovePart1.py
+               TestMovePart2.py
 )
index 7c51ccc9aeb3bdbb29db403f80b12357e4fa42a0..2245d51e0a2045469d80a4a05aa44b3ede228342 100644 (file)
@@ -480,36 +480,6 @@ void checkObjects(const QObjectPtrList& theObjects, bool& hasResult, bool& hasFe
   }
 }
 
-/*bool setDefaultDeviationCoefficient(std::shared_ptr<GeomAPI_Shape> theGeomShape)
-{
-  if (!theGeomShape.get())
-    return false;
-  // if the shape could not be exploded on faces, it contains only wires, edges, and vertices
-  // correction of deviation for them should not influence to the application performance
-  GeomAPI_ShapeExplorer anExp(theGeomShape, GeomAPI_Shape::FACE);
-  bool anEmpty = anExp.empty();
-  return !anExp.more();
-}*/
-
-/*void setDefaultDeviationCoefficient(const std::shared_ptr<ModelAPI_Result>& theResult,
-                                    const Handle(Prs3d_Drawer)& theDrawer)
-{
-  if (!theResult.get())
-    return;
-  bool aUseDeviation = false;
-
-  std::string aResultGroup = theResult->groupName();
-  if (aResultGroup == ModelAPI_ResultConstruction::group())
-    aUseDeviation = true;
-  else if (aResultGroup == ModelAPI_ResultBody::group()) {
-    GeomShapePtr aGeomShape = theResult->shape();
-    if (aGeomShape.get())
-      aUseDeviation = setDefaultDeviationCoefficient(aGeomShape);
-  }
-  if (aUseDeviation)
-    theDrawer->SetDeviationCoefficient(DEFAULT_DEVIATION_COEFFICIENT);
-}
-*/
 void setDefaultDeviationCoefficient(const TopoDS_Shape& theShape,
                                     const Handle(Prs3d_Drawer)& theDrawer)
 {
index a3b445ca2e3d240e544587bd4b2152962aad6e06..be3486c2b0aaad26e72a529f28f58b551a680fee 100644 (file)
@@ -91,10 +91,7 @@ bool ModuleBase_WidgetLabel::restoreValueCustom()
         std::wstring aWStr((wchar_t*)aStr);
         static const int aBufSize = 1000;
         static char aMBStr[aBufSize];
-#ifdef _DEBUG
-        size_t aLen =
-#endif
-          wcstombs(aMBStr, aWStr.c_str(), aBufSize);
+        wcstombs(aMBStr, aWStr.c_str(), aBufSize);
         std::string aCodec = Config_Translator::codec("");
         aText = QTextCodec::codecForName(aCodec.c_str())->toUnicode(aMBStr);
       } else {
index 77ee2162e031b732711c47726c25362736d5c22c..7428db7d0045dfd7ede4c0c28178ab79800eeee3 100644 (file)
@@ -57,10 +57,7 @@ int ModuleBase_WidgetSwitch::addPage(ModuleBase_PageBase* thePage, const QString
                                                                    const QPixmap& theIcon,
                                                                    const QString& theTooltip)
 {
-#ifdef _DEBUG
-  int aSuperCount =
-#endif
-    ModuleBase_PagedContainer::addPage(thePage, theName, theCaseId, theIcon, theTooltip);
+  ModuleBase_PagedContainer::addPage(thePage, theName, theCaseId, theIcon, theTooltip);
   myCombo->addItem(translate(theName.toStdString()));
   int aResultCount = myCombo->count();
   if (aResultCount == 2)
index df635b9b2d43b429fc029989c788bd82d44fea2f..fc50230386dd841963d2ecf6b0f2029c0479245b 100644 (file)
@@ -59,3 +59,18 @@ Result
 
 Selected part is removed together with all its objects.
 
+Move Part
+-----------
+The part feature may be moved in the Object Browser by mouse Drag-and-Drop. For this user should
+press mouse left button on a Part feature and move it up or down along the PartSet features, then release the mouse button
+to put a moved part between other PartSet features.
+
+The following conditions must be met:
+
+- PartSet document must be active.
+- No operation is started.
+- The moved part(s) do not break order of depended objects.
+
+Only enabled features could participate in the movement. If the history line arrow is located not in the end of the history
+of features and the user moves the Part much lower than this arrow, the Part feature will be dropped just after the arrow
+(the last active feature).
index b4954c42f1a97a95a832655e7618d451af300653..e1a9ba2933eebb4c1faae09b7ad9b689a8e9426b 100644 (file)
 
 #include "XGUI_DataModel.h"
 #include "XGUI_ObjectsBrowser.h"
+#include "XGUI_Workshop.h"
 
 #include <ModuleBase_IconFactory.h>
 #include <ModuleBase_ITreeNode.h>
 
 #include <ModelAPI_Session.h>
 #include <ModelAPI_ResultField.h>
+#include <ModelAPI_Tools.h>
+#include <ModelAPI_CompositeFeature.h>
 
 #include <Config_FeatureMessage.h>
 
 #include <Events_Loop.h>
 
+#include <QMimeData>
+#include <QMessageBox>
+
 #include <cassert>
 
 #ifdef _MSC_VER
 #pragma warning(disable: 4100)
 #endif
 
-
-
 // Constructor *************************************************
 XGUI_DataModel::XGUI_DataModel(QObject* theParent) : QAbstractItemModel(theParent)//,
   //myIsEventsProcessingBlocked(false)
@@ -364,11 +368,161 @@ Qt::ItemFlags XGUI_DataModel::flags(const QModelIndex& theIndex) const
 {
   if (theIndex.isValid()) {
     ModuleBase_ITreeNode* aNode = (ModuleBase_ITreeNode*)theIndex.internalPointer();
-    return aNode->flags(theIndex.column());
+    Qt::ItemFlags aResultFlags = aNode->flags(theIndex.column());
+    // Drag and drop of Part features only if:
+    // - PartSet is active
+    // - active Part feature of PartSet is dragged
+    // - finally if it does not break dependencies between features (but here only drag possibility is checked)
+    SessionPtr aSession = ModelAPI_Session::get();
+    if (aSession->hasModuleDocument() && aSession->moduleDocument() == aSession->activeDocument()) {
+
+      ObjectPtr aNodeObj = aNode->object();
+      if (aNodeObj.get() && aNodeObj->groupName() == ModelAPI_Feature::group())
+      {
+        FeaturePtr aNodeFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aNodeObj);
+        if (aNodeFeature.get() && aNodeFeature->getKind() == "Part" && !aNodeFeature->isDisabled())
+          aResultFlags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
+      }
+    }
+    return aResultFlags;
   }
-  return Qt::ItemFlags();
+  return Qt::ItemIsDropEnabled | Qt::ItemFlags();
 }
 
+bool XGUI_DataModel::canDropMimeData(const QMimeData *theData, Qt::DropAction theAction,
+  int theRow, int theColumn, const QModelIndex &theParent) const
+{
+  if (theParent.isValid())
+    return false;
+  ModuleBase_ITreeNode* aSubNode = myRoot->subNode(theRow);
+  if ((aSubNode && aSubNode->object() && aSubNode->object()->groupName() == ModelAPI_Feature::group())
+      || theRow == myRoot->childrenCount()) // into the end of a list of features
+  {
+    return true;
+  }
+
+  return false; // in other cases drop is forbidden
+}
+
+QMimeData* XGUI_DataModel::mimeData(const QModelIndexList& theIndexes) const
+{
+  std::set<int> aRows; // to avoid duplication of rows and for sorting the indices
+  foreach (QModelIndex anIndex, theIndexes) {
+    if (anIndex.isValid() && anIndex.internalPointer())
+      aRows.insert(anIndex.row());
+  }
+  QByteArray anEncodedData;
+  QDataStream aStream(&anEncodedData, QIODevice::WriteOnly);
+  for(std::set<int>::iterator aRIter = aRows.begin(); aRIter != aRows.end(); aRIter++)
+    aStream << *aRIter;
+
+  QMimeData* aMimeData = new QMimeData();
+  aMimeData->setData("xgui/moved.rows", anEncodedData);
+  return aMimeData;
+}
+
+bool XGUI_DataModel::dropMimeData(const QMimeData *theData, Qt::DropAction theAction,
+  int theRow, int theColumn, const QModelIndex &theParent)
+{
+  FeaturePtr aDropAfter; // after this feature it is dropped, NULL if drop the the first place
+  if (theRow > 0)
+  {
+    ModuleBase_ITreeNode* aNode = myRoot->subNode(theRow - 1);
+    if (aNode && aNode->object() && aNode->object()->groupName() == ModelAPI_Feature::group())
+      aDropAfter = std::dynamic_pointer_cast<ModelAPI_Feature>(aNode->object());
+  }
+  SessionPtr aSession = ModelAPI_Session::get();
+  if (aDropAfter.get()) // move to the upper enabled feature
+  {
+    while (aDropAfter.get() && (aDropAfter->isDisabled() || !aDropAfter->isInHistory()))
+      aDropAfter = aDropAfter->document()->nextFeature(aDropAfter, true);
+  }
+  else { // move after invisible items, not the first (which is coordinate system by default)
+    std::list<FeaturePtr> allFeatures = aSession->get()->moduleDocument()->allFeatures();
+    std::list<FeaturePtr>::iterator aFeature = allFeatures.begin();
+    for(; aFeature != allFeatures.end(); aFeature++)
+    {
+      if ((*aFeature)->isInHistory())
+        break;
+      aDropAfter = *aFeature;
+    }
+  }
+  // move after the composite feature memebers, if they are invisible (sub elements of sketch)
+  CompositeFeaturePtr aComposite = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(aDropAfter);
+  if (aComposite.get())
+  {
+    FeaturePtr aNext = aDropAfter->document()->nextFeature(aDropAfter);
+    while (aNext.get() && !aNext->isInHistory() && aComposite->isSub(aNext)) {
+      aDropAfter = aNext;
+      aNext = aDropAfter->document()->nextFeature(aNext);
+    }
+  }
+
+  QByteArray anEncodedData = theData->data("xgui/moved.rows");
+  if (anEncodedData.isEmpty())
+    return false; // dropped something alien, decline
+
+  QDataStream stream(&anEncodedData, QIODevice::ReadOnly);
+  std::list<FeaturePtr> aDropped;
+  while (!stream.atEnd()) {
+    int aRow;
+    stream >> aRow;
+    ModuleBase_ITreeNode* aNode = myRoot->subNode(aRow);
+    if (aNode)
+    {
+      FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(aNode->object());
+      // feature moved after itself is not moved, add only Part feature, other skip
+      if (aFeature.get() && aFeature != aDropAfter && aFeature->getKind() == "Part")
+        aDropped.push_back(aFeature);
+    }
+  }
+  if (aDropped.empty()) // nothing to move
+    return false;
+
+  // check for the movement is valid due to existing dependencies
+  std::wstring anErrorStr = ModelAPI_Tools::validateMovement(aDropAfter, aDropped);
+  if (!anErrorStr.empty())
+  {
+    QMessageBox aMessageBox;
+    aMessageBox.setWindowTitle(QObject::tr("Move part"));
+    aMessageBox.setIcon(QMessageBox::Warning);
+    aMessageBox.setStandardButtons(QMessageBox::Ok);
+    aMessageBox.setDefaultButton(QMessageBox::Ok);
+    QString aMessageText(QObject::tr("Part(s) cannot be moved because of breaking dependencies."));
+    aMessageBox.setText(aMessageText);
+    aMessageBox.setDetailedText(QString::fromStdWString(anErrorStr));
+    aMessageBox.exec();
+    return false;
+  }
+
+  if (aSession->isOperation())
+  {
+    QMessageBox aMessageBox;
+    aMessageBox.setWindowTitle(QObject::tr("Move part"));
+    aMessageBox.setIcon(QMessageBox::Warning);
+    aMessageBox.setStandardButtons(QMessageBox::Ok);
+    aMessageBox.setDefaultButton(QMessageBox::Ok);
+    QString aMessageText(QObject::tr("Cannot move part(s) during another operation."));
+    aMessageBox.setText(aMessageText);
+    aMessageBox.exec();
+    return false;
+  }
+
+  aSession->startOperation("Move Part");
+  DocumentPtr aPartSet = aSession->moduleDocument();
+  for (std::list<FeaturePtr>::iterator aDrop = aDropped.begin(); aDrop != aDropped.end(); aDrop++)
+  {
+    aPartSet->moveFeature(*aDrop, aDropAfter);
+    aDropAfter = *aDrop;
+  }
+  aSession->finishOperation();
+
+  updateSubTree(myRoot);
+  myWorkshop->updateHistory();
+
+  // returns false in any case to avoid calling removeRows after it,
+  return false; // because number of rows stays the same
+}
 
 //******************************************************
 QModelIndex XGUI_DataModel::documentRootIndex(DocumentPtr theDoc, int theColumn) const
index 6a9a9b6e35e1d3adf521e8890caa62d321fa8e5e..d8dca75b5c70bf53c3a1f2e66abc42279cc1bea0 100644 (file)
@@ -132,18 +132,20 @@ public:
   /// \param theIndex a model index
   virtual Qt::ItemFlags flags(const QModelIndex& theIndex) const;
 
-  /// Returns an index which is root of the given document
-  /// \param theDoc a document
-  QModelIndex documentRootIndex(DocumentPtr theDoc, int theColumn = 1) const;
+  /// Returns true if a model can accept a drop of the data (used for drag and drop functionality).
+  virtual bool canDropMimeData(const QMimeData *theData, Qt::DropAction theAction,
+                               int theRow, int theColumn, const QModelIndex &theParent) const override;
 
-  /// Returns last history object index
-  //virtual QModelIndex lastHistoryIndex() const;
+  /// Converts the dragged items information into mime data format (to be encoded in dropMimeData)
+  virtual QMimeData* mimeData(const QModelIndexList &indexes) const override;
 
-  /// Initialises XML data model reader. It must be initialised before DataModel using.
-  //void setXMLReader(Config_DataModelReader* theReader) { myXMLReader = theReader; }
+  /// Performs a drag and drop of Part feature operation when it is droped.
+  virtual bool dropMimeData(const QMimeData *theData, Qt::DropAction theAction,
+                            int theRow, int theColumn, const QModelIndex &theParent) override;
 
-  /// Do not processing anymore events of model loop
-  //bool blockEventsProcessing(const bool theState);
+  /// Returns an index which is root of the given document
+  /// \param theDoc a document
+  QModelIndex documentRootIndex(DocumentPtr theDoc, int theColumn = 1) const;
 
   /// Returns true if the data model item has Hidden visual state
   /// \param theIndex a tree model item
@@ -167,6 +169,12 @@ public:
   /// \param thCol a column
   QModelIndex getIndex(ModuleBase_ITreeNode* theNode, int thCol) const;
 
+  /// Allows to support drag and drop of some model items
+  virtual Qt::DropActions supportedDropActions() const override
+  {
+    return Qt::MoveAction;
+  }
+
 signals:
   /// Signal send before tree rebuild
   void beforeTreeRebuild();
@@ -186,69 +194,8 @@ private:
 
   void updateSubTree(ModuleBase_ITreeNode* theParent);
 
-  /// Find a root index which contains objects of the given document
-  /// \param theDoc the document object
-  //QModelIndex findDocumentRootIndex(const ModelAPI_Document* theDoc, int aColumn = 1) const;
-
-  /// Returns number of folders in document.
-  /// Considered folders which has to be shown only if they are not empty.
-  /// \param theDoc document which has to be checked. If 0 then Root document will be considered
-  //int foldersCount(ModelAPI_Document* theDoc = 0) const;
-
-  /// Retrurns indexes of folders which can not be shown because they are empty
-  /// \param theDoc document which has to be checked. If 0 then Root document will be considered
-  //QIntList missedFolderIndexes(ModelAPI_Document* theDoc = 0) const;
-
-  /// Returns Id (row) of a folder taking into consideration
-  /// folders which can not be shown non empty
-  /// \param theType Type of the folder
-  /// \param theDoc a document which contains this folder
-  //int folderId(std::string theType, ModelAPI_Document* theDoc = 0) const;
-
-  /// Removes a row from branch of tree
-  /// \param theStart - start row to update indexes
-  /// \param theSize - number of indexes in the folder
-  /// \param theParent - index of parent folder
-  //void rebuildBranch(int theRow, int theCount, const QModelIndex& theParent = QModelIndex());
-
-  /// Returns list of folders types which can not be shown empty
-  /// \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)
-  //{
-  //  if (!myShownFolders.contains(theDoc)) {
-  //    myShownFolders[theDoc] = QStringList();
-  //  }
-  //  myShownFolders[theDoc].append(theFolder);
-  //}
-
-  //void removeShownFolder(DocumentPtr theDoc, QString theFolder)
-  //{
-  //  if (myShownFolders.contains(theDoc)) {
-  //    myShownFolders[theDoc].removeAll(theFolder);
-  //    if (myShownFolders[theDoc].isEmpty())
-  //      myShownFolders.remove(theDoc);
-  //  }
-  //}
-
-  //bool hasShownFolder(DocumentPtr theDoc, QString theFolder) const
-  //{
-  //  if (myShownFolders.contains(theDoc))
-  //    return myShownFolders[theDoc].contains(theFolder);
-  //  return false;
-  //}
-
-  //Config_DataModelReader* myXMLReader;
-
   XGUI_Workshop* myWorkshop;
   QMap<DocumentPtr, QStringList> myShownFolders;
-  //bool myIsEventsProcessingBlocked;
 
   ModuleBase_ITreeNode* myRoot;
 };
index 4339bf6bc8721c7b2865f47da1e1de1f82bc6af7..6aa634fe34279ac92de74f2f4a673e8039032dec 100644 (file)
@@ -206,12 +206,12 @@ QAction* XGUI_MenuMgr::buildAction(const std::shared_ptr<Config_FeatureMessage>&
     XGUI_OperationMgr* anOperationMgr = myWorkshop->operationMgr();
     XGUI_ActionsMgr* anActionsMgr = myWorkshop->actionsMgr();
     if (aNestedActions.contains(FEATURE_WHEN_NESTED_ACCEPT)) {
-      QAction* anAction = anActionsMgr->operationStateAction(XGUI_ActionsMgr::AcceptAll);
+      anAction = anActionsMgr->operationStateAction(XGUI_ActionsMgr::AcceptAll);
       QObject::connect(anAction, SIGNAL(triggered()), anOperationMgr, SLOT(commitAllOperations()));
       aNestedActList << anAction;
     }
     if (aNestedActions.contains(FEATURE_WHEN_NESTED_ABORT)) {
-      QAction* anAction = anActionsMgr->operationStateAction(XGUI_ActionsMgr::AbortAll);
+      anAction = anActionsMgr->operationStateAction(XGUI_ActionsMgr::AbortAll);
       QObject::connect(anAction, SIGNAL(triggered()), anOperationMgr, SLOT(onAbortAllOperation()));
       aNestedActList << anAction;
     }
index 34c113f2cdc01ef0a3b68fe714316684678d9a95..49ce56a3db092c88ad7db42f6c7c502dde9a3e1c 100644 (file)
@@ -94,6 +94,13 @@ XGUI_DataTree::XGUI_DataTree(QWidget* theParent)
   setSelectionBehavior(QAbstractItemView::SelectRows);
   setSelectionMode(QAbstractItemView::ExtendedSelection);
 
+  // drag and drop
+  setDragEnabled(true);
+  setAcceptDrops(true);
+  viewport()->setAcceptDrops(true);
+  setDropIndicatorShown(true);
+  setDragDropMode(QAbstractItemView::InternalMove);
+
   setItemDelegateForColumn(1, new XGUI_TreeViewItemDelegate(this));
 
   connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
index 2e320d15e2cee60c25fb8f756b8b528691075744..4f5f1ee3b190ed043ba5217932ce25549b631077 100644 (file)
         <source>Part files (*.shaperpart);;All files (*.*)</source>
         <translation>Fichiers pièces (*.shaperpart);;Tous les fichiers (*.*)</translation>
     </message>
+    <message>
+        <source>Part(s) cannot be moved because of breaking dependencies.</source>
+        <translation>La ou les pièces ne peuvent pas être déplacées en raison de la rupture des dépendances.</translation>
+    </message>
+    <message>
+        <source>Move part</source>
+        <translation>Déplacer une partie</translation>
+    </message>
+    <message>
+        <source>Cannot move part(s) during another operation.</source>
+        <translation>Impossible de déplacer la pièce lors d'une autre opération.</translation>
+    </message>
 </context>
 <context>
     <name>XGUI_ActionsMgr</name>