Salome HOME
26451: Import Result with Groups
authorjgv <jgv@opencascade.com>
Fri, 10 Dec 2021 17:26:05 +0000 (20:26 +0300)
committervsr <vsr@opencascade.com>
Mon, 20 Dec 2021 10:26:00 +0000 (13:26 +0300)
13 files changed:
src/ExchangePlugin/ExchangePlugin_ExportFeature.cpp
src/FeaturesAPI/FeaturesAPI_ImportResult.cpp
src/FeaturesPlugin/FeaturesPlugin_ImportResult.cpp
src/FeaturesPlugin/FeaturesPlugin_ImportResult.h
src/FeaturesPlugin/Test/TestImportResultWithGroups1.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestImportResultWithGroups2.py [new file with mode: 0644]
src/FeaturesPlugin/Test/TestImportResultWithGroups3.py [new file with mode: 0644]
src/FeaturesPlugin/doc/importResultFeature.rst
src/FeaturesPlugin/tests.set
src/ModelAPI/ModelAPI_Tools.cpp
src/ModelAPI/ModelAPI_Tools.h
src/PartSet/PartSet_Module.cpp
src/PartSet/PartSet_TreeNodes.cpp

index 32d52205c1f99273a1cf7635d836c9a7c5d75fe5..fcb3c56e126dbd2e95a5ec6970b289a1bc13b6d2 100644 (file)
@@ -265,93 +265,6 @@ static std::string valToString(const ModelAPI_AttributeTables::Value& theVal,
   return aStr.str();
 }
 
-/// Returns true if something in selection is presented in the results list
-static bool isInResults(AttributeSelectionListPtr theSelection,
-                        const std::list<ResultPtr>& theResults,
-                        std::set<ResultPtr>& theCashedResults)
-{
-  // collect all results into a cashed set
-  if (theCashedResults.empty()) {
-    std::list<ResultPtr>::const_iterator aRes = theResults.cbegin();
-    for(; aRes != theResults.cend(); aRes++) {
-      if (theCashedResults.count(*aRes))
-        continue;
-      else
-        theCashedResults.insert(*aRes);
-      if ((*aRes)->groupName() == ModelAPI_ResultBody::group()) {
-        ResultBodyPtr aResBody = std::dynamic_pointer_cast<ModelAPI_ResultBody>(*aRes);
-        std::list<ResultPtr> aResults;
-        ModelAPI_Tools::allSubs(aResBody, aResults, false);
-        for(std::list<ResultPtr>::iterator aR = aResults.begin(); aR != aResults.end(); aR++) {
-          theCashedResults.insert(std::dynamic_pointer_cast<ModelAPI_ResultBody>(*aR));
-        }
-      } else if ((*aRes)->groupName() == ModelAPI_ResultPart::group()) { // all results of the part
-        ResultPartPtr aResPart = std::dynamic_pointer_cast<ModelAPI_ResultPart>(*aRes);
-        DocumentPtr aPartDoc = aResPart->partDoc();
-        if (!aPartDoc.get() || !aPartDoc->isOpened()) { // document is not accessible
-          return false;
-        }
-        int aBodyCount = aPartDoc->size(ModelAPI_ResultBody::group());
-        for (int aBodyIndex = 0; aBodyIndex < aBodyCount; ++aBodyIndex) {
-          ResultBodyPtr aResBody =
-            std::dynamic_pointer_cast<ModelAPI_ResultBody>(
-              aPartDoc->object(ModelAPI_ResultBody::group(), aBodyIndex));
-          if (aResBody.get()) {
-            theCashedResults.insert(aResBody);
-            std::list<ResultPtr> aResults;
-            ModelAPI_Tools::allSubs(aResBody, aResults, false);
-            for(std::list<ResultPtr>::iterator aR = aResults.begin(); aR != aResults.end(); aR++) {
-              theCashedResults.insert(std::dynamic_pointer_cast<ModelAPI_ResultBody>(*aR));
-            }
-          }
-        }
-      }
-    }
-  }
-  // if context is in results, return true
-  for(int a = 0; a < theSelection->size(); a++) {
-    AttributeSelectionPtr anAttr = theSelection->value(a);
-    ResultPtr aContext = anAttr->context();
-    // check is it group selected for groups BOP
-    if (aContext.get() && aContext->groupName() == ModelAPI_ResultGroup::group()) {
-      // it is impossible by used results check which result is used in this group result,
-      // so check the results shapes is it in results of this document or not
-      FeaturePtr aSelFeature =
-        std::dynamic_pointer_cast<ModelAPI_Feature>(theSelection->owner());
-      if (!aSelFeature.get() || aSelFeature->results().empty())
-        continue;
-      GeomShapePtr aGroupResShape = aSelFeature->firstResult()->shape();
-
-      std::set<ResultPtr>::iterator allResultsIter = theCashedResults.begin();
-      for(; allResultsIter != theCashedResults.end(); allResultsIter++) {
-        GeomShapePtr aResultShape = (*allResultsIter)->shape();
-
-        GeomAPI_Shape::ShapeType aType =
-          GeomAPI_Shape::shapeTypeByStr(theSelection->selectionType());
-        GeomAPI_ShapeExplorer aGroupResExp(aGroupResShape, aType);
-        for(; aGroupResExp.more(); aGroupResExp.next()) {
-          if (aResultShape->isSubShape(aGroupResExp.current(), false))
-            return true; // at least one shape of the group is in the used results
-        }
-      }
-    }
-    ResultBodyPtr aSelected = std::dynamic_pointer_cast<ModelAPI_ResultBody>(anAttr->context());
-    if (!aSelected.get()) { // try to get selected feature and all its results
-      FeaturePtr aContextFeature = anAttr->contextFeature();
-      if (aContextFeature.get() && !aContextFeature->results().empty()) {
-        const std::list<ResultPtr>& allResluts = aContextFeature->results();
-        std::list<ResultPtr>::const_iterator aResIter = allResluts.cbegin();
-        for(; aResIter != allResluts.cend(); aResIter++) {
-          if (aResIter->get() && theCashedResults.count(*aResIter))
-            return true;
-        }
-      }
-    } else if (aSelected.get() && theCashedResults.count(aSelected))
-      return true;
-  }
-  return false;
-}
-
 void ExchangePlugin_ExportFeature::exportSTL(const std::string& theFileName)
 {
   // Get shape.
@@ -504,7 +417,9 @@ void ExchangePlugin_ExportFeature::exportXAO(const std::string& theFileName)
 
       AttributeSelectionListPtr aSelectionList =
           aGroupFeature->selectionList("group_list");
-      if (!isInResults(aSelectionList, aResults, allResultsCashed))// skip group not used in result
+      if (!ModelAPI_Tools::isInResults(aSelectionList,
+                                       aResults,
+                                       allResultsCashed))// skip group not used in result
         continue;
 
       // conversion of dimension
@@ -558,7 +473,8 @@ void ExchangePlugin_ExportFeature::exportXAO(const std::string& theFileName)
       std::string aSelectionType = aSelectionList->selectionType();
       bool isWholePart = aSelectionType == "part";
       // skip field not used in results
-      if (!isWholePart && !isInResults(aSelectionList, aResults, allResultsCashed))
+      if (!isWholePart &&
+          !ModelAPI_Tools::isInResults(aSelectionList, aResults, allResultsCashed))
         continue;
 
       // conversion of dimension
index 22e93b7182bfcb875679e4b5ae22faff8cfa0634..fff4a917bde19106e932e70f838fea50b86e0357 100644 (file)
@@ -57,6 +57,14 @@ void FeaturesAPI_ImportResult::dump(ModelHighAPI_Dumper& theDumper) const
   AttributeSelectionListPtr anObjects =
     aBase->selectionList(FeaturesPlugin_ImportResult::OBJECTS());
 
+  CompositeFeaturePtr aCompositeFeature =
+      std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(aBase);
+  int aNbOfSubs = aCompositeFeature->numberOfSubs();
+  for (int anIndex = 0; anIndex < aNbOfSubs; anIndex++) {
+    FeaturePtr aSubFeature = aCompositeFeature->subFeature(anIndex);
+    theDumper.name(aSubFeature, false, false, true); //mark as not to dump
+  }
+
   theDumper << aBase << " = model.addImportResult("
             << aDocName << ", " << anObjects << ")" << std::endl;
 }
index a49ef0fb480529c1edb95e640f8386493804400f..9e6a14c4844df1f522e4fcaac1b33b5aa1194a83 100644 (file)
 #include "FeaturesPlugin_ImportResult.h"
 
 #include <ModelAPI_AttributeSelectionList.h>
+#include <ModelAPI_AttributeRefList.h>
 #include <ModelAPI_ResultBody.h>
+#include <ModelAPI_ResultGroup.h>
 #include <ModelAPI_Session.h>
 #include <ModelAPI_ResultPart.h>
+#include <ModelAPI_Tools.h>
 #include <Events_InfoMessage.h>
+#include <GeomAPI_ShapeExplorer.h>
 
 void FeaturesPlugin_ImportResult::initAttributes()
 {
   data()->addAttribute(OBJECTS(), ModelAPI_AttributeSelectionList::typeId());
+
+  AttributePtr aFeaturesAttribute =
+    data()->addAttribute(FEATURES_ID(),
+                         ModelAPI_AttributeRefList::typeId());
+  aFeaturesAttribute->setIsArgument(false);
+
+  ModelAPI_Session::get()->validators()->registerNotObligatory(
+      getKind(), FEATURES_ID());
 }
 
 void FeaturesPlugin_ImportResult::execute()
 {
+  // Process groups/fields
+  std::shared_ptr<ModelAPI_AttributeRefList> aRefListOfGroups =
+      std::dynamic_pointer_cast<ModelAPI_AttributeRefList>(data()->attribute(FEATURES_ID()));
+
+  // Remove previous groups/fields stored in RefList
+  //std::list<ObjectPtr> anGroupList = aRefListOfGroups->list();
+  //std::list<ObjectPtr>::iterator anGroupIt = anGroupList.begin();
+  const std::list<ObjectPtr>& anGroupList = aRefListOfGroups->list();
+  std::list<ObjectPtr>::const_iterator anGroupIt = anGroupList.begin();
+  for (; anGroupIt != anGroupList.end(); ++anGroupIt) {
+    std::shared_ptr<ModelAPI_Feature> aFeature = ModelAPI_Feature::feature(*anGroupIt);
+    if (aFeature)
+      document()->removeFeature(aFeature);
+  }
+
+  aRefListOfGroups->clear();
+
+  std::set<ResultPtr> aGlobalResultsCashed; // cash to speed up searching in all results selected
+  std::list<ResultGroupPtr> aGroups;
+
+  std::list<ResultPtr> aResults;
+
   AttributeSelectionListPtr aList = selectionList(OBJECTS());
   int aResultIndex = 0;
-  for (int aSelIndex = 0; aSelIndex < aList->size(); aSelIndex++) {
+  for (int aSelIndex = 0; aSelIndex < aList->size(); aSelIndex++)
+  {
     AttributeSelectionPtr aSel = aList->value(aSelIndex);
+
     ResultPtr aContext = aSel->context();
-    if (!aContext.get())
-      continue;
-    GeomShapePtr aShape = aContext->shape();
+    if (aContext.get())
+      aResults.push_back (aContext);
+    else
+    {
+      FeaturePtr aFeature = aSel->contextFeature();
+      if (aFeature.get())
+      {
+        const std::list<ResultPtr>& aResList = aFeature->results();
+        aResults.assign (aResList.begin(), aResList.end());
+      }
+    }
+  }
+
+  std::list<ResultPtr>::iterator aResIter = aResults.begin();
+  for(; aResIter != aResults.end(); aResIter++)
+  {
+    GeomShapePtr aShape = (*aResIter)->shape();
     if (!aShape.get() || aShape->isNull())
       continue;
+
     std::shared_ptr<ModelAPI_ResultBody> aResultBody = document()->createBody(data(), aResultIndex);
     aResultBody->store(aShape);
     aResultBody->loadFirstLevel(aShape, "ImportResult");
     setResult(aResultBody, aResultIndex++);
+
+    std::set<ResultPtr> allResultsCashed; // cash to speed up searching in all results selected
+    std::list<ResultPtr> aLocalResults;
+    aLocalResults.push_back (*aResIter);
+
+    std::list<DocumentPtr> aDocuments; /// documents of Parts
+    aDocuments.push_back ((*aResIter)->document());
+    std::list<DocumentPtr>::iterator aDoc = aDocuments.begin();
+    for(; aDoc != aDocuments.end(); aDoc++)
+    {
+      // groups
+      int aGroupCount = (*aDoc)->size(ModelAPI_ResultGroup::group());
+      for (int aGroupIndex = 0; aGroupIndex < aGroupCount; ++aGroupIndex)
+      {
+        ResultGroupPtr aResultGroup =
+          std::dynamic_pointer_cast<ModelAPI_ResultGroup>((*aDoc)->object(ModelAPI_ResultGroup::group(), aGroupIndex));
+
+        if (!aResultGroup.get() || !aResultGroup->shape().get())
+          continue;
+
+        FeaturePtr aGroupFeature = (*aDoc)->feature(aResultGroup);
+
+        AttributeSelectionListPtr aSelectionList =
+          aGroupFeature->selectionList("group_list");
+
+        if (!ModelAPI_Tools::isInResults(aSelectionList,
+                                         aLocalResults,
+                                         allResultsCashed))// skip group not used in result
+          continue;
+
+        aGlobalResultsCashed.insert (allResultsCashed.begin(), allResultsCashed.end());
+
+        //Check: may be this group already exists in the list
+        bool anIsFound = false;
+        std::list<ResultGroupPtr>::iterator anIter = aGroups.begin();
+        for (; anIter != aGroups.end(); anIter++)
+        {
+          if (*anIter == aResultGroup)
+          {
+            anIsFound = true;
+            break;
+          }
+        }
+        if (!anIsFound)
+          aGroups.push_back (aResultGroup);
+      }
+    }
+  }
+
+  std::list<ResultGroupPtr>::iterator anIter = aGroups.begin();
+  for (; anIter != aGroups.end(); anIter++)
+  {
+    DocumentPtr aDoc = (*anIter)->document();
+
+    FeaturePtr aGroupFeature = aDoc->feature(*anIter);
+
+    AttributeSelectionListPtr aSelectionList =
+      aGroupFeature->selectionList("group_list");
+
+    std::shared_ptr<ModelAPI_Feature> aNewGroupFeature = addFeature("Group");
+    aNewGroupFeature->data()->setName(aGroupFeature->name());
+
+    AttributeSelectionListPtr aNewSelectionList = aNewGroupFeature->selectionList("group_list");
+    aNewSelectionList->setSelectionType (aSelectionList->selectionType());
+    GeomAPI_Shape::ShapeType aTypeOfShape = GeomAPI_Shape::shapeTypeByStr (aSelectionList->selectionType());
+
+    for (int aLocalSelIndex = 0; aLocalSelIndex < aSelectionList->size(); aLocalSelIndex++) {
+
+      AttributeSelectionPtr aLocalSel = aSelectionList->value(aLocalSelIndex);
+      ResultPtr aLocalContext = aLocalSel->context();
+      ResultGroupPtr aLocalGroup = std::dynamic_pointer_cast<ModelAPI_ResultGroup> (aLocalContext);
+      if (aLocalGroup.get())
+      {
+        GeomShapePtr aLocalShape = aGroupFeature->firstResult()->shape();
+        GeomAPI_ShapeExplorer anExplo (aLocalShape, aTypeOfShape);
+        for (; anExplo.more(); anExplo.next())
+        {
+          GeomShapePtr anExploredShape = anExplo.current();
+          std::set<ResultPtr>::iterator aResultIter = aGlobalResultsCashed.begin();
+          for (; aResultIter != aGlobalResultsCashed.end(); aResultIter++)
+          {
+            GeomShapePtr aCashedShape = (*aResultIter)->shape();
+            if (aCashedShape->isSubShape(anExploredShape))
+              aNewSelectionList->append((*aResultIter), anExploredShape);
+          }
+        }
+        break;
+      }
+      else
+      {
+        GeomShapePtr aLocalShape = aLocalSel->value();
+
+        if (aLocalContext.get() && aGlobalResultsCashed.count(aLocalContext))
+          aNewSelectionList->append(aLocalContext, aLocalShape);
+      }
+    }
   }
+
   removeResults(aResultIndex);
 }
 
@@ -115,3 +263,69 @@ bool FeaturesPlugin_ValidatorImportResults::isValid(const AttributePtr& theAttri
   }
   return true;
 }
+
+//============================================================================
+std::shared_ptr<ModelAPI_Feature> FeaturesPlugin_ImportResult::addFeature(
+    std::string theID)
+{
+  std::shared_ptr<ModelAPI_Feature> aNew = document()->addFeature(theID, false);
+  if (aNew)
+    data()->reflist(FEATURES_ID())->append(aNew);
+  // set as current also after it becomes sub to set correctly enabled for other subs
+  //document()->setCurrentFeature(aNew, false);
+  return aNew;
+}
+
+//=================================================================================================
+int FeaturesPlugin_ImportResult::numberOfSubs(bool /*forTree*/) const
+{
+  return data()->reflist(FEATURES_ID())->size(true);
+}
+
+//=================================================================================================
+std::shared_ptr<ModelAPI_Feature> FeaturesPlugin_ImportResult::subFeature(const int theIndex,
+                                                                          bool /*forTree*/)
+{
+  ObjectPtr anObj = data()->reflist(FEATURES_ID())->object(theIndex, false);
+  FeaturePtr aRes = std::dynamic_pointer_cast<ModelAPI_Feature>(anObj);
+  return aRes;
+}
+
+//=================================================================================================
+int FeaturesPlugin_ImportResult::subFeatureId(const int theIndex) const
+{
+  std::shared_ptr<ModelAPI_AttributeRefList> aRefList = std::dynamic_pointer_cast<
+      ModelAPI_AttributeRefList>(data()->attribute(FEATURES_ID()));
+  std::list<ObjectPtr> aFeatures = aRefList->list();
+  std::list<ObjectPtr>::const_iterator anIt = aFeatures.begin();
+  int aResultIndex = 1; // number of the counted (created) features, started from 1
+  int aFeatureIndex = -1; // number of the not-empty features in the list
+  for (; anIt != aFeatures.end(); anIt++) {
+    if (anIt->get())
+      aFeatureIndex++;
+    if (aFeatureIndex == theIndex)
+      break;
+    aResultIndex++;
+  }
+  return aResultIndex;
+}
+
+//=================================================================================================
+bool FeaturesPlugin_ImportResult::isSub(ObjectPtr theObject) const
+{
+  // check is this feature of result
+  FeaturePtr aFeature = ModelAPI_Feature::feature(theObject);
+  if (aFeature)
+    return data()->reflist(FEATURES_ID())->isInList(aFeature);
+  return false;
+}
+
+//=================================================================================================
+void FeaturesPlugin_ImportResult::removeFeature(
+    std::shared_ptr<ModelAPI_Feature> theFeature)
+{
+  if (!data()->isValid())
+    return;
+  AttributeRefListPtr aList = reflist(FEATURES_ID());
+  aList->remove(theFeature);
+}
index 9b946ab18fb3982dd427841494cd3e260472c93a..69aef3aa2a5c1639591084d9493553b37c674745 100644 (file)
@@ -22,7 +22,7 @@
 
 #include "FeaturesPlugin.h"
 
-#include <ModelAPI_Feature.h>
+#include <ModelAPI_CompositeFeature.h>
 #include <ModelAPI_AttributeValidator.h>
 
 /// \class FeaturesPlugin_ImportResult
@@ -30,7 +30,7 @@
 /// \brief The Import Result feature allows the user to import one or several results
 ///        from another Part.
 
-class FeaturesPlugin_ImportResult : public ModelAPI_Feature
+class FeaturesPlugin_ImportResult : public ModelAPI_CompositeFeature
 {
 public:
   /// Feature kind.
@@ -39,6 +39,12 @@ public:
     static const std::string MY_ID("ImportResult");
     return MY_ID;
   }
+  /// All features (list of references)
+  inline static const std::string& FEATURES_ID()
+  {
+    static const std::string MY_FEATURES_ID("Features");
+    return MY_FEATURES_ID;
+  }
 
   /// \return the kind of a feature.
   FEATURESPLUGIN_EXPORT virtual const std::string& getKind()
@@ -60,6 +66,26 @@ public:
   /// Request for initialization of data model of the feature: adding all attributes.
   FEATURESPLUGIN_EXPORT virtual void initAttributes();
 
+  /// Appends a feature
+  FEATURESPLUGIN_EXPORT virtual std::shared_ptr<ModelAPI_Feature> addFeature(std::string theID);
+
+  /// \return the number of sub-elements.
+  FEATURESPLUGIN_EXPORT virtual int numberOfSubs(bool forTree = false) const;
+
+  /// \return the sub-feature by zero-base index.
+  FEATURESPLUGIN_EXPORT virtual
+    std::shared_ptr<ModelAPI_Feature> subFeature(const int theIndex, bool forTree = false);
+
+  /// \return the sub-feature unique identifier in this composite feature by zero-base index.
+  FEATURESPLUGIN_EXPORT virtual int subFeatureId(const int theIndex) const;
+
+  /// \return true if feature or result belong to this composite feature as subs.
+  FEATURESPLUGIN_EXPORT virtual bool isSub(ObjectPtr theObject) const;
+
+  /// This method to inform that sub-feature is removed and must be removed from the internal data
+  /// structures of the owner (the remove from the document will be done outside just after).
+  FEATURESPLUGIN_EXPORT virtual void removeFeature(std::shared_ptr<ModelAPI_Feature> theFeature);
+
   /// Use plugin manager for features creation.
   FeaturesPlugin_ImportResult() {}
 };
diff --git a/src/FeaturesPlugin/Test/TestImportResultWithGroups1.py b/src/FeaturesPlugin/Test/TestImportResultWithGroups1.py
new file mode 100644 (file)
index 0000000..29c7ec9
--- /dev/null
@@ -0,0 +1,78 @@
+# Copyright (C) 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
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Box_1 = model.addBox(Part_1_doc, 10, 10, 10)
+### Create Groups
+Group_1 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Box_1_1/Left"), model.selection("FACE", "Box_1_1/Top")])
+Group_2 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Box_1_1/Front"), model.selection("FACE", "Box_1_1/Top")])
+GroupAddition_1 = model.addGroupAddition(Part_1_doc, [model.selection("COMPOUND", "Group_1"), model.selection("COMPOUND", "Group_2")])
+
+model.do()
+
+### Create Part 2
+Part_2 = model.addPart(partSet)
+Part_2_doc = Part_2.document()
+ImportResult_1 = model.addImportResult(Part_2_doc, [model.selection("SOLID", "Part_1/Box_1_1")])
+
+model.end()
+
+from GeomAPI import *
+from ModelAPI import *
+import math
+
+TOLERANCE = 1.e-7
+
+assert(ImportResult_1.feature().error() == "")
+model.testNbResults(ImportResult_1, 1)
+model.testNbSubResults(ImportResult_1, [0])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.SOLID, [1])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.FACE, [6])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.EDGE, [24])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.VERTEX, [48])
+model.testResultsVolumes(ImportResult_1, [1000])
+model.testResultsAreas(ImportResult_1, [600])
+
+# check groups are the same in both parts
+assert(Part_1_doc.size("Groups") == Part_2_doc.size("Groups"))
+for ind in range(0, Part_1_doc.size("Groups")):
+    res1 = objectToResult(Part_1_doc.object("Groups", ind))
+    res2 = objectToResult(Part_2_doc.object("Groups", ind))
+    assert(res1 is not None)
+    assert(res2 is not None)
+    res1It = GeomAPI_ShapeExplorer(res1.shape(), GeomAPI_Shape.FACE)
+    res2It = GeomAPI_ShapeExplorer(res2.shape(), GeomAPI_Shape.FACE)
+    while res1It.more() and res2It.more():
+        shape1 = res1It.current()
+        shape2 = res2It.current()
+        assert(shape1.shapeType() == shape2.shapeType())
+        res1It.next()
+        res2It.next()
+    p1 = res1.shape().middlePoint()
+    p2 = res2.shape().middlePoint()
+    assert(math.fabs(p1.x() - p2.x()) <= TOLERANCE and math.fabs(p1.y() - p2.y()) <= TOLERANCE and math.fabs(p1.z() - p2.z()) <= TOLERANCE), "({}, {}, {}) != ({}, {}, {})".format(p1.x(), p1.y(), p1.z(), p2.x(), p2.y(), p2.z())
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestImportResultWithGroups2.py b/src/FeaturesPlugin/Test/TestImportResultWithGroups2.py
new file mode 100644 (file)
index 0000000..38f93ae
--- /dev/null
@@ -0,0 +1,85 @@
+# Copyright (C) 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
+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, "Faces", [model.selection("FACE", "Box_1_1/Left"), model.selection("FACE", "Box_1_1/Top")])
+
+Cylinder_1 = model.addCylinder(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), model.selection("EDGE", "PartSet/OZ"), 5, 10)
+Group_2 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Cylinder_1_1/Face_1"), model.selection("FACE", "Box_1_1/Front")])
+Group_3 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Cylinder_1_1/Face_1")])
+GroupAddition_1 = model.addGroupAddition(Part_1_doc, [model.selection("COMPOUND", "Group_1"), model.selection("COMPOUND", "Group_2")])
+GroupSubstraction_1 = model.addGroupSubstraction(Part_1_doc, [model.selection("COMPOUND", "GroupAddition_1")], [model.selection("COMPOUND", "Group_3")])
+
+model.do()
+
+### Create Part 2
+Part_2 = model.addPart(partSet)
+Part_2_doc = Part_2.document()
+
+### Create ImportResult
+ImportResult_1 = model.addImportResult(Part_2_doc, [model.selection("SOLID", "Part_1/Box_1_1"), model.selection("SOLID", "Part_1/Cylinder_1_1")])
+
+model.end()
+
+from GeomAPI import *
+from ModelAPI import *
+import math
+
+TOLERANCE = 1.e-7
+
+assert(ImportResult_1.feature().error() == "")
+model.testNbResults(ImportResult_1, 2)
+model.testNbSubResults(ImportResult_1, [0, 0])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.SOLID, [1, 1])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.FACE, [6, 3])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.EDGE, [24, 6])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.VERTEX, [48, 12])
+model.testResultsVolumes(ImportResult_1, [1000, 785.3981634])
+model.testResultsAreas(ImportResult_1, [600, 471.238898])
+
+# check groups are the same in both parts
+assert(Part_1_doc.size("Groups") == Part_2_doc.size("Groups"))
+REFIND = [0, 1, 3, 4, 2]
+for ind in range(0, Part_1_doc.size("Groups")):
+    res1 = objectToResult(Part_1_doc.object("Groups", REFIND[ind]))
+    res2 = objectToResult(Part_2_doc.object("Groups", ind))
+    assert(res1 is not None)
+    assert(res2 is not None)
+    res1It = GeomAPI_ShapeExplorer(res1.shape(), GeomAPI_Shape.FACE)
+    res2It = GeomAPI_ShapeExplorer(res2.shape(), GeomAPI_Shape.FACE)
+    while res1It.more() and res2It.more():
+        shape1 = res1It.current()
+        shape2 = res2It.current()
+        assert(shape1.shapeType() == shape2.shapeType())
+        res1It.next()
+        res2It.next()
+    p1 = res1.shape().middlePoint()
+    p2 = res2.shape().middlePoint()
+    assert(math.fabs(p1.x() - p2.x()) <= TOLERANCE and math.fabs(p1.y() - p2.y()) <= TOLERANCE and math.fabs(p1.z() - p2.z()) <= TOLERANCE), "({}, {}, {}) != ({}, {}, {})".format(p1.x(), p1.y(), p1.z(), p2.x(), p2.y(), p2.z())
+
+assert(model.checkPythonDump())
diff --git a/src/FeaturesPlugin/Test/TestImportResultWithGroups3.py b/src/FeaturesPlugin/Test/TestImportResultWithGroups3.py
new file mode 100644 (file)
index 0000000..8702339
--- /dev/null
@@ -0,0 +1,87 @@
+# Copyright (C) 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
+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, "Faces", [model.selection("FACE", "Box_1_1/Left"), model.selection("FACE", "Box_1_1/Top")])
+
+Cylinder_1 = model.addCylinder(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), model.selection("EDGE", "PartSet/OZ"), 5, 10)
+Group_2 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Cylinder_1_1/Face_1"), model.selection("FACE", "Box_1_1/Front")])
+Group_3 = model.addGroup(Part_1_doc, "Faces", [model.selection("FACE", "Cylinder_1_1/Face_1")])
+GroupAddition_1 = model.addGroupAddition(Part_1_doc, [model.selection("COMPOUND", "Group_1"), model.selection("COMPOUND", "Group_2")])
+GroupSubstraction_1 = model.addGroupSubstraction(Part_1_doc, [model.selection("COMPOUND", "GroupAddition_1")], [model.selection("COMPOUND", "Group_3")])
+
+model.do()
+
+### Create Part 2
+Part_2 = model.addPart(partSet)
+Part_2_doc = Part_2.document()
+
+### Create ImportResult
+ImportResult_1 = model.addImportResult(Part_2_doc, [model.selection("SOLID", "Part_1/Box_1_1")])
+
+model.end()
+
+from GeomAPI import *
+from ModelAPI import *
+import math
+
+TOLERANCE = 1.e-7
+
+assert(ImportResult_1.feature().error() == "")
+model.testNbResults(ImportResult_1, 1)
+model.testNbSubResults(ImportResult_1, [0])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.SOLID, [1])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.FACE, [6])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.EDGE, [24])
+model.testNbSubShapes(ImportResult_1, GeomAPI_Shape.VERTEX, [48])
+model.testResultsVolumes(ImportResult_1, [1000])
+model.testResultsAreas(ImportResult_1, [600])
+
+# check imported groups are correct
+assert(Part_1_doc.size("Groups") == 5)
+assert(Part_2_doc.size("Groups") == 4)
+REFERENCE = [(2, 5.0, 5.0, 5.0),
+             (1, 10.0, 5.0, 5.0),
+             (3, 5.0, 5.0, 5.0),
+             (3, 5.0, 5.0, 5.0)]
+for ind in range(0, Part_2_doc.size("Groups")):
+    res = objectToResult(Part_2_doc.object("Groups", ind))
+    assert(res is not None)
+    nbShapes = 0
+    it = GeomAPI_ShapeExplorer(res.shape(), GeomAPI_Shape.FACE)
+    while it.more():
+        nbShapes += 1
+        it.next()
+    assert(nbShapes == REFERENCE[ind][0])
+    p = res.shape().middlePoint()
+    x = REFERENCE[ind][1]
+    y = REFERENCE[ind][2]
+    z = REFERENCE[ind][3]
+    assert(math.fabs(p.x() - x) <= TOLERANCE and math.fabs(p.y() - y) <= TOLERANCE and math.fabs(p.z() - z) <= TOLERANCE), "({}, {}, {}) != ({}, {}, {})".format(p.x(), p.y(), p.z(), x, y, z)
+
+assert(model.checkPythonDump())
index b2349e635190fd0086b64a1cd9414d57efe52848..a25cb09f31a40181fd1efc8404d4f4f714cfe219 100644 (file)
@@ -8,6 +8,9 @@ changed, the part and part result that contains the copy-results will be updated
 copy-shape, so, even the document was opened and the source-part was not activated (loaded), the part with copy-feature works well
 with this result-shape.
 
+If the source-part contains one or several groups, which refer to sub-shapes of source results, then these groups are mutually imported
+as a sub-features of the Import Result feature. The copied groups contain only the elements referring to the imported results.
+
 It may be necessary for the user to load the other parts before using this feature otherwise the content of the **Results** folders will be empty.
 
 To create a Copy in the active part:
@@ -42,7 +45,7 @@ current Part where the import is done. Only results from the **Results** folder
 Result
 """"""
 
-The Result of the operation will be copy of one or several results selected in another part located in the same place:
+The Result of the operation will be copy of one or several results selected and groups referring to these results in another part located in the same place:
 
 .. figure:: images/CreatedImportResult.png
    :align: center
index 7e5e58612fc4a1a9278d70448ccc74d38c87c133..b1d33e64d97ed043c29d36ffa9ac34b6a2744e51 100644 (file)
@@ -393,6 +393,9 @@ SET(TEST_NAMES
                TestCopySubShapes.py
                TestCopyWholeFeature.py
                TestImportResult.py
+               TestImportResultWithGroups1.py
+               TestImportResultWithGroups2.py
+               TestImportResultWithGroups3.py
                TestDefeaturing_ErrorMsg.py
                TestDefeaturing_OnSolid1.py
                TestDefeaturing_OnSolid2.py
index 1eeea82c7b836aa876fbef89351dfe38bf4bdc7e..9554d1e1068b0ac9021f8c493b059c8408c2b79b 100644 (file)
@@ -42,6 +42,7 @@
 
 #include <GeomAPI_ShapeHierarchy.h>
 #include <GeomAPI_ShapeIterator.h>
+#include <GeomAPI_ShapeExplorer.h>
 
 #include <algorithm>
 #include <iostream>
@@ -234,6 +235,95 @@ bool findVariable(FeaturePtr theSearcher, const std::wstring& theName, double& o
   return false;
 }
 
+static void cacheSubresults(const ResultBodyPtr& theTopLevelResult,
+                            std::set<ResultPtr>& theCashedResults)
+{
+  std::list<ResultPtr> aResults;
+  ModelAPI_Tools::allSubs(theTopLevelResult, aResults, false);
+  for (std::list<ResultPtr>::iterator aR = aResults.begin(); aR != aResults.end(); ++aR) {
+    theCashedResults.insert(*aR);
+  }
+}
+
+bool isInResults(AttributeSelectionListPtr theSelection,
+                 const std::list<ResultPtr>& theResults,
+                 std::set<ResultPtr>& theCashedResults)
+{
+  // collect all results into a cashed set
+  if (theCashedResults.empty()) {
+    std::list<ResultPtr>::const_iterator aRes = theResults.cbegin();
+    for(; aRes != theResults.cend(); aRes++) {
+      if (theCashedResults.count(*aRes))
+        continue;
+      else
+        theCashedResults.insert(*aRes);
+
+      if ((*aRes)->groupName() == ModelAPI_ResultBody::group()) {
+        ResultBodyPtr aResBody = std::dynamic_pointer_cast<ModelAPI_ResultBody>(*aRes);
+        cacheSubresults(aResBody, theCashedResults);
+      } else if ((*aRes)->groupName() == ModelAPI_ResultPart::group()) { // all results of the part
+        ResultPartPtr aResPart = std::dynamic_pointer_cast<ModelAPI_ResultPart>(*aRes);
+        DocumentPtr aPartDoc = aResPart->partDoc();
+        if (!aPartDoc.get() || !aPartDoc->isOpened()) { // document is not accessible
+          return false;
+        }
+        int aBodyCount = aPartDoc->size(ModelAPI_ResultBody::group());
+        for (int aBodyIndex = 0; aBodyIndex < aBodyCount; ++aBodyIndex) {
+          ResultBodyPtr aResBody =
+            std::dynamic_pointer_cast<ModelAPI_ResultBody>(
+              aPartDoc->object(ModelAPI_ResultBody::group(), aBodyIndex));
+          if (aResBody.get()) {
+            theCashedResults.insert(aResBody);
+            cacheSubresults(aResBody, theCashedResults);
+          }
+        }
+      }
+    }
+  }
+  // if context is in results, return true
+  for(int a = 0; a < theSelection->size(); a++) {
+    AttributeSelectionPtr anAttr = theSelection->value(a);
+    ResultPtr aContext = anAttr->context();
+    // check is it group selected for groups BOP
+    if (aContext.get() && aContext->groupName() == ModelAPI_ResultGroup::group()) {
+      // it is impossible by used results check which result is used in this group result,
+      // so check the results shapes is it in results of this document or not
+      FeaturePtr aSelFeature =
+        std::dynamic_pointer_cast<ModelAPI_Feature>(theSelection->owner());
+      if (!aSelFeature.get() || aSelFeature->results().empty())
+        continue;
+      GeomShapePtr aGroupResShape = aSelFeature->firstResult()->shape();
+
+      std::set<ResultPtr>::iterator allResultsIter = theCashedResults.begin();
+      for(; allResultsIter != theCashedResults.end(); allResultsIter++) {
+        GeomShapePtr aResultShape = (*allResultsIter)->shape();
+
+        GeomAPI_Shape::ShapeType aType =
+          GeomAPI_Shape::shapeTypeByStr(theSelection->selectionType());
+        GeomAPI_ShapeExplorer aGroupResExp(aGroupResShape, aType);
+        for(; aGroupResExp.more(); aGroupResExp.next()) {
+          if (aResultShape->isSubShape(aGroupResExp.current(), false))
+            return true; // at least one shape of the group is in the used results
+        }
+      }
+    }
+    ResultBodyPtr aSelected = std::dynamic_pointer_cast<ModelAPI_ResultBody>(anAttr->context());
+    if (!aSelected.get()) { // try to get selected feature and all its results
+      FeaturePtr aContextFeature = anAttr->contextFeature();
+      if (aContextFeature.get() && !aContextFeature->results().empty()) {
+        const std::list<ResultPtr>& allResluts = aContextFeature->results();
+        std::list<ResultPtr>::const_iterator aResIter = allResluts.cbegin();
+        for(; aResIter != allResluts.cend(); aResIter++) {
+          if (aResIter->get() && theCashedResults.count(*aResIter))
+            return true;
+        }
+      }
+    } else if (aSelected.get() && theCashedResults.count(aSelected))
+      return true;
+  }
+  return false;
+}
+
 ResultPtr findPartResult(const DocumentPtr& theMain, const DocumentPtr& theSub)
 {
   // to optimize and avoid of crash on partset document close
index bf49fe5dfd510b2154a0a9ef6c5d1997c50b408b..68545c09cf5848a63c625b81d76cdcebd1289ddb 100644 (file)
@@ -21,6 +21,7 @@
 #define ModelAPI_Tools_HeaderFile
 
 #include "ModelAPI.h"
+#include <ModelAPI_AttributeSelectionList.h>
 
 class ModelAPI_CompositeFeature;
 class ModelAPI_Document;
@@ -310,6 +311,12 @@ MODELAPI_EXPORT std::list<std::shared_ptr<ModelAPI_Feature> > referencedFeatures
   std::shared_ptr<ModelAPI_Result> theTarget, const std::string& theFeatureKind,
   const bool theSortResults);
 
+/*!
+ * Returns true if something in selection is presented in the results list
+ */
+MODELAPI_EXPORT bool isInResults(AttributeSelectionListPtr theSelection,
+                                 const std::list<ResultPtr>& theResults,
+                                 std::set<ResultPtr>& theCashedResults);
 
 /*! Returns a container with the current color value.
 *   These are tree int values for RGB definition.
index a83da4472b9eae3d7420cf8136e616cd8f9a6e7b..8add0208abd914e959b64cd0e701d210b09510c4 100644 (file)
@@ -1610,11 +1610,29 @@ void PartSet_Module::addObjectBrowserMenu(QMenu* theMenu) const
 
       } else if (aObject->document() == aMgr->activeDocument()) {
         if (hasParameter || hasFeature) {
-          myMenuMgr->action("EDIT_CMD")->setEnabled(true);
-          theMenu->addAction(myMenuMgr->action("EDIT_CMD"));
-          if (aCurrentOp && aFeature.get()) {
-            if (aCurrentOp->id().toStdString() == aFeature->getKind())
+
+          // disable Edit menu for groups under ImportResult feature
+          bool isEnabled = true;
+          if (aFeature.get() && aFeature->getKind() == "Group")
+          {
+            std::shared_ptr<ModelAPI_CompositeFeature> anOwner =
+              ModelAPI_Tools::compositeOwner (aFeature);
+
+            if (anOwner.get() && anOwner->getKind() == "ImportResult")
+            {
               myMenuMgr->action("EDIT_CMD")->setEnabled(false);
+              isEnabled = false;
+            }
+          }
+
+          if (isEnabled)
+          {
+            myMenuMgr->action("EDIT_CMD")->setEnabled(true);
+            theMenu->addAction(myMenuMgr->action("EDIT_CMD"));
+            if (aCurrentOp && aFeature.get()) {
+              if (aCurrentOp->id().toStdString() == aFeature->getKind())
+                myMenuMgr->action("EDIT_CMD")->setEnabled(false);
+            }
           }
         }
       }
index dab8161792d418f6694d4951da9faea858ae1b52..f8166269c8fe4051cc549c9afd937f94fa2dc547 100644 (file)
@@ -158,6 +158,17 @@ Qt::ItemFlags PartSet_ObjectNode::flags(int theColumn) const
   } else {
     DocumentPtr aDoc = myObject->document();
     SessionPtr aSession = ModelAPI_Session::get();
+
+    FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(myObject);
+    if (aFeature.get() && aFeature->getKind() == "Group")
+    {
+      std::shared_ptr<ModelAPI_CompositeFeature> anOwner =
+        ModelAPI_Tools::compositeOwner (aFeature);
+
+      if (anOwner.get() && anOwner->getKind() == "ImportResult")
+        return aDefaultFlag;
+    }
+
     if (aSession->activeDocument() == aDoc)
       return aEditingFlag;
   }