From 5705ca5927c36695f4575b44f8091f1ee23e36aa Mon Sep 17 00:00:00 2001 From: jgv Date: Fri, 10 Dec 2021 20:26:05 +0300 Subject: [PATCH] 26451: Import Result with Groups --- .../ExchangePlugin_ExportFeature.cpp | 94 +------- src/FeaturesAPI/FeaturesAPI_ImportResult.cpp | 8 + .../FeaturesPlugin_ImportResult.cpp | 222 +++++++++++++++++- .../FeaturesPlugin_ImportResult.h | 30 ++- .../Test/TestImportResultWithGroups1.py | 78 ++++++ .../Test/TestImportResultWithGroups2.py | 85 +++++++ .../Test/TestImportResultWithGroups3.py | 87 +++++++ .../doc/importResultFeature.rst | 5 +- src/FeaturesPlugin/tests.set | 3 + src/ModelAPI/ModelAPI_Tools.cpp | 90 +++++++ src/ModelAPI/ModelAPI_Tools.h | 7 + src/PartSet/PartSet_Module.cpp | 26 +- src/PartSet/PartSet_TreeNodes.cpp | 11 + 13 files changed, 646 insertions(+), 100 deletions(-) create mode 100644 src/FeaturesPlugin/Test/TestImportResultWithGroups1.py create mode 100644 src/FeaturesPlugin/Test/TestImportResultWithGroups2.py create mode 100644 src/FeaturesPlugin/Test/TestImportResultWithGroups3.py diff --git a/src/ExchangePlugin/ExchangePlugin_ExportFeature.cpp b/src/ExchangePlugin/ExchangePlugin_ExportFeature.cpp index 32d52205c..fcb3c56e1 100644 --- a/src/ExchangePlugin/ExchangePlugin_ExportFeature.cpp +++ b/src/ExchangePlugin/ExchangePlugin_ExportFeature.cpp @@ -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& theResults, - std::set& theCashedResults) -{ - // collect all results into a cashed set - if (theCashedResults.empty()) { - std::list::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(*aRes); - std::list aResults; - ModelAPI_Tools::allSubs(aResBody, aResults, false); - for(std::list::iterator aR = aResults.begin(); aR != aResults.end(); aR++) { - theCashedResults.insert(std::dynamic_pointer_cast(*aR)); - } - } else if ((*aRes)->groupName() == ModelAPI_ResultPart::group()) { // all results of the part - ResultPartPtr aResPart = std::dynamic_pointer_cast(*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( - aPartDoc->object(ModelAPI_ResultBody::group(), aBodyIndex)); - if (aResBody.get()) { - theCashedResults.insert(aResBody); - std::list aResults; - ModelAPI_Tools::allSubs(aResBody, aResults, false); - for(std::list::iterator aR = aResults.begin(); aR != aResults.end(); aR++) { - theCashedResults.insert(std::dynamic_pointer_cast(*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(theSelection->owner()); - if (!aSelFeature.get() || aSelFeature->results().empty()) - continue; - GeomShapePtr aGroupResShape = aSelFeature->firstResult()->shape(); - - std::set::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(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& allResluts = aContextFeature->results(); - std::list::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 diff --git a/src/FeaturesAPI/FeaturesAPI_ImportResult.cpp b/src/FeaturesAPI/FeaturesAPI_ImportResult.cpp index 22e93b718..fff4a917b 100644 --- a/src/FeaturesAPI/FeaturesAPI_ImportResult.cpp +++ b/src/FeaturesAPI/FeaturesAPI_ImportResult.cpp @@ -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(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; } diff --git a/src/FeaturesPlugin/FeaturesPlugin_ImportResult.cpp b/src/FeaturesPlugin/FeaturesPlugin_ImportResult.cpp index a49ef0fb4..9e6a14c48 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_ImportResult.cpp +++ b/src/FeaturesPlugin/FeaturesPlugin_ImportResult.cpp @@ -20,33 +20,181 @@ #include "FeaturesPlugin_ImportResult.h" #include +#include #include +#include #include #include +#include #include +#include 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 aRefListOfGroups = + std::dynamic_pointer_cast(data()->attribute(FEATURES_ID())); + + // Remove previous groups/fields stored in RefList + //std::list anGroupList = aRefListOfGroups->list(); + //std::list::iterator anGroupIt = anGroupList.begin(); + const std::list& anGroupList = aRefListOfGroups->list(); + std::list::const_iterator anGroupIt = anGroupList.begin(); + for (; anGroupIt != anGroupList.end(); ++anGroupIt) { + std::shared_ptr aFeature = ModelAPI_Feature::feature(*anGroupIt); + if (aFeature) + document()->removeFeature(aFeature); + } + + aRefListOfGroups->clear(); + + std::set aGlobalResultsCashed; // cash to speed up searching in all results selected + std::list aGroups; + + std::list 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& aResList = aFeature->results(); + aResults.assign (aResList.begin(), aResList.end()); + } + } + } + + std::list::iterator aResIter = aResults.begin(); + for(; aResIter != aResults.end(); aResIter++) + { + GeomShapePtr aShape = (*aResIter)->shape(); if (!aShape.get() || aShape->isNull()) continue; + std::shared_ptr aResultBody = document()->createBody(data(), aResultIndex); aResultBody->store(aShape); aResultBody->loadFirstLevel(aShape, "ImportResult"); setResult(aResultBody, aResultIndex++); + + std::set allResultsCashed; // cash to speed up searching in all results selected + std::list aLocalResults; + aLocalResults.push_back (*aResIter); + + std::list aDocuments; /// documents of Parts + aDocuments.push_back ((*aResIter)->document()); + std::list::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((*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::iterator anIter = aGroups.begin(); + for (; anIter != aGroups.end(); anIter++) + { + if (*anIter == aResultGroup) + { + anIsFound = true; + break; + } + } + if (!anIsFound) + aGroups.push_back (aResultGroup); + } + } + } + + std::list::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 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 (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::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 FeaturesPlugin_ImportResult::addFeature( + std::string theID) +{ + std::shared_ptr 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 FeaturesPlugin_ImportResult::subFeature(const int theIndex, + bool /*forTree*/) +{ + ObjectPtr anObj = data()->reflist(FEATURES_ID())->object(theIndex, false); + FeaturePtr aRes = std::dynamic_pointer_cast(anObj); + return aRes; +} + +//================================================================================================= +int FeaturesPlugin_ImportResult::subFeatureId(const int theIndex) const +{ + std::shared_ptr aRefList = std::dynamic_pointer_cast< + ModelAPI_AttributeRefList>(data()->attribute(FEATURES_ID())); + std::list aFeatures = aRefList->list(); + std::list::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 theFeature) +{ + if (!data()->isValid()) + return; + AttributeRefListPtr aList = reflist(FEATURES_ID()); + aList->remove(theFeature); +} diff --git a/src/FeaturesPlugin/FeaturesPlugin_ImportResult.h b/src/FeaturesPlugin/FeaturesPlugin_ImportResult.h index 9b946ab18..69aef3aa2 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_ImportResult.h +++ b/src/FeaturesPlugin/FeaturesPlugin_ImportResult.h @@ -22,7 +22,7 @@ #include "FeaturesPlugin.h" -#include +#include #include /// \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 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 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 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 index 000000000..29c7ec92d --- /dev/null +++ b/src/FeaturesPlugin/Test/TestImportResultWithGroups1.py @@ -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 index 000000000..38f93ae65 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestImportResultWithGroups2.py @@ -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 index 000000000..870233997 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestImportResultWithGroups3.py @@ -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()) diff --git a/src/FeaturesPlugin/doc/importResultFeature.rst b/src/FeaturesPlugin/doc/importResultFeature.rst index b2349e635..a25cb09f3 100644 --- a/src/FeaturesPlugin/doc/importResultFeature.rst +++ b/src/FeaturesPlugin/doc/importResultFeature.rst @@ -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 diff --git a/src/FeaturesPlugin/tests.set b/src/FeaturesPlugin/tests.set index 7e5e58612..b1d33e64d 100644 --- a/src/FeaturesPlugin/tests.set +++ b/src/FeaturesPlugin/tests.set @@ -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 diff --git a/src/ModelAPI/ModelAPI_Tools.cpp b/src/ModelAPI/ModelAPI_Tools.cpp index 1eeea82c7..9554d1e10 100644 --- a/src/ModelAPI/ModelAPI_Tools.cpp +++ b/src/ModelAPI/ModelAPI_Tools.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include @@ -234,6 +235,95 @@ bool findVariable(FeaturePtr theSearcher, const std::wstring& theName, double& o return false; } +static void cacheSubresults(const ResultBodyPtr& theTopLevelResult, + std::set& theCashedResults) +{ + std::list aResults; + ModelAPI_Tools::allSubs(theTopLevelResult, aResults, false); + for (std::list::iterator aR = aResults.begin(); aR != aResults.end(); ++aR) { + theCashedResults.insert(*aR); + } +} + +bool isInResults(AttributeSelectionListPtr theSelection, + const std::list& theResults, + std::set& theCashedResults) +{ + // collect all results into a cashed set + if (theCashedResults.empty()) { + std::list::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(*aRes); + cacheSubresults(aResBody, theCashedResults); + } else if ((*aRes)->groupName() == ModelAPI_ResultPart::group()) { // all results of the part + ResultPartPtr aResPart = std::dynamic_pointer_cast(*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( + 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(theSelection->owner()); + if (!aSelFeature.get() || aSelFeature->results().empty()) + continue; + GeomShapePtr aGroupResShape = aSelFeature->firstResult()->shape(); + + std::set::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(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& allResluts = aContextFeature->results(); + std::list::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 diff --git a/src/ModelAPI/ModelAPI_Tools.h b/src/ModelAPI/ModelAPI_Tools.h index bf49fe5df..68545c09c 100644 --- a/src/ModelAPI/ModelAPI_Tools.h +++ b/src/ModelAPI/ModelAPI_Tools.h @@ -21,6 +21,7 @@ #define ModelAPI_Tools_HeaderFile #include "ModelAPI.h" +#include class ModelAPI_CompositeFeature; class ModelAPI_Document; @@ -310,6 +311,12 @@ MODELAPI_EXPORT std::list > referencedFeatures std::shared_ptr 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& theResults, + std::set& theCashedResults); /*! Returns a container with the current color value. * These are tree int values for RGB definition. diff --git a/src/PartSet/PartSet_Module.cpp b/src/PartSet/PartSet_Module.cpp index a83da4472..8add0208a 100644 --- a/src/PartSet/PartSet_Module.cpp +++ b/src/PartSet/PartSet_Module.cpp @@ -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 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); + } } } } diff --git a/src/PartSet/PartSet_TreeNodes.cpp b/src/PartSet/PartSet_TreeNodes.cpp index dab816179..f8166269c 100644 --- a/src/PartSet/PartSet_TreeNodes.cpp +++ b/src/PartSet/PartSet_TreeNodes.cpp @@ -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(myObject); + if (aFeature.get() && aFeature->getKind() == "Group") + { + std::shared_ptr anOwner = + ModelAPI_Tools::compositeOwner (aFeature); + + if (anOwner.get() && anOwner->getKind() == "ImportResult") + return aDefaultFlag; + } + if (aSession->activeDocument() == aDoc) return aEditingFlag; } -- 2.39.2