Update behavior of UNION operation to keep hierarchy of compounds.
//================================================================================================
FeaturesAPI_Union::FeaturesAPI_Union(const std::shared_ptr<ModelAPI_Feature>& theFeature,
- const std::list<ModelHighAPI_Selection>& theBaseObjects)
+ const std::list<ModelHighAPI_Selection>& theBaseObjects,
+ const int theVersion)
: ModelHighAPI_Interface(theFeature)
{
if(initialize()) {
+ fillAttribute(theVersion, theFeature->integer(FeaturesPlugin_VersionedBoolean::VERSION_ID()));
setBase(theBaseObjects);
}
}
AttributeSelectionListPtr anAttrObjects =
aBase->selectionList(FeaturesPlugin_Union::BASE_OBJECTS_ID());
+ AttributeIntegerPtr aVersion =
+ aBase->integer(FeaturesPlugin_Union::VERSION_ID());
- theDumper << aBase << " = model.addUnion(" << aDocName <<
- ", " << anAttrObjects << ")" << std::endl;
+ theDumper << aBase << " = model.addUnion(" << aDocName << ", " << anAttrObjects;
+
+ if (aVersion && aVersion->isInitialized())
+ theDumper << ", " << aVersion->value();
+
+ theDumper << ")" << std::endl;
}
//==================================================================================================
UnionPtr addUnion(const std::shared_ptr<ModelAPI_Document>& thePart,
- const std::list<ModelHighAPI_Selection>& theBaseObjects)
+ const std::list<ModelHighAPI_Selection>& theBaseObjects,
+ const int theVersion)
{
std::shared_ptr<ModelAPI_Feature> aFeature = thePart->addFeature(FeaturesAPI_Union::ID());
- return UnionPtr(new FeaturesAPI_Union(aFeature, theBaseObjects));
+ return UnionPtr(new FeaturesAPI_Union(aFeature, theBaseObjects, theVersion));
}
/// Constructor with values.
FEATURESAPI_EXPORT
explicit FeaturesAPI_Union(const std::shared_ptr<ModelAPI_Feature>& theFeature,
- const std::list<ModelHighAPI_Selection>& theBaseObjects);
+ const std::list<ModelHighAPI_Selection>& theBaseObjects,
+ const int theVersion = 0);
/// Destructor.
FEATURESAPI_EXPORT
/// \brief Create Union feature.
FEATURESAPI_EXPORT
UnionPtr addUnion(const std::shared_ptr<ModelAPI_Document>& thePart,
- const std::list<ModelHighAPI_Selection>& theBaseObjects);
+ const std::list<ModelHighAPI_Selection>& theBaseObjects,
+ const int theVersion = 0);
#endif // FeaturesAPI_Union_H_
TestPartition_MultiLevelCompound_v20190506_1.py
TestPartition_MultiLevelCompound_v20190506_2.py
TestPartition_MultiLevelCompound_v20190506_3.py
+ TestUnion_MultiLevelCompound_v0.py
+ TestUnion_MultiLevelCompound_v20190506.py
+ TestUnionFaces_v20190506.py
)
#include "FeaturesPlugin_Union.h"
#include <GeomAlgoAPI_Boolean.h>
+#include <GeomAlgoAPI_CompoundBuilder.h>
#include <GeomAlgoAPI_MakeShapeList.h>
#include <GeomAlgoAPI_PaveFiller.h>
+#include <GeomAlgoAPI_ShapeBuilder.h>
#include <GeomAlgoAPI_Tools.h>
-#include <GeomAlgoAPI_UnifySameDomain.h>
#include <GeomAPI_ShapeExplorer.h>
#include <GeomAPI_ShapeIterator.h>
#include <ModelAPI_ResultBody.h>
#include <ModelAPI_Tools.h>
+static const int THE_UNION_VERSION_1 = 20190506;
+
//=================================================================================================
FeaturesPlugin_Union::FeaturesPlugin_Union()
{
void FeaturesPlugin_Union::initAttributes()
{
data()->addAttribute(BASE_OBJECTS_ID(), ModelAPI_AttributeSelectionList::typeId());
+ initVersion(THE_UNION_VERSION_1, selectionList(BASE_OBJECTS_ID()));
}
//=================================================================================================
void FeaturesPlugin_Union::execute()
{
- ListOfShape anObjects;
- std::map<std::shared_ptr<GeomAPI_Shape>, ListOfShape> aCompSolidsObjects;
+ ObjectHierarchy anObjects;
+ ListOfShape anEmptyList;
// Getting objects.
- AttributeSelectionListPtr anObjectsSelList =
- selectionList(FeaturesPlugin_Union::BASE_OBJECTS_ID());
- for(int anObjectsIndex = 0; anObjectsIndex < anObjectsSelList->size(); anObjectsIndex++) {
- AttributeSelectionPtr anObjectAttr = anObjectsSelList->value(anObjectsIndex);
- std::shared_ptr<GeomAPI_Shape> anObject = anObjectAttr->value();
- if(!anObject.get()) {
- return;
- }
- ResultPtr aContext = anObjectAttr->context();
- ResultBodyPtr aResCompSolidPtr = ModelAPI_Tools::bodyOwner(aContext);
- if(aResCompSolidPtr.get()) {
- std::shared_ptr<GeomAPI_Shape> aContextShape = aResCompSolidPtr->shape();
- std::map<std::shared_ptr<GeomAPI_Shape>, ListOfShape>::iterator
- anIt = aCompSolidsObjects.begin();
- for(; anIt != aCompSolidsObjects.end(); anIt++) {
- if(anIt->first->isEqual(aContextShape)) {
- aCompSolidsObjects[anIt->first].push_back(anObject);
- break;
- }
- }
- if(anIt == aCompSolidsObjects.end()) {
- aCompSolidsObjects[aContextShape].push_back(anObject);
- }
- } else {
- anObjects.push_back(anObject);
- }
- }
-
- // Collecting solids from compsolids which will not be modified in
- // boolean operation and will be added to result.
- ListOfShape aShapesToAdd;
- for(std::map<std::shared_ptr<GeomAPI_Shape>, ListOfShape>::iterator
- anIt = aCompSolidsObjects.begin();
- anIt != aCompSolidsObjects.end(); anIt++) {
- std::shared_ptr<GeomAPI_Shape> aCompSolid = anIt->first;
- ListOfShape& aUsedInOperationSolids = anIt->second;
- anObjects.insert(anObjects.end(), aUsedInOperationSolids.begin(), aUsedInOperationSolids.end());
-
- // Collect solids from compsolid which will not be modified in boolean operation.
- for(GeomAPI_ShapeIterator anExp(aCompSolid); anExp.more(); anExp.next()) {
- std::shared_ptr<GeomAPI_Shape> aSolidInCompSolid = anExp.current();
- ListOfShape::iterator anIt = aUsedInOperationSolids.begin();
- for(; anIt != aUsedInOperationSolids.end(); anIt++) {
- if(aSolidInCompSolid->isEqual(*anIt)) {
- break;
- }
- }
- if(anIt == aUsedInOperationSolids.end()) {
- aShapesToAdd.push_back(aSolidInCompSolid);
- }
- }
- }
+ if (!processAttribute(BASE_OBJECTS_ID(), anObjects, anEmptyList))
+ return;
- if(anObjects.size() < 2) {
+ if(anObjects.Objects().size() < 2) {
setError("Error: Not enough objects for operation. Should be at least 2.");
return;
}
- // Fuse objects.
std::string anError;
- std::shared_ptr<GeomAlgoAPI_MakeShape> anAlgo;
- ListOfShape aTools;
- if (anObjects.front()->shapeType() == GeomAPI_Shape::SOLID) {
- aTools.splice(aTools.begin(), anObjects, anObjects.begin());
- anAlgo.reset(new GeomAlgoAPI_Boolean(anObjects,
- aTools,
- GeomAlgoAPI_Tools::BOOL_FUSE));
- } else {
- anAlgo.reset(new GeomAlgoAPI_UnifySameDomain(anObjects));
+ int aResultIndex = 0;
+ std::vector<FeaturesPlugin_Tools::ResultBaseAlgo> aResultBaseAlgoList;
+ ListOfShape aResultShapesList;
+
+ int aUnionVersion = version();
+ GeomShapePtr aResultCompound = GeomAlgoAPI_CompoundBuilder::compound(ListOfShape());
+
+ // Fuse objects.
+ bool isOk = true;
+ for (ObjectHierarchy::Iterator anObjectsIt = anObjects.Begin();
+ anObjectsIt != anObjects.End() && isOk;
+ ++anObjectsIt) {
+ GeomShapePtr anObject = *anObjectsIt;
+ GeomShapePtr aParent = anObjects.Parent(anObject, false);
+
+ if (aParent && aParent->shapeType() <= GeomAPI_Shape::COMPSOLID) {
+ // get parent once again to mark it and the subs as processed
+ aParent = anObjects.Parent(anObject);
+ // compsolid handling
+ isOk = processCompsolid(GeomAlgoAPI_Tools::BOOL_FUSE,
+ anObjects, aParent, anEmptyList, anEmptyList,
+ aResultIndex, aResultBaseAlgoList, aResultShapesList,
+ aResultCompound);
+ } else {
+ // process object as is
+ isOk = processObject(GeomAlgoAPI_Tools::BOOL_FUSE,
+ anObject, anEmptyList, anEmptyList,
+ aResultIndex, aResultBaseAlgoList, aResultShapesList,
+ aResultCompound);
+ }
}
- // Checking that the algorithm worked properly.
std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeShapeList(new GeomAlgoAPI_MakeShapeList());
- GeomAPI_DataMapOfShapeShape aMapOfShapes;
- if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(anAlgo, getKind(), anError)) {
- setError(anError);
- return;
+ for (std::vector<FeaturesPlugin_Tools::ResultBaseAlgo>::iterator
+ aRBAIt = aResultBaseAlgoList.begin();
+ aRBAIt != aResultBaseAlgoList.end(); ++aRBAIt) {
+ aMakeShapeList->appendAlgo(aRBAIt->makeShape);
}
- GeomShapePtr aShape = anAlgo->shape();
- aMakeShapeList->appendAlgo(anAlgo);
- aMapOfShapes.merge(anAlgo->mapOfSubShapes());
-
- // Store original shapes for naming.
- anObjects.splice(anObjects.begin(), aTools);
- anObjects.insert(anObjects.end(), aShapesToAdd.begin(), aShapesToAdd.end());
-
- // Combine result with not used solids from compsolid.
- if(aShapesToAdd.size() > 0) {
- aShapesToAdd.push_back(aShape);
- std::shared_ptr<GeomAlgoAPI_PaveFiller> aFillerAlgo(
- new GeomAlgoAPI_PaveFiller(aShapesToAdd, true));
- if (GeomAlgoAPI_Tools::AlgoError::isAlgorithmFailed(aFillerAlgo, getKind(), anError)) {
- setError(anError);
- return;
+ GeomShapePtr aShape;
+ GeomAPI_ShapeIterator aCIt(aResultCompound);
+ if (aUnionVersion < THE_UNION_VERSION_1) {
+ // if the compound consists of a single sub-shape, take it,
+ // otherwise, take the full compound
+ aShape = aCIt.current();
+ aCIt.next();
+ if (aCIt.more())
+ aShape = aResultCompound;
+ }
+ else {
+ // merge hierarchies of compounds containing objects and tools
+ aShape = keepUnusedSubsOfCompound(aCIt.current(), anObjects, ObjectHierarchy(), aMakeShapeList);
+ for (aCIt.next(); aCIt.more(); aCIt.next()) {
+ std::shared_ptr<GeomAlgoAPI_ShapeBuilder> aBuilder(new GeomAlgoAPI_ShapeBuilder);
+ aBuilder->add(aShape, aCIt.current());
+ aMakeShapeList->appendAlgo(aBuilder);
}
-
- aShape = aFillerAlgo->shape();
- aMakeShapeList->appendAlgo(aFillerAlgo);
- aMapOfShapes.merge(aFillerAlgo->mapOfSubShapes());
}
- // workaround: make copy to name edges correctly
// Store result and naming.
-
std::shared_ptr<ModelAPI_ResultBody> aResultBody = document()->createBody(data());
- aResultBody->storeModified(anObjects.front(), aShape);
+ ListOfShape anObjectsList = anObjects.Objects();
+ aResultBody->storeModified(anObjectsList.front(), aShape);
- for(ListOfShape::const_iterator anIter = anObjects.begin(); anIter != anObjects.end(); ++anIter) {
+ for(ListOfShape::const_iterator anIter = anObjectsList.begin(); anIter != anObjectsList.end(); ++anIter) {
aResultBody->loadModifiedShapes(aMakeShapeList, *anIter, GeomAPI_Shape::EDGE);
aResultBody->loadModifiedShapes(aMakeShapeList, *anIter, GeomAPI_Shape::FACE);
//aResultBody->loadDeletedShapes(&aMakeShapeList, *anIter, GeomAPI_Shape::FACE, aDeletedTag);
#ifndef FeaturesPlugin_Union_H_
#define FeaturesPlugin_Union_H_
-#include "FeaturesPlugin.h"
-#include <ModelAPI_Feature.h>
-
-#include <GeomAPI_Shape.h>
-
-class GeomAlgoAPI_MakeShape;
+#include "FeaturesPlugin_VersionedBoolean.h"
/// \class FeaturesPlugin_Union
/// \ingroup Plugins
/// \brief Feature for applying of Union operations on Shapes. Union removes shared shapes from
/// several shapes and combines them into one.
-class FeaturesPlugin_Union : public ModelAPI_Feature
+class FeaturesPlugin_Union : public FeaturesPlugin_VersionedBoolean
{
public:
/// Feature kind.
#include <GeomAlgoAPI_ShapeBuilder.h>
#include <GeomAlgoAPI_ShapeTools.h>
#include <GeomAlgoAPI_Tools.h>
+#include <GeomAlgoAPI_UnifySameDomain.h>
#include <GeomAPI_Face.h>
#include <GeomAPI_ShapeExplorer.h>
#include <GeomAPI_ShapeIterator.h>
#include <algorithm>
#include <map>
+static void performBoolean(const GeomAlgoAPI_Tools::BOPType theBooleanType,
+ GeomMakeShapePtr& theBooleanAlgo,
+ const ListOfShape& theObjects,
+ const ListOfShape& theTools)
+{
+ if (theBooleanType == GeomAlgoAPI_Tools::BOOL_PARTITION)
+ theBooleanAlgo.reset(new GeomAlgoAPI_Partition(theObjects, theTools));
+ else {
+ // separate processing of FUSE, if only objects are given
+ if (theBooleanType == GeomAlgoAPI_Tools::BOOL_FUSE && theTools.empty()) {
+ if (theObjects.front()->shapeType() == GeomAPI_Shape::FACE)
+ theBooleanAlgo.reset(new GeomAlgoAPI_UnifySameDomain(theObjects));
+ else {
+ ListOfShape anObjects = theObjects;
+ ListOfShape aTools;
+ aTools.splice(aTools.begin(), anObjects, anObjects.begin());
+ theBooleanAlgo.reset(new GeomAlgoAPI_Boolean(anObjects, aTools, theBooleanType));
+ }
+ }
+ else
+ theBooleanAlgo.reset(new GeomAlgoAPI_Boolean(theObjects, theTools, theBooleanType));
+ }
+}
+
//=================================================================================================
void FeaturesPlugin_VersionedBoolean::initVersion(const int theVersion,
const AttributePtr theObjectsAttr,
//=================================================================================================
bool FeaturesPlugin_VersionedBoolean::processCompsolid(
const GeomAlgoAPI_Tools::BOPType theBooleanType,
- const ObjectHierarchy& theCompsolidHierarchy,
+ ObjectHierarchy& theCompsolidHierarchy,
const GeomShapePtr& theCompsolid,
const ListOfShape& theTools,
const ListOfShape& thePlanes,
aToolsWithPlanes.insert(aToolsWithPlanes.end(), aPlanesCopy.begin(), aPlanesCopy.end());
std::shared_ptr<GeomAlgoAPI_MakeShape> aBoolAlgo;
- if (theBooleanType == GeomAlgoAPI_Tools::BOOL_PARTITION)
- aBoolAlgo.reset(new GeomAlgoAPI_Partition(aUsedInOperationSolids, aToolsWithPlanes));
- else
- aBoolAlgo.reset(new GeomAlgoAPI_Boolean(aUsedInOperationSolids,
- aToolsWithPlanes,
- theBooleanType));
+ performBoolean(theBooleanType, aBoolAlgo, aUsedInOperationSolids, aToolsWithPlanes);
// Checking that the algorithm worked properly.
std::string anError;
// Add result to not used solids from compsolid.
if (!aNotUsedSolids.empty()) {
+ theCompsolidHierarchy.MarkProcessed(aNotUsedSolids);
+
ListOfShape aShapesToAdd = aNotUsedSolids;
aShapesToAdd.push_back(aBoolAlgo->shape());
std::shared_ptr<GeomAlgoAPI_PaveFiller> aFillerAlgo(
//=================================================================================================
bool FeaturesPlugin_VersionedBoolean::processCompound(
const GeomAlgoAPI_Tools::BOPType theBooleanType,
- const ObjectHierarchy& theCompoundHierarchy,
+ ObjectHierarchy& theCompoundHierarchy,
const GeomShapePtr& theCompound,
const ListOfShape& theTools,
int& theResultIndex,
}
std::shared_ptr<GeomAlgoAPI_MakeShapeList> aMakeShapeList(new GeomAlgoAPI_MakeShapeList());
- std::shared_ptr<GeomAlgoAPI_Boolean> aBoolAlgo(
- new GeomAlgoAPI_Boolean(aUsedInOperationShapes,
- theTools,
- theBooleanType));
+ std::shared_ptr<GeomAlgoAPI_MakeShape> aBoolAlgo;
+ performBoolean(theBooleanType, aBoolAlgo, aUsedInOperationShapes, theTools);
// Checking that the algorithm worked properly.
std::string anError;
// Add result to not used shape from compound.
if (!aNotUsedShapes.empty()) {
+ theCompoundHierarchy.MarkProcessed(aNotUsedShapes);
+
ListOfShape aShapesForResult = aNotUsedShapes;
if (aResultShape->shapeType() == GeomAPI_Shape::COMPOUND) {
for (GeomAPI_ShapeIterator aResultIt(aResultShape); aResultIt.more(); aResultIt.next()) {
return aParent;
}
+void FeaturesPlugin_VersionedBoolean::ObjectHierarchy::MarkProcessed(const GeomShapePtr& theShape)
+{
+ myProcessedObjects.insert(theShape);
+}
+
+void FeaturesPlugin_VersionedBoolean::ObjectHierarchy::MarkProcessed(const ListOfShape& theShapes)
+{
+ for (ListOfShape::const_iterator anIt = theShapes.begin(); anIt != theShapes.end(); ++anIt)
+ MarkProcessed(*anIt);
+}
+
void FeaturesPlugin_VersionedBoolean::ObjectHierarchy::ObjectsByType(
ListOfShape& theShapesByType,
ListOfShape& theOtherShapes,
void FeaturesPlugin_VersionedBoolean::ObjectHierarchy::CompoundsOfUnusedObjects(
ListOfShape& theDestination) const
{
- SetOfShape aUsedObjects;
+ SetOfShape aUsedObjects = myProcessedObjects;
aUsedObjects.insert(myObjects.begin(), myObjects.end());
for (std::vector<ShapeAndSubshapes>::const_iterator anIt = mySubshapes.begin();
/// By default, the parent and all its subshapes are marked as processed for further skip.
GeomShapePtr Parent(const GeomShapePtr& theShape, bool theMarkProcessed = true);
+ /// Marke the shape as already processed
+ void MarkProcessed(const GeomShapePtr& theShape);
+ /// Marke list ofshapes as already processed
+ void MarkProcessed(const ListOfShape& theShapes);
+
/// Split compound/compsolid shape for subshapes selected for Boolean operation and the other.
void SplitCompound(const GeomShapePtr& theCompShape,
ListOfShape& theUsed,
/// is added to this compound, and corresponding ResultBody is not generated.
/// \return \c false if something went wrong
bool processCompsolid(const GeomAlgoAPI_Tools::BOPType theBooleanType,
- const ObjectHierarchy& theCompsolidHierarchy,
+ ObjectHierarchy& theCompsolidHierarchy,
const GeomShapePtr& theCompsolid,
const ListOfShape& theTools,
const ListOfShape& thePlanes,
/// is added to this compound, and corresponding ResultBody is not generated.
/// \return \c false if something went wrong
bool processCompound(const GeomAlgoAPI_Tools::BOPType theBooleanType,
- const ObjectHierarchy& theCompoundHierarchy,
+ ObjectHierarchy& theCompoundHierarchy,
const GeomShapePtr& theCompound,
const ListOfShape& theTools,
int& theResultIndex,
--- /dev/null
+# 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
+#
+
+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(-5.197255574614073, 0.1732418524871273, 67.586529314451)
+model.do()
+Face_1 = model.addFace(Part_1_doc, [model.selection("EDGE", "Sketch_1/SketchCircle_1_2")])
+Partition_1_objects = [model.selection("FACE", "Face_1_1"), model.selection("FACE", "PartSet/YOZ"), model.selection("FACE", "PartSet/XOZ")]
+Partition_1 = model.addPartition(Part_1_doc, Partition_1_objects)
+Union_1 = model.addUnion(Part_1_doc, [model.selection("FACE", "Partition_1_1_1"), model.selection("FACE", "Partition_1_1_2")], 20190506)
+model.do()
+model.end()
+
+from GeomAPI import GeomAPI_Shape
+
+model.testNbResults(Union_1, 1)
+model.testNbSubResults(Union_1, [3])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.SOLID, [0])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.FACE, [3])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.EDGE, [10])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.VERTEX, [20])
+
+assert(model.checkPythonDump())
--- /dev/null
+# 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
+#
+
+from SketchAPI import *
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchLine_1 = Sketch_1.addLine(25, -25, -25, -25)
+SketchLine_2 = Sketch_1.addLine(-25, -25, -25, 25)
+SketchLine_3 = Sketch_1.addLine(-25, 25, 25, 25)
+SketchLine_4 = Sketch_1.addLine(25, 25, 25, -25)
+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())
+SketchConstraintEqual_1 = Sketch_1.setEqual(SketchLine_1.result(), SketchLine_2.result())
+SketchConstraintLength_1 = Sketch_1.setLength(SketchLine_1.result(), 50)
+SketchProjection_1 = Sketch_1.addProjection(model.selection("VERTEX", "PartSet/Origin"), False)
+SketchPoint_1 = SketchProjection_1.createdFeature()
+SketchConstraintDistance_1 = Sketch_1.setDistance(SketchAPI_Point(SketchPoint_1).coordinates(), SketchLine_1.result(), 25, True)
+SketchConstraintDistance_2 = Sketch_1.setDistance(SketchAPI_Point(SketchPoint_1).coordinates(), SketchLine_2.result(), 25, True)
+SketchMultiRotation_1_objects = [SketchLine_1.result(), SketchLine_2.result(), SketchLine_3.result(), SketchLine_4.result()]
+SketchMultiRotation_1 = Sketch_1.addRotation(SketchMultiRotation_1_objects, SketchAPI_Point(SketchPoint_1).coordinates(), 45, 2, True)
+[SketchLine_5, SketchLine_6, SketchLine_7, SketchLine_8] = SketchMultiRotation_1.rotated()
+SketchLine_8.setName("SketchLine_14")
+SketchLine_8.result().setName("SketchLine_14")
+SketchLine_7.setName("SketchLine_11")
+SketchLine_7.result().setName("SketchLine_11")
+SketchLine_6.setName("SketchLine_8")
+SketchLine_6.result().setName("SketchLine_8")
+SketchCircle_1 = Sketch_1.addCircle(80, 0, 20)
+SketchConstraintRadius_1 = Sketch_1.setRadius(SketchCircle_1.results()[1], 20)
+SketchProjection_2 = Sketch_1.addProjection(model.selection("EDGE", "PartSet/OX"), False)
+SketchLine_9 = SketchProjection_2.createdFeature()
+SketchLine_9.setName("SketchLine_9")
+SketchLine_9.result().setName("SketchLine_9")
+SketchConstraintCoincidence_5 = Sketch_1.setCoincident(SketchCircle_1.center(), SketchLine_9.result())
+SketchConstraintDistance_3 = Sketch_1.setDistance(SketchAPI_Line(SketchLine_9).startPoint(), SketchCircle_1.center(), 80, True)
+SketchMultiRotation_2 = Sketch_1.addRotation([SketchCircle_1.results()[1]], SketchAPI_Line(SketchLine_9).startPoint(), 360, 8, True)
+[SketchCircle_2, SketchCircle_3, SketchCircle_4, SketchCircle_5, SketchCircle_6, SketchCircle_7, SketchCircle_8] = SketchMultiRotation_2.rotated()
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("COMPOUND", "Sketch_1")], model.selection(), 50, 0)
+Compound_1_objects = [model.selection("COMPSOLID", "Extrusion_1_1"), model.selection("SOLID", "Extrusion_1_2"), model.selection("SOLID", "Extrusion_1_3"), model.selection("SOLID", "Extrusion_1_4"), model.selection("SOLID", "Extrusion_1_5"), model.selection("SOLID", "Extrusion_1_6"), model.selection("SOLID", "Extrusion_1_7"), model.selection("SOLID", "Extrusion_1_8"), model.selection("SOLID", "Extrusion_1_9")]
+Compound_1 = model.addCompound(Part_1_doc, Compound_1_objects)
+Union_1_objects = [model.selection("SOLID", "Compound_1_1_1_1"), model.selection("SOLID", "Compound_1_1_1_2"), model.selection("SOLID", "Compound_1_1_1_3")]
+Union_1 = model.addUnion(Part_1_doc, Union_1_objects)
+
+model.testHaveNamingSubshapes(Union_1, model, Part_1_doc)
+
+model.end()
+
+from GeomAPI import GeomAPI_Shape
+
+model.testNbResults(Union_1, 1)
+model.testNbSubResults(Union_1, [7])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.SOLID, [7])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.FACE, [46])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.EDGE, [176])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.VERTEX, [352])
+model.testResultsVolumes(Union_1, [146446.6094067262311])
+
+assert(model.checkPythonDump())
--- /dev/null
+# 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
+#
+
+from SketchAPI import *
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchLine_1 = Sketch_1.addLine(25, -25, -25, -25)
+SketchLine_2 = Sketch_1.addLine(-25, -25, -25, 25)
+SketchLine_3 = Sketch_1.addLine(-25, 25, 25, 25)
+SketchLine_4 = Sketch_1.addLine(25, 25, 25, -25)
+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())
+SketchConstraintEqual_1 = Sketch_1.setEqual(SketchLine_1.result(), SketchLine_2.result())
+SketchConstraintLength_1 = Sketch_1.setLength(SketchLine_1.result(), 50)
+SketchProjection_1 = Sketch_1.addProjection(model.selection("VERTEX", "PartSet/Origin"), False)
+SketchPoint_1 = SketchProjection_1.createdFeature()
+SketchConstraintDistance_1 = Sketch_1.setDistance(SketchAPI_Point(SketchPoint_1).coordinates(), SketchLine_1.result(), 25, True)
+SketchConstraintDistance_2 = Sketch_1.setDistance(SketchAPI_Point(SketchPoint_1).coordinates(), SketchLine_2.result(), 25, True)
+SketchMultiRotation_1_objects = [SketchLine_1.result(), SketchLine_2.result(), SketchLine_3.result(), SketchLine_4.result()]
+SketchMultiRotation_1 = Sketch_1.addRotation(SketchMultiRotation_1_objects, SketchAPI_Point(SketchPoint_1).coordinates(), 45, 2, True)
+[SketchLine_5, SketchLine_6, SketchLine_7, SketchLine_8] = SketchMultiRotation_1.rotated()
+SketchLine_8.setName("SketchLine_14")
+SketchLine_8.result().setName("SketchLine_14")
+SketchLine_7.setName("SketchLine_11")
+SketchLine_7.result().setName("SketchLine_11")
+SketchLine_6.setName("SketchLine_8")
+SketchLine_6.result().setName("SketchLine_8")
+SketchCircle_1 = Sketch_1.addCircle(80, 0, 20)
+SketchConstraintRadius_1 = Sketch_1.setRadius(SketchCircle_1.results()[1], 20)
+SketchProjection_2 = Sketch_1.addProjection(model.selection("EDGE", "PartSet/OX"), False)
+SketchLine_9 = SketchProjection_2.createdFeature()
+SketchLine_9.setName("SketchLine_9")
+SketchLine_9.result().setName("SketchLine_9")
+SketchConstraintCoincidence_5 = Sketch_1.setCoincident(SketchCircle_1.center(), SketchLine_9.result())
+SketchConstraintDistance_3 = Sketch_1.setDistance(SketchAPI_Line(SketchLine_9).startPoint(), SketchCircle_1.center(), 80, True)
+SketchMultiRotation_2 = Sketch_1.addRotation([SketchCircle_1.results()[1]], SketchAPI_Line(SketchLine_9).startPoint(), 360, 8, True)
+[SketchCircle_2, SketchCircle_3, SketchCircle_4, SketchCircle_5, SketchCircle_6, SketchCircle_7, SketchCircle_8] = SketchMultiRotation_2.rotated()
+model.do()
+Extrusion_1 = model.addExtrusion(Part_1_doc, [model.selection("COMPOUND", "Sketch_1")], model.selection(), 50, 0)
+Compound_1_objects = [model.selection("COMPSOLID", "Extrusion_1_1"), model.selection("SOLID", "Extrusion_1_2"), model.selection("SOLID", "Extrusion_1_3"), model.selection("SOLID", "Extrusion_1_4"), model.selection("SOLID", "Extrusion_1_5"), model.selection("SOLID", "Extrusion_1_6"), model.selection("SOLID", "Extrusion_1_7"), model.selection("SOLID", "Extrusion_1_8"), model.selection("SOLID", "Extrusion_1_9")]
+Compound_1 = model.addCompound(Part_1_doc, Compound_1_objects)
+Union_1_objects = [model.selection("SOLID", "Compound_1_1_1_1"), model.selection("SOLID", "Compound_1_1_1_2"), model.selection("SOLID", "Compound_1_1_1_3")]
+Union_1 = model.addUnion(Part_1_doc, Union_1_objects, 20190506)
+
+model.testHaveNamingSubshapes(Union_1, model, Part_1_doc)
+
+model.end()
+
+from GeomAPI import GeomAPI_Shape
+
+model.testNbResults(Union_1, 1)
+model.testNbSubResults(Union_1, [9])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.SOLID, [15])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.FACE, [70])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.EDGE, [224])
+model.testNbSubShapes(Union_1, GeomAPI_Shape.VERTEX, [448])
+model.testResultsVolumes(Union_1, [649101.433981])
+
+assert(model.checkPythonDump())