From: mbs Date: Sat, 10 Dec 2022 00:10:58 +0000 (+0000) Subject: * added missing tests and documentation X-Git-Tag: V9_11_0a1~33 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=1ab95131ca95f1ddf0c692f43e38f974ee22a2be;p=modules%2Fshaper.git * added missing tests and documentation * code cleanup --- diff --git a/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp b/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp index c92dd6f89..71030a080 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp +++ b/src/FeaturesPlugin/FeaturesPlugin_Validators.cpp @@ -2098,24 +2098,11 @@ bool FeaturesPlugin_ValidatorDefeaturingSelection::isValid( return true; } - -//--------------------------------------------------------- -// #define USE_DEBUG -// #define DEBUG -// static const char *dbg_class = "FeaturesPlugin_ValidatorSewingSelection"; -// #include "MBDebug.h" -// #include "MBModel.h" -// #include "MBGeom.h" -//--------------------------------------------------------- - //================================================================================================== bool FeaturesPlugin_ValidatorSewingSelection::isValid(const AttributePtr& theAttribute, const std::list& theArguments, Events_InfoMessage& theError) const { - // DBG_FUN(); - // ARG(theAttribute); - AttributeSelectionListPtr anAttrSelectionList = std::dynamic_pointer_cast(theAttribute); if (!anAttrSelectionList.get()) { @@ -2160,6 +2147,5 @@ bool FeaturesPlugin_ValidatorSewingSelection::isValid(const AttributePtr& theAtt } - // MSGEL("...selection is VALID."); return true; } diff --git a/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts b/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts index 23c8c6be6..25fa2623d 100644 --- a/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts +++ b/src/FeaturesPlugin/FeaturesPlugin_msg_fr.ts @@ -5570,10 +5570,5 @@ Le résultat est vide - - - - - diff --git a/src/FeaturesPlugin/Test/TestSewing_Manifold.py b/src/FeaturesPlugin/Test/TestSewing_Manifold.py new file mode 100644 index 000000000..0f9835b3b --- /dev/null +++ b/src/FeaturesPlugin/Test/TestSewing_Manifold.py @@ -0,0 +1,134 @@ +# Copyright (C) 2014-2022 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 +from GeomAPI import GeomAPI_Shape + +aShapeTypes = { + GeomAPI_Shape.SOLID: "GeomAPI_Shape.SOLID", + GeomAPI_Shape.FACE: "GeomAPI_Shape.FACE", + GeomAPI_Shape.EDGE: "GeomAPI_Shape.EDGE", + GeomAPI_Shape.VERTEX: "GeomAPI_Shape.VERTEX"} + +def testNbUniqueSubShapes(theFeature, theShapeType, theExpectedNbSubShapes): + """ Tests number of unique feature sub-shapes of passed type for each result. + :param theFeature: feature to test. + :param theShapeType: shape type of sub-shapes to test. + :param theExpectedNbSubShapes: list of sub-shapes numbers. Size of list should be equal to len(theFeature.results()). + """ + aResults = theFeature.feature().results() + aNbResults = len(aResults) + aListSize = len(theExpectedNbSubShapes) + assert (aNbResults == aListSize), "Number of results: {} not equal to list size: {}.".format(aNbResults, aListSize) + for anIndex in range(0, aNbResults): + aNbResultSubShapes = 0 + anExpectedNbSubShapes = theExpectedNbSubShapes[anIndex] + aNbResultSubShapes = aResults[anIndex].shape().subShapes(theShapeType, True).size() + assert (aNbResultSubShapes == anExpectedNbSubShapes), "Number of sub-shapes of type {} for result[{}]: {}. Expected: {}.".format(aShapeTypes[theShapeType], anIndex, aNbResultSubShapes, anExpectedNbSubShapes) + +def testResults(theFeature,theModel,NbRes,NbSubRes,NbShell,NbFace,NbEdge,NbVertex): + """ Tests numbers of unique sub-shapes in the results + """ + aResults = theFeature.feature().results() + aNbResults = len(aResults) + assert (aNbResults == NbRes), "Number of results: {} not equal to {}}.".format(aNbResults, NbRes) + theModel.testNbSubResults(theFeature, NbSubRes) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.SHELL, NbShell) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.FACE, NbFace) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.EDGE, NbEdge) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.VERTEX, NbVertex) + +# Create document +model.begin() +partSet = model.moduleDocument() +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() +Param_dx = model.addParameter(Part_1_doc, "dx", '10') +Param_alfa = model.addParameter(Part_1_doc, "alfa", '90') + +# ============================================================================= +# Test 1. Sew two shells with a single common edge +# ============================================================================= +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) +Box_2 = model.addBox(Part_1_doc, 10, 10, 10) +Translation_1 = model.addTranslation(Part_1_doc, [model.selection("SOLID", "Box_2_1")], axis = model.selection("EDGE", "PartSet/OX"), distance = "dx", keepSubResults = True) +Rotation_1 = model.addRotation(Part_1_doc, [model.selection("SOLID", "Translation_1_1")], axis = model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Right]"), angle = "alfa", keepSubResults = True) + +Shell_1_objects = [model.selection("FACE", "Box_1_1/Top"), + model.selection("FACE", "Box_1_1/Left"), + model.selection("FACE", "Box_1_1/Right"), + model.selection("FACE", "Box_1_1/Back"), + model.selection("FACE", "Box_1_1/Bottom")] +Shell_1 = model.addShell(Part_1_doc, Shell_1_objects) + +Shell_2_objects = [model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Top"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Left"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Front"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Right"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Bottom")] +Shell_2 = model.addShell(Part_1_doc, Shell_2_objects) +model.do() + +Sewing_1 = model.addSewing(Part_1_doc, [model.selection("SHELL", "Shell_2_1"), model.selection("SHELL", "Shell_1_1")], 1e-07, allowNonManifold = False, alwaysCreateResult = True) +model.end() + +# sewing successful +testResults(Sewing_1, model, 1, [0], [1], [10], [23], [14]) + +# ============================================================================= +# Test 2. Sew two shells with four common edges +# ============================================================================= +model.undo() +model.begin() +Param_alfa.setValue(0) +model.do() + +Sewing_2 = model.addSewing(Part_1_doc, [model.selection("SHELL", "Shell_2_1"), model.selection("SHELL", "Shell_1_1")], 1e-07, allowNonManifold = False, alwaysCreateResult = True) +model.end() + +# sewing successful +testResults(Sewing_2, model, 1, [0], [1], [10], [20], [12]) + +# ============================================================================= +# Test 3. Sew two slightly disconnected shells +# ============================================================================= +model.undo() +model.begin() +Param_dx.setValue(10.0001) +model.do() + +Sewing_3 = model.addSewing(Part_1_doc, [model.selection("SHELL", "Shell_2_1"), model.selection("SHELL", "Shell_1_1")], 1e-07, allowNonManifold = False, alwaysCreateResult = True) +model.end() + +# no sewing done (result is a compound with the two input shells) +testResults(Sewing_3, model, 1, [2], [2], [10], [24], [16]) + +# ============================================================================= +# Test 4. Sew two slightly disconnected shells with larger tolerance +# ============================================================================= +model.undo() +model.begin() +Param_dx.setValue(10.0001) +model.do() + +Sewing_4 = model.addSewing(Part_1_doc, [model.selection("SHELL", "Shell_2_1"), model.selection("SHELL", "Shell_1_1")], 1e-04, allowNonManifold = False, alwaysCreateResult = True) +model.end() + +# sewing successful +testResults(Sewing_4, model, 1, [0], [1], [10], [20], [12]) diff --git a/src/FeaturesPlugin/Test/TestSewing_NonManifold.py b/src/FeaturesPlugin/Test/TestSewing_NonManifold.py new file mode 100644 index 000000000..d64ba8b09 --- /dev/null +++ b/src/FeaturesPlugin/Test/TestSewing_NonManifold.py @@ -0,0 +1,97 @@ +# Copyright (C) 2014-2022 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 +from GeomAPI import GeomAPI_Shape + +aShapeTypes = { + GeomAPI_Shape.SOLID: "GeomAPI_Shape.SOLID", + GeomAPI_Shape.FACE: "GeomAPI_Shape.FACE", + GeomAPI_Shape.EDGE: "GeomAPI_Shape.EDGE", + GeomAPI_Shape.VERTEX: "GeomAPI_Shape.VERTEX"} + +def testNbUniqueSubShapes(theFeature, theShapeType, theExpectedNbSubShapes): + """ Tests number of unique feature sub-shapes of passed type for each result. + :param theFeature: feature to test. + :param theShapeType: shape type of sub-shapes to test. + :param theExpectedNbSubShapes: list of sub-shapes numbers. Size of list should be equal to len(theFeature.results()). + """ + aResults = theFeature.feature().results() + aNbResults = len(aResults) + aListSize = len(theExpectedNbSubShapes) + assert (aNbResults == aListSize), "Number of results: {} not equal to list size: {}.".format(aNbResults, aListSize) + for anIndex in range(0, aNbResults): + aNbResultSubShapes = 0 + anExpectedNbSubShapes = theExpectedNbSubShapes[anIndex] + aNbResultSubShapes = aResults[anIndex].shape().subShapes(theShapeType, True).size() + assert (aNbResultSubShapes == anExpectedNbSubShapes), "Number of sub-shapes of type {} for result[{}]: {}. Expected: {}.".format(aShapeTypes[theShapeType], anIndex, aNbResultSubShapes, anExpectedNbSubShapes) + +def testResults(theFeature,theModel,NbRes,NbSubRes,NbShell,NbFace,NbEdge,NbVertex): + """ Tests numbers of unique sub-shapes in the results + """ + aResults = theFeature.feature().results() + aNbResults = len(aResults) + assert (aNbResults == NbRes), "Number of results: {} not equal to {}}.".format(aNbResults, NbRes) + theModel.testNbSubResults(theFeature, NbSubRes) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.SHELL, NbShell) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.FACE, NbFace) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.EDGE, NbEdge) + testNbUniqueSubShapes(theFeature, GeomAPI_Shape.VERTEX, NbVertex) + +# Create document +model.begin() +partSet = model.moduleDocument() +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() +Param_dx = model.addParameter(Part_1_doc, "dx", '10') +Param_alfa = model.addParameter(Part_1_doc, "alfa", '90') + +# ============================================================================= +# Test 1. Sew two shells with a single common edge +# ============================================================================= +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) +Box_2 = model.addBox(Part_1_doc, 10, 10, 10) +Vertex_1 = model.selection("VERTEX", "[Box_1_1/Back][Box_1_1/Left][Box_1_1/Bottom]") +Vertex_2 = model.selection("VERTEX", "[Box_1_1/Front][Box_1_1/Left][Box_1_1/Top]") +Translation_1 = model.addTranslation(Part_1_doc, [model.selection("SOLID", "Box_2_1")], startPoint = Vertex_1, endPoint = Vertex_2, keepSubResults = True) + +Shell_1_objects = [model.selection("FACE", "Box_1_1/Top"), model.selection("FACE", "Box_1_1/Front")] +Shell_1 = model.addShell(Part_1_doc, Shell_1_objects) + +Shell_2_objects = [model.selection("FACE", "Translation_1_1/MF:Translated&Box_2_1/Back"), model.selection("FACE", "Translation_1_1/MF:Translated&Box_2_1/Bottom")] +Shell_2 = model.addShell(Part_1_doc, Shell_2_objects) +model.do() + +Sewing_1 = model.addSewing(Part_1_doc, [model.selection("SHELL", "Shell_2_1"), model.selection("SHELL", "Shell_1_1")], 1e-07, allowNonManifold = False, alwaysCreateResult = True) +model.end() + +# sewing failed (result is a compound with the two input shells) +testResults(Sewing_1, model, 1, [2], [2], [4], [14], [12]) + +# ============================================================================= +# Test 2. Sew the same two shells allowing non-manifold results +# ============================================================================= +model.undo() +model.begin() + +Sewing_2 = model.addSewing(Part_1_doc, [model.selection("SHELL", "Shell_2_1"), model.selection("SHELL", "Shell_1_1")], 1e-07, allowNonManifold = True, alwaysCreateResult = True) +model.end() + +# sewing successful +testResults(Sewing_2, model, 1, [0], [1], [4], [13], [10]) diff --git a/src/FeaturesPlugin/doc/FeaturesPlugin.rst b/src/FeaturesPlugin/doc/FeaturesPlugin.rst index ac4a0fd5b..767922df8 100644 --- a/src/FeaturesPlugin/doc/FeaturesPlugin.rst +++ b/src/FeaturesPlugin/doc/FeaturesPlugin.rst @@ -37,6 +37,7 @@ Features plug-in provides a set of common topological operations. It implements revolutionFeature.rst revolutionFuseFeature.rst rotationFeature.rst + sewingFeature.rst symmetryFeature.rst transformationFeature.rst translationFeature.rst diff --git a/src/FeaturesPlugin/doc/TUI_sewingFeature.rst b/src/FeaturesPlugin/doc/TUI_sewingFeature.rst new file mode 100644 index 000000000..30f49113b --- /dev/null +++ b/src/FeaturesPlugin/doc/TUI_sewingFeature.rst @@ -0,0 +1,12 @@ + + .. _tui_create_sewing: + +Create Sewing +============= + +.. literalinclude:: examples/sewing.py + :linenos: + :language: python + +:download:`Download this script ` + diff --git a/src/FeaturesPlugin/doc/examples/sewing.py b/src/FeaturesPlugin/doc/examples/sewing.py new file mode 100644 index 000000000..52f2c3ad8 --- /dev/null +++ b/src/FeaturesPlugin/doc/examples/sewing.py @@ -0,0 +1,31 @@ +from salome.shaper import model + +model.begin() +partSet = model.moduleDocument() +Part_1 = model.addPart(partSet) +Part_1_doc = Part_1.document() +model.addParameter(Part_1_doc, "dx", '10') +model.addParameter(Part_1_doc, "alfa", '90') + +Box_1 = model.addBox(Part_1_doc, 10, 10, 10) +Box_2 = model.addBox(Part_1_doc, 10, 10, 10) +Translation_1 = model.addTranslation(Part_1_doc, [model.selection("SOLID", "Box_2_1")], axis = model.selection("EDGE", "PartSet/OX"), distance = "dx", keepSubResults = True) +Rotation_1 = model.addRotation(Part_1_doc, [model.selection("SOLID", "Translation_1_1")], axis = model.selection("EDGE", "[Box_1_1/Front][Box_1_1/Right]"), angle = "alfa", keepSubResults = True) + +Shell_1_objects = [model.selection("FACE", "Box_1_1/Top"), + model.selection("FACE", "Box_1_1/Left"), + model.selection("FACE", "Box_1_1/Right"), + model.selection("FACE", "Box_1_1/Back"), + model.selection("FACE", "Box_1_1/Bottom")] +Shell_1 = model.addShell(Part_1_doc, Shell_1_objects) + +Shell_2_objects = [model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Top"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Left"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Front"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Right"), + model.selection("FACE", "Rotation_1_1/MF:Rotated&Box_2_1/Bottom")] +Shell_2 = model.addShell(Part_1_doc, Shell_2_objects) + +Sewing_1 = model.addSewing(Part_1_doc, [model.selection("SHELL", "Shell_2_1"), model.selection("SHELL", "Shell_1_1")], 1e-07, allowNonManifold = False, alwaysCreateResult = True) + +model.end() diff --git a/src/FeaturesPlugin/doc/images/Sewing.png b/src/FeaturesPlugin/doc/images/Sewing.png new file mode 100644 index 000000000..4e5d26831 Binary files /dev/null and b/src/FeaturesPlugin/doc/images/Sewing.png differ diff --git a/src/FeaturesPlugin/doc/images/sewing_btn.png b/src/FeaturesPlugin/doc/images/sewing_btn.png new file mode 100644 index 000000000..f9d9ca78d Binary files /dev/null and b/src/FeaturesPlugin/doc/images/sewing_btn.png differ diff --git a/src/FeaturesPlugin/doc/sewingFeature.rst b/src/FeaturesPlugin/doc/sewingFeature.rst new file mode 100644 index 000000000..d4f2b50ef --- /dev/null +++ b/src/FeaturesPlugin/doc/sewingFeature.rst @@ -0,0 +1,49 @@ +.. |sewing_btn.icon| image:: images/sewing_btn.png + +.. _featureSewing: + +Sewing +====== + +Sewing feature unites several faces (possibly contained in a shell, solid or compound) +into one shell. Geometrically coincident (within a specified tolerance) edges (or parts +of edges) of different faces are replaced by one edge thus producing a shell or faces +with shared boundaries. + +To perform the Sewing in the active part: + +#. select in the Main Menu *Features - > Sewing* item or +#. click |sewing_btn.icon| **Sewing** button in the toolbar + +The following property panel will be opened: + +.. figure:: images/Sewing.png + :align: center + + **Sewing feature** + +Input fields: + +- **Objects** contains a list of objects selected in the Object Browser or in the Viewer, which will be sewn. +- **Tolerance** defines the tolerance to use for the Sewing operation. +- **Allow Non-Manifold** allows to create non-manifold shapes, if checked. +- **Always create a result** allows to always create a result shape, even if there was nothing sewn. + +**TUI Command**: + +.. py:function:: model.addSewing(Part_doc, objects, tolerance, allowNonManifold, createResult) + + :param part: The current part object. + :param list: A list of objects. + :param real: The tolerance value to be used for the sewing. + :param boolean: Defines whether to allow non-manifold results. + :param boolean: Defines whether to always create a result. + :return: Created object. + +Result +"""""" + +The Result of the operation will be a shape with all coincident edges being united: + + +**See Also** a sample TUI Script of :ref:`tui_create_sewing` operation. diff --git a/src/FeaturesPlugin/sewing_widget.xml b/src/FeaturesPlugin/sewing_widget.xml index 231e43b06..62956dfbd 100644 --- a/src/FeaturesPlugin/sewing_widget.xml +++ b/src/FeaturesPlugin/sewing_widget.xml @@ -9,11 +9,10 @@ - diff --git a/src/FeaturesPlugin/tests.set b/src/FeaturesPlugin/tests.set index 302daa49b..e6544e29e 100644 --- a/src/FeaturesPlugin/tests.set +++ b/src/FeaturesPlugin/tests.set @@ -529,6 +529,8 @@ SET(TEST_NAMES_PARA Test23885.py TestNormalToFace.py TestLoft.py + TestSewing_Manifold.py + TestSewing_NonManifold.py ) SET(TEST_NAMES_SEQ diff --git a/src/GeomAlgoAPI/GeomAlgoAPI_Sewing.h b/src/GeomAlgoAPI/GeomAlgoAPI_Sewing.h index 416ac84f2..5da273d74 100644 --- a/src/GeomAlgoAPI/GeomAlgoAPI_Sewing.h +++ b/src/GeomAlgoAPI/GeomAlgoAPI_Sewing.h @@ -32,10 +32,14 @@ class GeomAlgoAPI_Sewing : public GeomAlgoAPI_MakeShape { public: - /// Constructor. + /// Constructor (used by MakeShell). + /// \param[in] theShapes list of selected shapes. GEOMALGOAPI_EXPORT GeomAlgoAPI_Sewing(const ListOfShape& theShapes); - /// Constructor with additional arguments + /// Constructor with additional arguments (used by Sewing feature) + /// \param[in] theShapes list of selected shapes. + /// \param[in] theAllowNonManifold if True, non-manifold results are allowed. + /// \param[in] theTolerance tolerance value used for the sewing operation. GEOMALGOAPI_EXPORT GeomAlgoAPI_Sewing(const ListOfShape& theShapes, const bool theAllowNonManifold, const double theTolerance); /// \return the list of shapes modified from the shape \a theShape. @@ -45,7 +49,7 @@ public: ListOfShape& theHistory); protected: - bool myBuildShell; + bool myBuildShell; // whether algorithm is used by MakeShell or by Sewing private: /// Builds resulting shape.