]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
Implementation of the task #3109 : Feature Copy
authormpv <mpv@opencascade.com>
Mon, 9 Dec 2019 11:35:06 +0000 (14:35 +0300)
committermpv <mpv@opencascade.com>
Mon, 9 Dec 2019 11:35:06 +0000 (14:35 +0300)
29 files changed:
src/FeaturesAPI/CMakeLists.txt
src/FeaturesAPI/FeaturesAPI.i
src/FeaturesAPI/FeaturesAPI_Copy.cpp [new file with mode: 0644]
src/FeaturesAPI/FeaturesAPI_Copy.h [new file with mode: 0644]
src/FeaturesAPI/FeaturesAPI_swig.h
src/FeaturesPlugin/CMakeLists.txt
src/FeaturesPlugin/FeaturesPlugin_Copy.cpp [new file with mode: 0644]
src/FeaturesPlugin/FeaturesPlugin_Copy.h [new file with mode: 0644]
src/FeaturesPlugin/FeaturesPlugin_Plugin.cpp
src/FeaturesPlugin/Test/TestCopyFeature.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestCopyFeatureMoveGroupOfFeature.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestCopyMoveResult.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestCopyMoveSubShapes.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestCopyNames.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestCopySubShapes.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestCopyWholeFeature.py [new file with mode: 0644]
src/FeaturesPlugin/copy_widget.xml [new file with mode: 0644]
src/FeaturesPlugin/icons/copy.png [new file with mode: 0644]
src/FeaturesPlugin/plugin-Features.xml
src/Model/Model_AttributeSelection.cpp
src/Model/Model_AttributeSelection.h
src/Model/Model_Document.cpp
src/Model/Model_ResultBody.cpp
src/Model/Model_ResultBody.h
src/ModelAPI/ModelAPI_AttributeSelectionList.h
src/ModelAPI/ModelAPI_Feature.h
src/ModelAPI/ModelAPI_ResultBody.cpp
src/ModelAPI/ModelAPI_ResultBody.h
src/PythonAPI/model/features/__init__.py

index acd6222f5e1cc4c674c701072acaf6b93b2f4f21..dbdb6bc5b388d7926537c898e7f2ac04651da234 100644 (file)
@@ -48,6 +48,7 @@ SET(PROJECT_HEADERS
   FeaturesAPI_Translation.h
   FeaturesAPI_Union.h
   FeaturesAPI_FusionFaces.h
+  FeaturesAPI_Copy.h
 )
 
 SET(PROJECT_SOURCES
@@ -78,6 +79,7 @@ SET(PROJECT_SOURCES
   FeaturesAPI_Translation.cpp
   FeaturesAPI_Union.cpp
   FeaturesAPI_FusionFaces.cpp
+  FeaturesAPI_Copy.cpp
 )
 
 SET(PROJECT_LIBRARIES
index c0a747b2f65bf52d060c472d250209cf053f792a..9ed6db0d36611bac85dad93aa0d4e611d6535ef5 100644 (file)
@@ -77,6 +77,7 @@
 %shared_ptr(FeaturesAPI_Union)
 %shared_ptr(FeaturesAPI_FusionFaces)
 %shared_ptr(FeaturesAPI_RemoveResults)
+%shared_ptr(FeaturesAPI_Copy)
 
 
 %typecheck(SWIG_TYPECHECK_POINTER) std::pair<std::list<ModelHighAPI_Selection>, bool>, const std::pair<std::list<ModelHighAPI_Selection>, bool> & {
 %include "FeaturesAPI_Union.h"
 %include "FeaturesAPI_FusionFaces.h"
 %include "FeaturesAPI_RemoveResults.h"
+%include "FeaturesAPI_Copy.h"
diff --git a/src/FeaturesAPI/FeaturesAPI_Copy.cpp b/src/FeaturesAPI/FeaturesAPI_Copy.cpp
new file mode 100644 (file)
index 0000000..5ec81ab
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (C) 2014-2019  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "FeaturesAPI_Copy.h"
+
+#include <ModelHighAPI_Dumper.h>
+#include <ModelHighAPI_Tools.h>
+
+//================================================================================================
+FeaturesAPI_Copy::FeaturesAPI_Copy(const std::shared_ptr<ModelAPI_Feature>& theFeature)
+: ModelHighAPI_Interface(theFeature)
+{
+  initialize();
+}
+
+//================================================================================================
+FeaturesAPI_Copy::FeaturesAPI_Copy(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                     const std::list<ModelHighAPI_Selection>& theObjects,
+                                     const int theNumber)
+: ModelHighAPI_Interface(theFeature)
+{
+  if(initialize()) {
+    setNumber(theNumber);
+    setObjects(theObjects);
+  }
+}
+
+//================================================================================================
+FeaturesAPI_Copy::~FeaturesAPI_Copy() {}
+
+//=================================================================================================
+void FeaturesAPI_Copy::setObjects(const std::list<ModelHighAPI_Selection>& theObjects)
+{
+  fillAttribute(theObjects, myobjects);
+  execute();
+}
+//=================================================================================================
+void FeaturesAPI_Copy::setNumber(const int theNumber)
+{
+  fillAttribute(theNumber, mynumber);
+  execute();
+}
+
+//=================================================================================================
+void FeaturesAPI_Copy::dump(ModelHighAPI_Dumper& theDumper) const
+{
+  FeaturePtr aBase = feature();
+  const std::string& aDocName = theDumper.name(aBase->document());
+
+  AttributeSelectionListPtr anObjects = aBase->selectionList(FeaturesPlugin_Copy::OBJECTS());
+  AttributeIntegerPtr aNumber = aBase->integer(FeaturesPlugin_Copy::NUMBER());
+
+  theDumper << aBase << " = model.addCopy("
+            << aDocName << ", " << anObjects << ", " << aNumber << ")" << std::endl;
+}
+
+//=================================================================================================
+CopyPtr addCopy(const std::shared_ptr<ModelAPI_Document>& thePart,
+                const std::list<ModelHighAPI_Selection>& theObjects,
+                const int theNumber)
+{
+  std::shared_ptr<ModelAPI_Feature> aFeature = thePart->addFeature(FeaturesAPI_Copy::ID());
+  return CopyPtr(new FeaturesAPI_Copy(aFeature, theObjects, theNumber));
+}
diff --git a/src/FeaturesAPI/FeaturesAPI_Copy.h b/src/FeaturesAPI/FeaturesAPI_Copy.h
new file mode 100644 (file)
index 0000000..92174f9
--- /dev/null
@@ -0,0 +1,78 @@
+// 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
+//
+
+#ifndef FeaturesAPI_Copy_H_
+#define FeaturesAPI_Copy_H_
+
+#include "FeaturesAPI.h"
+
+#include <FeaturesPlugin_Copy.h>
+
+#include <ModelHighAPI_Interface.h>
+#include <ModelHighAPI_Macro.h>
+
+class ModelHighAPI_Dumper;
+class ModelHighAPI_Selection;
+
+/// \class FeaturesAPI_Copy
+/// \ingroup CPPHighAPI
+/// \brief Interface for Copy feature.
+class FeaturesAPI_Copy: public ModelHighAPI_Interface
+{
+public:
+  /// Constructor without values.
+  FEATURESAPI_EXPORT
+  explicit FeaturesAPI_Copy(const std::shared_ptr<ModelAPI_Feature>& theFeature);
+
+  /// Constructor with values.
+  FEATURESAPI_EXPORT
+  explicit FeaturesAPI_Copy(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                            const std::list<ModelHighAPI_Selection>& theBaseObjects,
+                            const int theVersion = 0);
+
+  /// Destructor.
+  FEATURESAPI_EXPORT virtual ~FeaturesAPI_Copy();
+
+  INTERFACE_2(FeaturesPlugin_Copy::ID(),
+              objects, FeaturesPlugin_Copy::OBJECTS(),
+              ModelAPI_AttributeSelectionList, /** Source objects */,
+              number, FeaturesPlugin_Copy::NUMBER(),
+              ModelAPI_AttributeInteger, /** Number of copies */)
+
+  /// Modify objects attribute of the feature.
+  FEATURESAPI_EXPORT void setObjects(const std::list<ModelHighAPI_Selection>& theBaseObjects);
+
+  /// Modify number of copies attribute of the feature.
+  FEATURESAPI_EXPORT void setNumber(const int theNumber);
+
+  /// Dump wrapped feature
+  FEATURESAPI_EXPORT virtual void dump(ModelHighAPI_Dumper& theDumper) const;
+};
+
+/// Pointer on Copy object.
+typedef std::shared_ptr<FeaturesAPI_Copy> CopyPtr;
+
+/// \ingroup CPPHighAPI
+/// \brief Create Copy feature.
+FEATURESAPI_EXPORT
+CopyPtr addCopy(const std::shared_ptr<ModelAPI_Document>& thePart,
+                const std::list<ModelHighAPI_Selection>& theObjects,
+                const int theNumber);
+
+#endif // FeaturesAPI_Copy_H_
index cd71216fae72dacf4ad6f159760cf424ba686105..767e0966fda6e81afa2a17b84e0e484c8a62d266 100644 (file)
@@ -50,5 +50,6 @@
   #include "FeaturesAPI_Union.h"
   #include "FeaturesAPI_FusionFaces.h"
   #include "FeaturesAPI_RemoveResults.h"
+  #include "FeaturesAPI_Copy.h"
 
 #endif // FeaturesAPI_swig_H_
index ea14d9ea6191b21c6dcb092626702e2aecae0a7a..913a83aff1bd71a27cb4b307822a6d3f49f7fe82 100644 (file)
@@ -61,6 +61,7 @@ SET(PROJECT_HEADERS
     FeaturesPlugin_FusionFaces.h
     FeaturesPlugin_RemoveResults.h
     FeaturesPlugin_Chamfer.h
+    FeaturesPlugin_Copy.h
 )
 
 SET(PROJECT_SOURCES
@@ -103,6 +104,7 @@ SET(PROJECT_SOURCES
     FeaturesPlugin_FusionFaces.cpp
     FeaturesPlugin_RemoveResults.cpp
     FeaturesPlugin_Chamfer.cpp
+    FeaturesPlugin_Copy.cpp
 )
 
 SET(XML_RESOURCES
@@ -135,6 +137,7 @@ SET(XML_RESOURCES
   measurement_widget.xml
   fusion_faces_widget.xml
   chamfer_widget.xml
+  copy_widget.xml
 )
 
 SET(TEXT_RESOURCES
@@ -541,4 +544,11 @@ ADD_UNIT_TESTS(TestExtrusion.py
                Test3033.py
                Test3076.py
                Test17909.py
+               TestCopyFeature.py
+               TestCopyFeatureMoveGroupOfFeature.py
+               TestCopyMoveResult.py
+               TestCopyMoveSubShapes.py
+               TestCopyNames.py
+               TestCopySubShapes.py
+               TestCopyWholeFeature.py
 )
diff --git a/src/FeaturesPlugin/FeaturesPlugin_Copy.cpp b/src/FeaturesPlugin/FeaturesPlugin_Copy.cpp
new file mode 100644 (file)
index 0000000..76db7fa
--- /dev/null
@@ -0,0 +1,155 @@
+// Copyright (C) 2017-2019  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "FeaturesPlugin_Copy.h"
+
+#include <ModelAPI_AttributeSelectionList.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_Tools.h>
+
+#include <GeomAlgoAPI_Copy.h>
+#include <GeomAlgoAPI_Tools.h>
+#include <GeomAPI_ShapeExplorer.h>
+
+#include <sstream>
+
+void FeaturesPlugin_Copy::initAttributes()
+{
+  data()->addAttribute(OBJECTS(), ModelAPI_AttributeSelectionList::typeId());
+  data()->addAttribute(NUMBER(), ModelAPI_AttributeInteger::typeId());
+}
+
+static GeomShapePtr shapeOfSelection(AttributeSelectionPtr theSel) {
+  GeomShapePtr aResult;
+  FeaturePtr aSelFeature = theSel->contextFeature();
+  if (aSelFeature.get()) {
+    if (aSelFeature->results().empty()) // if selected feature has no results, make nothing
+      return aResult;
+    if (aSelFeature->results().size() == 1) { // for one sub-result don't make compound
+      aResult = aSelFeature->firstResult()->shape();
+    }
+  }
+  if (!aResult.get())
+    aResult = theSel->value();
+  if (!aResult.get()) {
+    if (theSel->context().get())
+      aResult = theSel->context()->shape();
+  }
+  return aResult;
+}
+
+void FeaturesPlugin_Copy::execute()
+{
+  int aCopiesNum = integer(NUMBER())->value();
+  AttributeSelectionListPtr aList = selectionList(OBJECTS());
+  int aResultIndex = 0;
+  std::set<std::string> anExistingNames; // to avoid names duplication
+  for(int aCopy = 0; aCopy < aCopiesNum; aCopy++) {
+    for (int aSelIndex = 0; aSelIndex < aList->size(); aSelIndex++) {
+      AttributeSelectionPtr aSel = aList->value(aSelIndex);
+      GeomShapePtr aShape = shapeOfSelection(aSel);
+      if (!aShape.get())
+        continue;
+      std::shared_ptr<GeomAlgoAPI_Copy> aCopyBuilder(new GeomAlgoAPI_Copy(aShape, false, false));
+      std::string anError;
+      if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aCopyBuilder, getKind(), anError)) {
+        setError(anError);
+        return;
+      }
+      GeomShapePtr aResult = aCopyBuilder->shape();
+
+      std::string aBaseName = aSel->context() ? aSel->context()->data()->name() :
+        aSel->contextFeature()->firstResult()->data()->name();
+      std::string aName;
+      int anInd = 0;
+      do {
+        anInd++;
+        std::ostringstream aNameStr;
+        aNameStr << aBaseName << "_" << (aCopy + anInd);
+        aName = aNameStr.str();
+      } while (anExistingNames.count(aName));
+      anExistingNames.insert(aName);
+
+      std::shared_ptr<ModelAPI_ResultBody> aResultBody =
+        document()->createBody(data(), aResultIndex);
+      aResultBody->data()->setName(aName);
+      // to make sub-results also names with a similar name temporarily rename the feature
+      std::string anOrigName = name();
+      data()->setName(aBaseName);
+      aResultBody->store(aResult);
+      data()->setName(anOrigName);
+      aResultBody->loadFirstLevel(aResult, "Copy");
+      setResult(aResultBody, aResultIndex++);
+    }
+  }
+  removeResults(aResultIndex);
+}
+
+void FeaturesPlugin_Copy::getCopies(
+  ObjectPtr theContext, std::shared_ptr<GeomAPI_Shape> theValue,
+  std::list<ObjectPtr>& theCopyContext, std::list<std::shared_ptr<GeomAPI_Shape> >& theCopyVals)
+{
+  ResultPtr aContextRes = std::dynamic_pointer_cast<ModelAPI_Result>(theContext);
+  GeomShapePtr aGroupValue = theValue.get() ? theValue : aContextRes->shape();
+
+  AttributeSelectionListPtr aList = selectionList(OBJECTS());
+  std::list<ResultPtr>::const_iterator aResIter = results().cbegin();
+  while(aResIter != results().cend()) { // do as long as many iterations
+    for (int aSelIndex = 0; aSelIndex < aList->size(); aSelIndex++) {
+      if (aResIter == results().cend()) // no more results corresponding to the selection
+        return;
+      AttributeSelectionPtr aSel = aList->value(aSelIndex);
+      GeomShapePtr aShape = shapeOfSelection(aSel);
+      if (!aShape.get())
+        continue;
+
+      if (aShape->isSubShape(aGroupValue, false)) { // group value is subshape of copied => copy
+        // search the same result in the copy by the same index of sub-shape in the shape
+        GeomAPI_ShapeExplorer anOrigExp(aShape, aGroupValue->shapeType());
+        GeomAPI_ShapeExplorer aCopyShape((*aResIter)->shape(), aGroupValue->shapeType());
+        for(; anOrigExp.more(); anOrigExp.next(), aCopyShape.next()) {
+          if (anOrigExp.current()->isSame(aGroupValue)) {
+            // searching for sub-result if it is composite result, but context-not
+            ResultPtr aResContext = *aResIter;
+            if (aContextRes->shape()->shapeType() > (*aResIter)->shape()->shapeType()) {
+              ResultBodyPtr aResBody = std::dynamic_pointer_cast<ModelAPI_ResultBody>(aResContext);
+              if (aResBody.get()) {
+                std::list<ResultPtr> aSubs;
+                ModelAPI_Tools::allSubs(aResBody, aSubs, true);
+                std::list<ResultPtr>::iterator aSubIter = aSubs.begin();
+                for(; aSubIter != aSubs.end(); aSubIter++) {
+                  GeomShapePtr aSubShape = (*aSubIter)->shape();
+                  if (aSubShape.get() && aSubShape->isSubShape(aCopyShape.current(), false)) {
+                    aResContext = *aSubIter;
+                    break;
+                  }
+                }
+              }
+            }
+            theCopyContext.push_back(aResContext);
+            theCopyVals.push_back(aResContext->shape()->isSame(
+              aCopyShape.current()) ? GeomShapePtr() : aCopyShape.current());
+            break;
+          }
+        }
+      }
+      aResIter++;
+    }
+  }
+}
diff --git a/src/FeaturesPlugin/FeaturesPlugin_Copy.h b/src/FeaturesPlugin/FeaturesPlugin_Copy.h
new file mode 100644 (file)
index 0000000..b500b51
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (C) 2017-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
+//
+
+#ifndef FeaturesPlugin_Copy_H_
+#define FeaturesPlugin_Copy_H_
+
+#include "FeaturesPlugin.h"
+
+#include <ModelAPI_Feature.h>
+
+/// \class FeaturesPlugin_Copy
+/// \ingroup Plugins
+/// \brief This feature copies the selected results and sub-results (for the whole feature selected
+///        all results of this feature are copied). The referenced arguments of this feature are
+///        not concealed. The difference with \94Recover\94 feature is that not concealed results may
+///        be selected and in the history behavior: the \93Move to the End\94 of groups will move to
+///        all copy-results.
+
+class FeaturesPlugin_Copy : public ModelAPI_Feature, public ModelAPI_FeatureCopyInterface
+{
+public:
+  /// Feature kind.
+  inline static const std::string& ID()
+  {
+    static const std::string MY_ID("Copy");
+    return MY_ID;
+  }
+
+  /// \return the kind of a feature.
+  FEATURESPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    static std::string MY_KIND = FeaturesPlugin_Copy::ID();
+    return MY_KIND;
+  }
+
+  /// Selection list attribute that contains all copied shapes selection.
+  inline static const std::string& OBJECTS()
+  {
+    static const std::string MY_OBJECTS("objects");
+    return MY_OBJECTS;
+  }
+  /// Integer attribute that contains the number of resulting copies needed
+  inline static const std::string NUMBER()
+  {
+    static std::string MY_NUMBER("number");
+    return MY_NUMBER;
+  }
+
+  /// Performs the algorithm and stores results it in the data structure.
+  FEATURESPLUGIN_EXPORT virtual void execute();
+
+  /// Request for initialization of data model of the feature: adding all attributes.
+  FEATURESPLUGIN_EXPORT virtual void initAttributes();
+
+  /// To update the group feature which is moved over this copy feature (to add copies to selection)
+  FEATURESPLUGIN_EXPORT virtual void getCopies(
+    ObjectPtr theContext, std::shared_ptr<GeomAPI_Shape> theValue,
+    std::list<ObjectPtr>& theCopyContext, std::list<std::shared_ptr<GeomAPI_Shape> >& theCopyVals);
+
+  /// Use plugin manager for features creation.
+  FeaturesPlugin_Copy() {}
+};
+
+#endif
index 7973434462db3f8af12183e9c78748163ed714b5..5a2ff7c5c99ab3c69b6a166de124e61d46966ac6 100644 (file)
@@ -48,6 +48,7 @@
 #include <FeaturesPlugin_Union.h>
 #include <FeaturesPlugin_FusionFaces.h>
 #include <FeaturesPlugin_RemoveResults.h>
+#include <FeaturesPlugin_Copy.h>
 #include <FeaturesPlugin_ValidatorTransform.h>
 #include <FeaturesPlugin_Validators.h>
 
@@ -179,8 +180,11 @@ FeaturePtr FeaturesPlugin_Plugin::createFeature(std::string theFeatureID)
     return FeaturePtr(new FeaturesPlugin_RemoveResults);
   } else if (theFeatureID == FeaturesPlugin_Chamfer::ID()) {
     return FeaturePtr(new FeaturesPlugin_Chamfer);
+  } else if (theFeatureID == FeaturesPlugin_Copy::ID()) {
+    return FeaturePtr(new FeaturesPlugin_Copy);
   }
 
+
   // feature of such kind is not found
   return FeaturePtr();
 }
diff --git a/src/FeaturesPlugin/Test/TestCopyFeature.py b/src/FeaturesPlugin/Test/TestCopyFeature.py
new file mode 100644 (file)
index 0000000..17f99d1
--- /dev/null
@@ -0,0 +1,48 @@
+# 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
+#
+
+# Checks selection of the whole featurte and move to the end of the group created on results of this feature..
+
+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"))
+SketchCircle_1 = Sketch_1.addCircle(11.02869497636673, 9.8764247475525, 3.312248077480665)
+SketchCircle_2 = Sketch_1.addCircle(4.278198729238611, 4.677840612715367, 1.794922837237287)
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("COMPOUND", "all-in-Sketch_1")], model.selection(), 10, 0)
+Group_1 = model.addGroup(Part_1_doc, "Faces", [model.selection("SOLID", "Extrusion_1_1"), model.selection("SOLID", "Extrusion_1_2")])
+Copy_1 = model.addCopy(Part_1_doc, [model.selection("COMPOUND", "all-in-Extrusion_1")], 1)
+model.do()
+# move the group feature to the end - through copy of the whole feature
+Part_1_doc.moveFeature(Group_1.feature(), Copy_1.feature())
+model.end()
+
+from ModelAPI import *
+aFactory = ModelAPI_Session.get().validators()
+assert(aFactory.validate(Group_1.feature()))
+selectionList = Group_1.feature().selectionList("group_list")
+assert(selectionList.size() == 4) # two original solids plus two copies
+assert(selectionList.value(3).namingName() == "Extrusion_1_1_1_1")
+assert(selectionList.value(2).namingName() == "Extrusion_1_1_1_2")
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestCopyFeatureMoveGroupOfFeature.py b/src/FeaturesPlugin/Test/TestCopyFeatureMoveGroupOfFeature.py
new file mode 100644 (file)
index 0000000..0d8eb2d
--- /dev/null
@@ -0,0 +1,48 @@
+# 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
+#
+
+# Checks selection of the whole featurte and move to the end of the group created on this feature..
+
+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"))
+SketchCircle_1 = Sketch_1.addCircle(11.02869497636673, 9.8764247475525, 3.312248077480665)
+SketchCircle_2 = Sketch_1.addCircle(4.278198729238611, 4.677840612715367, 1.794922837237287)
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("COMPOUND", "all-in-Sketch_1")], model.selection(), 10, 0)
+Group_1 = model.addGroup(Part_1_doc, "Faces", [model.selection("COMPOUND", "all-in-Extrusion_1")])
+Copy_1 = model.addCopy(Part_1_doc, [model.selection("COMPOUND", "all-in-Extrusion_1")], 1)
+model.do()
+# move the group feature to the end - through copy of the whole feature
+Part_1_doc.moveFeature(Group_1.feature(), Copy_1.feature())
+model.end()
+
+from ModelAPI import *
+aFactory = ModelAPI_Session.get().validators()
+assert(aFactory.validate(Group_1.feature()))
+selectionList = Group_1.feature().selectionList("group_list")
+assert(selectionList.size() == 2) # two: original feature and the copy-feature
+assert(selectionList.value(0).namingName() == "all-in-Extrusion_1")
+assert(selectionList.value(1).namingName() == "all-in-Copy_1")
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestCopyMoveResult.py b/src/FeaturesPlugin/Test/TestCopyMoveResult.py
new file mode 100644 (file)
index 0000000..bde1d85
--- /dev/null
@@ -0,0 +1,66 @@
+# 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
+#
+
+# Checks selection of the whole shape and move to the end of the simple copy wihtout the
+# next modifications applied.
+
+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"))
+SketchLine_1 = Sketch_1.addLine(38.31034482758622, 31.49775664633739, 7.836206896551726, 31.49775664633739)
+SketchLine_2 = Sketch_1.addLine(7.836206896551726, 31.49775664633739, 7.836206896551726, 8.984209848307833)
+SketchLine_3 = Sketch_1.addLine(7.836206896551726, 8.984209848307833, 38.31034482758622, 8.984209848307833)
+SketchLine_4 = Sketch_1.addLine(38.31034482758622, 8.984209848307833, 38.31034482758622, 31.49775664633739)
+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())
+SketchLine_5 = Sketch_1.addLine(21.64285714285715, 31.4977566463374, 25.0012315270936, 8.984209848307833)
+SketchConstraintCoincidence_5 = Sketch_1.setCoincident(SketchLine_5.startPoint(), SketchLine_1.result())
+SketchConstraintCoincidence_6 = Sketch_1.setCoincident(SketchLine_5.endPoint(), SketchLine_3.result())
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchLine_1r-SketchLine_2f-SketchLine_3f-SketchLine_5r"), model.selection("FACE", "Sketch_1/Face-SketchLine_1r-SketchLine_5f-SketchLine_3f-SketchLine_4f")], model.selection(), 7, 0)
+Extrusion_1.result().setName("Origin")
+Group_1 = model.addGroup(Part_1_doc, "Solids", [model.selection("SOLID", "Extrusion_1_1_1")])
+Copy_1 = model.addCopy(Part_1_doc, [model.selection("COMPSOLID", "Origin")], 1)
+Copy_1.result().setName("Origin_1")
+Fillet_1 = model.addFillet(Part_1_doc, [model.selection("EDGE", "[Origin_1_1/Copy_3][Origin_1_1/Copy_7]"), model.selection("EDGE", "[Origin_1_1/Copy_12][Origin_1_1/Copy_14]")], 2)
+Fillet_1.result().setName("CopyCompound")
+model.do()
+# move the group feature to the end - through copy and fillet on this copy (to distinguish the origin and the copy)
+Part_1_doc.moveFeature(Group_1.feature(), Fillet_1.feature())
+model.end()
+
+from ModelAPI import *
+aFactory = ModelAPI_Session.get().validators()
+assert(aFactory.validate(Group_1.feature()))
+selectionList = Group_1.feature().selectionList("group_list")
+assert(selectionList.size() == 1) # still the same solid
+
+assert(selectionList.value(0).namingName() == "Fillet_1_1_1")
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestCopyMoveSubShapes.py b/src/FeaturesPlugin/Test/TestCopyMoveSubShapes.py
new file mode 100644 (file)
index 0000000..f2d78e5
--- /dev/null
@@ -0,0 +1,57 @@
+# 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
+#
+
+# Checks selection of the sub-shapes move to the end with combination of 2 Copy features in history
+
+from salome.shaper import model
+from ModelAPI import *
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Box_1 = model.addBox(Part_1_doc, 10, 10, 10)
+Group_1 = model.addGroup(Part_1_doc, "Edges", [model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Top]")])
+Copy_1 = model.addCopy(Part_1_doc, [model.selection("FACE", "Box_1_1/Front")], 2)
+ExtrusionCut_1 = model.addExtrusionCut(Part_1_doc, [], model.selection(), 0, 5, [model.selection("SOLID", "Box_1_1")])
+Sketch_1 = model.addSketch(Part_1_doc, model.selection("FACE", "Box_1_1/Front"))
+SketchCircle_1 = Sketch_1.addCircle(9.650212071680357, 9.344990586582618, 1.565166813054581)
+ExtrusionCut_1.setNestedSketch(Sketch_1)
+ExtrusionCut_1.result().setColor(225, 0, 0)
+Sketch_2 = model.addSketch(Part_1_doc, model.selection("FACE", "Box_1_1_1"))
+SketchCircle_2 = Sketch_2.addCircle(0.7603686814133139, 9.06793355634084, 1.630854194501576)
+model.do()
+Face_1 = model.addFace(Part_1_doc, [model.selection("FACE", "Sketch_2/Face-SketchCircle_2_2r")])
+Cut_1 = model.addCut(Part_1_doc, [model.selection("FACE", "Box_1_1_1")], [model.selection("FACE", "Face_1_1")], keepSubResults = True)
+Translation_1 = model.addTranslation(Part_1_doc, [model.selection("FACE", "Box_1_1_2")], model.selection("EDGE", "PartSet/OX"), 5)
+Copy_2 = model.addCopy(Part_1_doc, [model.selection("FACE", "Box_1_1_2")], 2)
+Rotation_1 = model.addRotation(Part_1_doc, [model.selection("FACE", "Box_1_1_2_1")], model.selection("EDGE", "PartSet/OZ"), 10)
+Rotation_2 = model.addRotation(Part_1_doc, [model.selection("FACE", "Box_1_1_2_2")], model.selection("EDGE", "PartSet/OZ"), 20)
+model.do()
+# move the group feature to the end - through 2 copies and many modifications of the selected edge
+Part_1_doc.moveFeature(Group_1.feature(), Rotation_2.feature())
+model.end()
+
+# result is 5 edges: cut, translation, 2 rotations, extrusion cut
+aFactory = ModelAPI_Session.get().validators()
+assert(aFactory.validate(Group_1.feature()))
+selectionList = Group_1.feature().selectionList("group_list")
+assert(selectionList.size() == 5)
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestCopyNames.py b/src/FeaturesPlugin/Test/TestCopyNames.py
new file mode 100644 (file)
index 0000000..eac569d
--- /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
+#
+
+# Checks that the Copy feature produces correct names, same as in the description #3109
+
+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"))
+SketchLine_1 = Sketch_1.addLine(-11.9408866995074, 14.67733990147784, -29.35467980295567, 14.67733990147784)
+SketchLine_2 = Sketch_1.addLine(-29.35467980295567, 14.67733990147784, -29.35467980295567, -7.960591133004924)
+SketchLine_3 = Sketch_1.addLine(-29.35467980295567, -7.960591133004924, -11.9408866995074, -7.960591133004924)
+SketchLine_4 = Sketch_1.addLine(-11.9408866995074, -7.960591133004924, -11.9408866995074, 14.67733990147784)
+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(14.92610837438425, 16.04556650246306, 6.602917012013241)
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchLine_1r-SketchLine_2f-SketchLine_3f-SketchLine_4f")], model.selection(), 10, 0)
+Extrusion_1.result().setName("Box")
+Extrusion_2 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchCircle_1_2f")], model.selection(), 10, 0)
+Extrusion_2.result().setName("Cylinder")
+Copy_1 = model.addCopy(Part_1_doc, [model.selection("SOLID", "Box"), model.selection("SOLID", "Cylinder")], 3)
+model.end()
+
+assert(Copy_1.feature().results().size() == 6)
+
+assert(Copy_1.feature().results()[0].data().name() == "Box_1")
+assert(Copy_1.feature().results()[1].data().name() == "Cylinder_1")
+assert(Copy_1.feature().results()[2].data().name() == "Box_2")
+assert(Copy_1.feature().results()[3].data().name() == "Cylinder_2")
+assert(Copy_1.feature().results()[4].data().name() == "Box_3")
+assert(Copy_1.feature().results()[5].data().name() == "Cylinder_3")
+
+model.begin()
+model.testHaveNamingSubshapes(Copy_1, model, Part_1_doc)
+model.end()
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestCopySubShapes.py b/src/FeaturesPlugin/Test/TestCopySubShapes.py
new file mode 100644 (file)
index 0000000..f8aedf2
--- /dev/null
@@ -0,0 +1,58 @@
+# 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
+#
+
+# Checks that the Copy feature produces correct results and names if sub-shapes of the same shape are selected
+
+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"))
+SketchLine_1 = Sketch_1.addLine(-25.99630541871921, 19.52832512315271, -16.66748768472907, 29.72783251231528)
+SketchLine_2 = Sketch_1.addLine(-16.66748768472907, 29.72783251231528, -12.4384236453202, 17.66256157635469)
+SketchConstraintCoincidence_1 = Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint())
+SketchLine_3 = Sketch_1.addLine(-12.4384236453202, 17.66256157635469, -25.99630541871921, 19.52832512315271)
+SketchConstraintCoincidence_2 = Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchLine_3.startPoint())
+SketchConstraintCoincidence_3 = Sketch_1.setCoincident(SketchLine_1.startPoint(), SketchLine_3.endPoint())
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchLine_3r-SketchLine_2r-SketchLine_1r")], model.selection(), 10, 0)
+Extrusion_1.result().setName("Prism")
+Copy_1_objects = [model.selection("FACE", "Prism/Generated_Face&Sketch_1/SketchLine_3"), model.selection("FACE", "Prism/Generated_Face&Sketch_1/SketchLine_2"), model.selection("EDGE", "[Prism/Generated_Face&Sketch_1/SketchLine_1][Prism/To_Face]")]
+Copy_1 = model.addCopy(Part_1_doc, Copy_1_objects, 2)
+Copy_1.result().setName("Prism_1")
+model.end()
+
+assert(Copy_1.feature().results().size() == 6)
+
+for index in range(6):
+  # name is just incremented
+  assert(Copy_1.feature().results()[index].data().name() == "Prism_" + str(index + 1))
+  # type of the shape corresponds to selection: 2 faces then edge
+  if index%3 == 2:
+    assert(Copy_1.feature().results()[index].shape().shapeTypeStr() == "EDGE")
+  else:
+    assert(Copy_1.feature().results()[index].shape().shapeTypeStr() == "FACE")
+
+model.begin()
+model.testHaveNamingSubshapes(Copy_1, model, Part_1_doc)
+model.end()
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestCopyWholeFeature.py b/src/FeaturesPlugin/Test/TestCopyWholeFeature.py
new file mode 100644 (file)
index 0000000..0a9a884
--- /dev/null
@@ -0,0 +1,73 @@
+# 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
+#
+
+# Checks that the Copy feature produces correct results and names if the whole result and feature are copied.
+
+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"))
+SketchLine_1 = Sketch_1.addLine(-19.47177806477657, -9.723714972297476, -32.14322074518937, 18.42485654704614)
+SketchLine_2 = Sketch_1.addLine(-32.14322074518937, 18.42485654704614, -13.96909823036938, 5.527027954588926)
+SketchConstraintCoincidence_1 = Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_2.startPoint())
+SketchLine_3 = Sketch_1.addLine(-13.96909823036938, 5.527027954588926, -19.47177806477657, -9.723714972297476)
+SketchConstraintCoincidence_2 = Sketch_1.setCoincident(SketchLine_2.endPoint(), SketchLine_3.startPoint())
+SketchConstraintCoincidence_3 = Sketch_1.setCoincident(SketchLine_1.startPoint(), SketchLine_3.endPoint())
+SketchLine_4 = Sketch_1.addLine(-19.47177806477657, -9.723714972297476, -38.20802856635906, 4.439747463237282)
+SketchConstraintCoincidence_4 = Sketch_1.setCoincident(SketchLine_1.startPoint(), SketchLine_4.startPoint())
+SketchLine_5 = Sketch_1.addLine(-38.20802856635906, 4.439747463237282, -32.14322074518937, 18.42485654704614)
+SketchConstraintCoincidence_5 = Sketch_1.setCoincident(SketchLine_4.endPoint(), SketchLine_5.startPoint())
+SketchConstraintCoincidence_6 = Sketch_1.setCoincident(SketchLine_1.endPoint(), SketchLine_5.endPoint())
+SketchCircle_1 = Sketch_1.addCircle(15.45187411494798, 10.57784256870842, 6.036432809751617)
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchLine_1r-SketchLine_5r-SketchLine_4r"), model.selection("FACE", "Sketch_1/Face-SketchLine_3r-SketchLine_2r-SketchLine_1r")], model.selection(), 10, 0)
+Extrusion_2 = model.addExtrusion(Part_1_doc, [model.selection("WIRE", "Sketch_1/Face-SketchCircle_1_2f_wire")], model.selection(), 10, 0)
+Copy_1 = model.addCopy(Part_1_doc, [model.selection("COMPSOLID", "Extrusion_1_1"), model.selection("COMPOUND", "all-in-Extrusion_2")], 1)
+model.end()
+
+assert(Copy_1.feature().results().size() == 2)
+
+from ModelAPI import *
+assert(Copy_1.feature().results()[0].data().name() == "Extrusion_1_1_1")
+assert(modelAPI_ResultBody(Copy_1.feature().results()[0]).subResult(0).data().name() == "Extrusion_1_1_1_1")
+assert(modelAPI_ResultBody(Copy_1.feature().results()[0]).subResult(1).data().name() == "Extrusion_1_1_1_2")
+assert(Copy_1.feature().results()[1].data().name() == "Extrusion_2_1_1")
+
+# Check that copy of the whole feature that contains several results produce compound of all results
+model.begin()
+Extrusion_3 = model.addExtrusion(Part_1_doc, [model.selection("COMPOUND", "all-in-Sketch_1")], model.selection(), 10, 0)
+Copy_2 = model.addCopy(Part_1_doc, [model.selection("COMPOUND", "all-in-Extrusion_3")], 1)
+model.end()
+
+assert(Copy_2.feature().results()[0].data().name() == "Extrusion_3_1_1")
+assert(Copy_2.feature().results()[0].shape().shapeTypeStr() == "COMPOUND")
+aSub1 = modelAPI_ResultBody(Copy_2.feature().results()[0]).subResult(0)
+assert(aSub1.shape().shapeTypeStr() == "COMPSOLID")
+aSub2 = modelAPI_ResultBody(Copy_2.feature().results()[0]).subResult(1)
+assert(aSub2.shape().shapeTypeStr() == "SOLID")
+
+model.begin()
+model.testHaveNamingSubshapes(Copy_1, model, Part_1_doc)
+model.testHaveNamingSubshapes(Copy_2, model, Part_1_doc)
+model.end()
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/copy_widget.xml b/src/FeaturesPlugin/copy_widget.xml
new file mode 100644 (file)
index 0000000..6f79ca1
--- /dev/null
@@ -0,0 +1,16 @@
+<source>
+  <multi_selector id="objects"
+                  label="Sources:"
+                  tooltip="Select copied objects"
+                  shape_types="vertices edges wires faces shells compsolids objects"
+                  use_choice="false"
+                  concealment="false">
+  </multi_selector>
+  <integervalue id="number"
+                label="Nb copies"
+                step="1"
+                default="1"
+                min="1"
+                tooltip="Number of copies">
+  </integervalue>
+</source>
diff --git a/src/FeaturesPlugin/icons/copy.png b/src/FeaturesPlugin/icons/copy.png
new file mode 100644 (file)
index 0000000..7e11163
Binary files /dev/null and b/src/FeaturesPlugin/icons/copy.png differ
index 3197488a7e3870b1b58ad4d5ed13060f249a736d..428f0031445938fe0354cc5f09dc6f0b3a0ebcf2 100644 (file)
         helpfile="recoverFeature.html">
         <source path="recover_widget.xml"/>
       </feature>
+      <feature id="Copy"
+        title="Copy"
+        tooltip="Copies results or sub-results"
+        icon="icons/Features/copy.png"
+        helpfile="copyFeature.html">
+        <source path="copy_widget.xml"/>
+      </feature>
       <feature id="RemoveResults" title="Remove results" tooltip="Internal feature for results removal" internal="1">
         <multi_selector id="results" concealment="true"/>
       </feature>
index 8ff6d81f8a87b93a8b4a402de30c454f7893cd8b..468b60f7ab1d0c87c5644336614ece5d804be168 100644 (file)
@@ -1292,8 +1292,8 @@ void Model_AttributeSelection::computeValues(
 
 
 void Model_AttributeSelection::concealedFeature(
-  const FeaturePtr theFeature, const FeaturePtr theStop, std::list<FeaturePtr>& theConcealers,
-  const ResultPtr theResultOfFeature)
+  const FeaturePtr theFeature, const FeaturePtr theStop, const bool theCheckCopy,
+  std::list<FeaturePtr>& theConcealers, const ResultPtr theResultOfFeature)
 {
   std::set<FeaturePtr> alreadyProcessed;
   alreadyProcessed.insert(theFeature);
@@ -1328,7 +1328,9 @@ void Model_AttributeSelection::concealedFeature(
         if (alreadyProcessed.find(aRefFeat) != alreadyProcessed.end()) // optimization
           continue;
         alreadyProcessed.insert(aRefFeat);
-        if (ModelAPI_Session::get()->validators()->isConcealed(aRefFeat->getKind(), (*aRef)->id()))
+        if (ModelAPI_Session::get()->validators()->isConcealed(aRefFeat->getKind(), (*aRef)->id())
+          || (theCheckCopy &&
+              std::dynamic_pointer_cast<ModelAPI_FeatureCopyInterface>(aRefFeat).get()))
         {
           // for extrusion cut in python script the nested sketch reference may be concealed before
           // it is nested, so, check this composite feature is valid
@@ -1410,12 +1412,13 @@ bool Model_AttributeSelection::searchNewContext(std::shared_ptr<Model_Document>
     } else aResIter++;
   }
 
+  bool aStaySame = false;
   if (aResults.empty()) {
     // check the context become concealed by operation which is earlier than this selection
     FeaturePtr aThisFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(owner());
     FeaturePtr aContextOwner = theDoc->feature(theContext);
     std::list<FeaturePtr> aConcealers;
-    concealedFeature(aContextOwner, aThisFeature, aConcealers, theContext);
+    concealedFeature(aContextOwner, aThisFeature, false, aConcealers, theContext);
     std::list<FeaturePtr>::iterator aConcealer = aConcealers.begin();
     for(; aConcealer != aConcealers.end(); aConcealer++) {
       std::list<ResultPtr> aRefResults;
@@ -1436,26 +1439,87 @@ bool Model_AttributeSelection::searchNewContext(std::shared_ptr<Model_Document>
       if (aResults.empty())
         return true; // feature conceals result, return true, so the context will be removed
     }
-    if (aResults.empty())
-      return false; // no modifications found, must stay the same
+    aStaySame = aResults.empty();
+  }
+  if (myParent && myParent->isMakeCopy()) {
+    // check there are copies before the new results, so, make a copy
+    std::set<ResultPtr>::iterator aResIter = aResults.begin();
+    std::list<ObjectPtr> aCopyContext;
+    std::list<GeomShapePtr> aCopyVals;
+    // features between the new and the old: check the "Move" interface to get a copy
+    FeaturePtr aRootOwner = theDoc->feature(theContext);
+    FeaturePtr anOwner = ModelAPI_Tools::compositeOwner(aRootOwner);
+    for(; anOwner.get(); anOwner = ModelAPI_Tools::compositeOwner(anOwner))
+      aRootOwner = anOwner;
+    FeaturePtr aThisFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(owner());
+    // iterate all results to find a "Copy" features between the new and one and to add the
+    // copy-results also to results if this attribute refers to the copied shape
+    int anIndex = kUNDEFINED_FEATURE_INDEX;
+    for(FeaturePtr aFeat = theDoc->objects()->nextFeature(aRootOwner, anIndex); aFeat.get() &&
+        aFeat != aThisFeature; aFeat = theDoc->objects()->nextFeature(aFeat, anIndex)) {
+      std::shared_ptr<ModelAPI_FeatureCopyInterface> aCopier =
+        std::dynamic_pointer_cast<ModelAPI_FeatureCopyInterface>(aFeat);
+      if (aCopier.get()) {
+        GeomShapePtr aValShape(new GeomAPI_Shape);
+        aValShape->setImpl<TopoDS_Shape>(new TopoDS_Shape(
+          theValShape.IsNull() ? theContShape : theValShape));
+        aCopier->getCopies(theContext, aValShape, aCopyContext, aCopyVals);
+      }
+    }
+    // check for the further modifications of the copy contexts and values
+    std::list<ObjectPtr>::iterator aCopyContIter = aCopyContext.begin();
+    std::list<GeomShapePtr>::iterator aCopyValIter = aCopyVals.begin();
+    for(; aCopyContIter != aCopyContext.end(); aCopyContIter++, aCopyValIter++) {
+      ResultPtr aNewCont = std::dynamic_pointer_cast<ModelAPI_Result>(*aCopyContIter);
+      TopoDS_Shape aNewContShape = aNewCont->shape()->impl<TopoDS_Shape>();
+      GeomShapePtr aNewVal = *aCopyValIter;
+      TopoDS_Shape aNewValShape;
+      if (aNewVal.get() && !aNewVal->isNull())
+        aNewValShape = aNewVal->impl<TopoDS_Shape>();
+      std::list<ResultPtr> aNewRes;
+      TopTools_ListOfShape aNewUpdatedVal;
+      if (searchNewContext(theDoc, aNewContShape, aNewCont, aNewValShape,
+          theAccessLabel, aNewRes, aNewUpdatedVal)) {
+        // append new results instead of the current ones
+        std::list<ResultPtr>::iterator aNewIter = aNewRes.begin();
+        TopTools_ListIteratorOfListOfShape aNewUpdVal(aNewUpdatedVal);
+        for(; aNewIter != aNewRes.end(); aNewIter++, aNewUpdVal.Next()) {
+          theResults.push_back(*aNewIter);
+          theValShapes.Append(aNewUpdVal.Value());
+        }
+      } else { // the current result is good
+        theResults.push_back(aNewCont);
+        theValShapes.Append(aNewValShape);
+      }
+    }
+    if (aStaySame && !theResults.empty()) { // no changes except copy, so, keep the origin as first
+      theResults.push_front(theContext);
+      theValShapes.Prepend(theValShape);
+      return true;
+    }
   }
+  if (aStaySame)
+    return false;
+
   // iterate all results to find further modifications
   std::set<ResultPtr>::iterator aResIter = aResults.begin();
-  for(; aResIter != aResults.end(); aResIter++) {
+  for(aResIter = aResults.begin(); aResIter != aResults.end(); aResIter++) {
     if (aResIter->get() != NULL) {
+      ResultPtr aNewResObj = *aResIter;
       // compute new values by two contexts: the old and the new
       TopTools_ListOfShape aValShapes;
-      computeValues(theContext, *aResIter, theValShape, aValShapes);
+      computeValues(theContext, aNewResObj, theValShape, aValShapes);
 
       TopTools_ListIteratorOfListOfShape aNewVal(aValShapes);
       for(; aNewVal.More(); aNewVal.Next()) {
         std::list<ResultPtr> aNewRes;
         TopTools_ListOfShape aNewUpdatedVal;
         TopoDS_Shape aNewValSh = aNewVal.Value();
-        TopoDS_Shape aNewContShape = (*aResIter)->shape()->impl<TopoDS_Shape>();
+        TopoDS_Shape aNewContShape = aNewResObj->shape()->impl<TopoDS_Shape>();
+
         if (theValShape.IsNull() && aNewContShape.IsSame(aNewValSh))
           aNewValSh.Nullify();
-        if (searchNewContext(theDoc, aNewContShape, *aResIter, aNewValSh,
+        if (searchNewContext(theDoc, aNewContShape, aNewResObj, aNewValSh,
                              theAccessLabel, aNewRes, aNewUpdatedVal))
         {
           // append new results instead of the current ones
@@ -1466,7 +1530,7 @@ bool Model_AttributeSelection::searchNewContext(std::shared_ptr<Model_Document>
             theValShapes.Append(aNewUpdVal.Value());
           }
         } else { // the current result is good
-          theResults.push_back(*aResIter);
+          theResults.push_back(aNewResObj);
           theValShapes.Append(aNewValSh);
         }
       }
@@ -1488,25 +1552,40 @@ void Model_AttributeSelection::updateInHistory(bool& theRemove)
       if (aFeature.get()) {
         FeaturePtr aThisFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(owner());
         std::list<FeaturePtr> aConcealers;
-        concealedFeature(aFeature, aThisFeature, aConcealers, ResultPtr());
+        bool aCopyPossible = myParent && myParent->isMakeCopy();
+        concealedFeature(aFeature, aThisFeature, aCopyPossible, aConcealers, ResultPtr());
         if (aConcealers.empty())
           return;
+        // if there are copies, but no direct modification, keep the original
+        bool aKeepOrigin = false;
+        if (aCopyPossible) {
+          std::list<FeaturePtr>::iterator aConcealer = aConcealers.begin();
+          for(aKeepOrigin = true; aConcealer != aConcealers.end(); aConcealer++)
+            if (!std::dynamic_pointer_cast<ModelAPI_FeatureCopyInterface>(*aConcealer).get()) {
+              aKeepOrigin = false;
+              break;
+            }
+          if (aKeepOrigin) {
+            aConcealers.push_front(aFeature);
+          }
+        }
         bool aChanged = false;
         std::list<FeaturePtr>::iterator aConcealer = aConcealers.begin();
         for(; aConcealer != aConcealers.end(); aConcealer++)
-          if (!myParent->isInList(*aConcealer, anEmptyShape)) {// avoid addition of duplicates
-            setValue(*aConcealer, anEmptyShape);
-            aChanged = true;
-          }
-        if (aConcealer == aConcealers.end()) {
-          if (!aChanged) // remove this
-            theRemove = true;
-        } else { // append new
-          for(aConcealer++; aConcealer != aConcealers.end(); aConcealer++)
-            if (!myParent->isInList(*aConcealer, anEmptyShape)) // avoid addition of duplicates
+          if (aChanged) {
+            if (aKeepOrigin || !myParent->isInList(*aConcealer, anEmptyShape))
               myParent->append(*aConcealer, anEmptyShape);
-        }
-        if (aChanged) // searching for the further modifications
+          } else {
+            if (!myParent->isInList(*aConcealer, anEmptyShape)) {// avoid addition of duplicates
+              setValue(*aConcealer, anEmptyShape);
+              aChanged = true;
+            } else if (aCopyPossible && *aConcealer == aFeature) { // keep the origin in case of copy
+              aChanged = true;
+            }
+          }
+        if (!aChanged) // remove this
+          theRemove = true;
+        else if (!aKeepOrigin) // searching further modifications only if current changed
           updateInHistory(theRemove);
       }
     }
@@ -1704,6 +1783,9 @@ void Model_AttributeSelection::updateInHistory(bool& theRemove)
         if (!myParent || !myParent->isInList(aNewContext, aValueShape)) { // avoid duplicates
           setValue(aNewContext, aValueShape);
           aFirst = false;
+        } else if (aNewContext == aContext && myParent && myParent->isMakeCopy()) {
+          // this may be exactly the old one, not modified in case of copy
+          aFirst = false;
         }
       } else if (myParent) {
         if (!myParent->isInList(aNewContext, aValueShape)) // avoid addition of duplicates
index 50a7ddbb91645726c340b095ce6db0ac97e3acd8..8e70cb28b3f595ea9c10f01c6b2fed92dc9fc296 100644 (file)
@@ -208,8 +208,8 @@ protected:
   /// Returns features that conceals theFeature and located in history before theStop
   /// theResultOfFeature if not null defines exact referenced result of a feature
   void concealedFeature(
-    const FeaturePtr theFeature, const FeaturePtr theStop, std::list<FeaturePtr>& theConcealers,
-    const ResultPtr theResultOfFeature);
+    const FeaturePtr theFeature, const FeaturePtr theStop, const bool theCheckCopy,
+    std::list<FeaturePtr>& theConcealers, const ResultPtr theResultOfFeature);
 
   friend class Model_Data;
   friend class Model_AttributeSelectionList;
index 79162a77945b2302e00d7ddc8818358b7cd60457..1efd395afc2519c15626f4bd9d77bdc561af615c 100644 (file)
@@ -1263,6 +1263,12 @@ void Model_Document::moveFeature(FeaturePtr theMoved, FeaturePtr theAfterThis, c
     } while (aSub.get());
   }
 
+  AttributeSelectionListPtr aMovedList;
+  if (theMoved->getKind() == "Group") {
+    aMovedList = theMoved->selectionList("group_list");
+    if (aMovedList.get())
+      aMovedList->setMakeCopy(true);
+  }
   myObjs->moveFeature(theMoved, anAfterThisSub);
 
   if (theSplit) { // split the group into sub-features
@@ -1275,10 +1281,8 @@ void Model_Document::moveFeature(FeaturePtr theMoved, FeaturePtr theAfterThis, c
     // 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");
-  }
+  if (aMovedList.get())
+    aMovedList->setMakeCopy(false);
 }
 
 void Model_Document::updateHistory(const std::shared_ptr<ModelAPI_Object> theObject)
index c5bedb6bae96d0c57bbaea334ff4e774b974df0c..4d19917385d6b25111e39640d112c97475bee7fe 100644 (file)
@@ -113,6 +113,21 @@ void Model_ResultBody::loadModifiedShapes(const std::shared_ptr<GeomAlgoAPI_Make
   }
 }
 
+void Model_ResultBody::loadFirstLevel(GeomShapePtr theShape, const std::string& theName)
+{
+  if (mySubs.size()) { // consists of subs
+    for (std::vector<ResultBodyPtr>::const_iterator aSubIter = mySubs.cbegin();
+      aSubIter != mySubs.cend();
+      ++aSubIter)
+    {
+      const ResultBodyPtr& aSub = *aSubIter;
+      aSub->loadFirstLevel(theShape, theName);
+    }
+  } else { // do for this directly
+    myBuilder->loadFirstLevel(theShape, theName);
+  }
+}
+
 int Model_ResultBody::numberOfSubs(bool forTree) const
 {
   return int(mySubs.size());
index 663bf8cecf416c54e02ed6f4e8ace29f3f719672..d0a1f908e68c946dbf38b6859de94cb8326ef33a 100644 (file)
@@ -78,6 +78,8 @@ public:
                                   const GeomAPI_Shape::ShapeType theShapeTypeToExplore,
                                   const std::string& theName = "") override;
 
+  /// load shapes of the first level (to be used during shape import)
+  MODEL_EXPORT virtual void loadFirstLevel(GeomShapePtr theShape, const std::string& theName);
 
   /// Returns the number of sub-elements
   MODEL_EXPORT virtual int numberOfSubs(bool forTree = false) const;
index a88017e3412403701017112c1a31277e6e17f0f8..60b9becfad0f08d09dc1acf4e2b56597a343095e 100644 (file)
@@ -39,7 +39,10 @@ class ModelAPI_AttributeSelectionList : public ModelAPI_Attribute
   /// may be sub-objects, so, it is the same as all sub-shapes are selected (#3005). It is "false"
   /// by default.
   bool myIsWholeResultAllowed;
- public:
+  /// Flag that indicates that update in history must check the copy-features
+  /// and make a copy of selection for them.
+  bool myMakeCopy;
+public:
   /// Adds the new reference to the end of the list
   /// \param theContext object where the sub-shape was selected
   /// \param theSubShape selected sub-shape (if null, the whole context is selected)
@@ -135,10 +138,20 @@ class ModelAPI_AttributeSelectionList : public ModelAPI_Attribute
     myIsWholeResultAllowed = theFlag;
   }
 
+  /// Returns true if a copy features must be used in update in history.
+  MODELAPI_EXPORT virtual const bool isMakeCopy() const {
+    return myMakeCopy;
+  }
+
+  /// Sets true if a copy features must be used in update in history.
+  MODELAPI_EXPORT virtual void setMakeCopy(const bool theFlag)  {
+    myMakeCopy = theFlag;
+  }
+
 protected:
   /// Default constructor
   MODELAPI_EXPORT ModelAPI_AttributeSelectionList() : ModelAPI_Attribute()
-  {myIsWholeResultAllowed = false;}
+  {myIsWholeResultAllowed = false; myMakeCopy = false;}
 
 };
 
index fe7cc35e872465e157331fc81353a5416ecfb5ad..8c231608656b13658eb1334ce373d715f557adbc 100644 (file)
@@ -250,5 +250,17 @@ class ModelAPI_Feature : public ModelAPI_Object
 //! Pointer on feature object
 typedef std::shared_ptr<ModelAPI_Feature> FeaturePtr;
 
-#endif
+//! An interface for performing special copy actions. To give feature which is moved (a group)
+//! over this feature.
+class ModelAPI_FeatureCopyInterface {
+public:
+  /// An algorithm to update the moved feature by the separate Copy feature
+  /// \param theContext the original context object
+  /// \param theValue the original shape
+  /// \param theCopies resulting copy-context will be appended here
+  virtual void getCopies(ObjectPtr theContext, std::shared_ptr<GeomAPI_Shape> theValue,
+                         std::list<ObjectPtr>& theCopyContext,
+                         std::list<std::shared_ptr<GeomAPI_Shape> >& theCopyVals) = 0;
+};
 
+#endif
index 603aaac4df93d98a740c804eefbafa7a4928d40d..0a291ae486bd13ed486ae86ba8558e60faeec8dd 100644 (file)
@@ -139,12 +139,6 @@ void ModelAPI_ResultBody::loadDeletedShapes(const GeomMakeShapePtr& theAlgo,
   myBuilder->loadDeletedShapes(theAlgo, theOldShape, theShapeTypeToExplore, theShapesToExclude);
 }
 
-void ModelAPI_ResultBody::loadFirstLevel(GeomShapePtr theShape,
-                                         const std::string& theName)
-{
-  myBuilder->loadFirstLevel(theShape, theName);
-}
-
 // LCOV_EXCL_START
 bool ModelAPI_ResultBody::isConnectedTopology()
 {
index c585280cc4c4c0e9ca30c32921aad5d5af7c6c74..7b9b3177871ffff939626c90df9b938758e107bf 100644 (file)
@@ -162,7 +162,7 @@ public:
 
   /// load shapes of the first level (to be used during shape import)
   MODELAPI_EXPORT virtual void loadFirstLevel(GeomShapePtr theShape,
-                                              const std::string& theName);
+                                              const std::string& theName) = 0;
 
   /// Returns true is the topology is connected.
   MODELAPI_EXPORT virtual bool isConnectedTopology() = 0;
index 475fdbae834ef5d578a50c2407ce91628d8e4fa5..b5afae0d06a957c5ac9253c7661b55dae2132c0f 100644 (file)
@@ -31,3 +31,4 @@ from FeaturesAPI import addFillet, addChamfer
 from FeaturesAPI import addFusionFaces
 from FeaturesAPI import measureLength, measureDistance, measureRadius, measureAngle
 from FeaturesAPI import addRemoveResults
+from FeaturesAPI import addCopy