Salome HOME
IMP 23612: EDF 14143 - Compute angle from 3 points
authoreap <eap@opencascade.com>
Thu, 29 Nov 2018 14:31:43 +0000 (17:31 +0300)
committereap <eap@opencascade.com>
Thu, 29 Nov 2018 14:31:43 +0000 (17:31 +0300)
26 files changed:
doc/salome/examples/measurements_ex04.py [new file with mode: 0644]
doc/salome/examples/tests.set
doc/salome/gui/SMESH/images/angle_measure.png [new file with mode: 0644]
doc/salome/gui/SMESH/images/basic_props.png
doc/salome/gui/SMESH/images/bnd_box.png
doc/salome/gui/SMESH/images/min_distance.png
doc/salome/gui/SMESH/input/face_groups_by_sharp_edges.rst
doc/salome/gui/SMESH/input/grouping_elements.rst
doc/salome/gui/SMESH/input/measurements.rst
doc/salome/gui/SMESH/input/modules.rst
doc/salome/gui/SMESH/input/tui_measurements.rst
idl/SMESH_Measurements.idl
idl/SMESH_Mesh.idl
resources/CMakeLists.txt
resources/mesh_angle_measure.png [new file with mode: 0644]
src/SMESHGUI/SMESHGUI.cxx
src/SMESHGUI/SMESHGUI_Measurements.cxx
src/SMESHGUI/SMESHGUI_Measurements.h
src/SMESHGUI/SMESHGUI_Operations.h
src/SMESHGUI/SMESHGUI_VTKUtils.cxx
src/SMESHGUI/SMESH_images.ts
src/SMESHGUI/SMESH_msg_en.ts
src/SMESH_I/SMESH_2smeshpy.cxx
src/SMESH_I/SMESH_Measurements_i.cxx
src/SMESH_I/SMESH_Measurements_i.hxx
src/SMESH_SWIG/smeshBuilder.py

diff --git a/doc/salome/examples/measurements_ex04.py b/doc/salome/examples/measurements_ex04.py
new file mode 100644 (file)
index 0000000..6524ac9
--- /dev/null
@@ -0,0 +1,26 @@
+# Angle measurement
+
+
+import salome
+salome.salome_init()
+from salome.smesh import smeshBuilder
+smesh =  smeshBuilder.New()
+
+# use smeshBuilder.GetAngle() to compute angle between 3 arbitrary points
+
+p0 = [1,0,0]
+p1 = [0,0,0]
+p2 = [0,1,0]
+
+a1 = smesh.GetAngle(p0, p1, p2)
+print("Right angle measure", a1 )
+
+# use Mesh.GetAngle() to compute angle between 3 nodes of a mesh
+
+mesh = smesh.Mesh()
+n0 = mesh.AddNode( *p0 )
+n1 = mesh.AddNode( *p1 )
+n2 = mesh.AddNode( *p2 )
+
+a2 = mesh.GetAngle( n0,n1,n2 )
+
index f894061..29910ed 100644 (file)
@@ -115,6 +115,8 @@ SET(GOOD_TESTS
   grouping_elements_ex09.py
   measurements_ex01.py
   measurements_ex02.py
+  measurements_ex03.py
+  measurements_ex04.py
   modifying_meshes_ex01.py
   modifying_meshes_ex02.py
   modifying_meshes_ex03.py
diff --git a/doc/salome/gui/SMESH/images/angle_measure.png b/doc/salome/gui/SMESH/images/angle_measure.png
new file mode 100644 (file)
index 0000000..80a2116
Binary files /dev/null and b/doc/salome/gui/SMESH/images/angle_measure.png differ
index bba200d..abf3aa3 100644 (file)
Binary files a/doc/salome/gui/SMESH/images/basic_props.png and b/doc/salome/gui/SMESH/images/basic_props.png differ
index d079eaa..4c0bbd3 100644 (file)
Binary files a/doc/salome/gui/SMESH/images/bnd_box.png and b/doc/salome/gui/SMESH/images/bnd_box.png differ
index 486dab3..294178f 100644 (file)
Binary files a/doc/salome/gui/SMESH/images/min_distance.png and b/doc/salome/gui/SMESH/images/min_distance.png differ
index 5fdee17..eed6996 100644 (file)
@@ -2,7 +2,7 @@
 Face Groups Separated By Sharp Edges
 ************************************
 
-**Face groups separated by sharp edges** operation distributes all faces of the mesh between groups using sharp edges and optionally existing 1D elements as group boundaries. Edges where more than two faces meet are always considered as a group boundary. The operation is available in **Mesh** menu.
+**Face groups separated by sharp edges** operation distributes all faces of the mesh among groups using sharp edges and optionally existing 1D elements as group boundaries. Edges where more than two faces meet are always considered as a group boundary. The operation is available in **Mesh** menu.
 
 The operation dialog looks as follows:
 
@@ -11,7 +11,7 @@ The operation dialog looks as follows:
 
 In this dialog box specify
 
-       * **Mesh** including the faces to distribute between groups.
+       * **Mesh** including the faces to distribute among groups.
         * **Sharp angle** in degrees, by which edges used as group boundaries are detected. An edge is considered as a group boundary if an angle between normals of adjacent faces is more than this angle.
        * Activate **Create edges** option if you wish that 1D elements to be created (if not yet exist) on the edges that served as group boundaries.
        * Activate **Use existing edges** option if you wish that existing 1D elements to be used as group boundaries.
index 57dcfdb..2846fa9 100644 (file)
@@ -27,7 +27,7 @@ The following ways of group creation are possible:
 
 * :ref:`Create group <creating_groups_page>` dialog allows creation of a group of any type: :ref:`Standalone group<standalone_group>`, :ref:`Group on geometry <group_on_geom>` and :ref:`Group on filter <group_on_filter>` using dedicated tabs.
 * :ref:`Create Groups from Geometry <create_groups_from_geometry_page>` dialog allows creation of several groups on geometry at once.
-* :doc:`face_groups_by_sharp_edges` operation distributes all faces of the mesh between groups using sharp edges and/or existing 1D elements as group boundaries.
+* :doc:`face_groups_by_sharp_edges` operation distributes all faces of the mesh among groups using sharp edges and/or existing 1D elements as group boundaries.
 * Standalone groups of all nodes and elements of the chosen sub-mesh (type of elements depends on dimension of sub-mesh geometry) can be created using **Mesh -> Construct Group** menu item (available from the context menu as well).
 * Standalone groups of any element type can be created basing on nodes of other groups - using :ref:`Group based on nodes of other groups <group_of_underlying_elements_page>` dialog.
 * Standalone groups can be created by applying :ref:`Boolean operations <using_operations_on_groups_page>` to other groups.
index ed1426a..248ea4f 100644 (file)
@@ -84,6 +84,17 @@ The result of calculation will be shown in the bottom area of the dialog.
        * As calculation result is a sum of lengths, areas and volumes of all mesh elements, the duplication is not taken into account; i.e. all duplicated elements (elements built on the same set of nodes) will be included into the result.
        * Similarly, intersection of elements is not taken into account.
 
-**See Also** a sample TUI Script of :ref:`tui_measurements_page`.
+.. _angle_anchor:
+
+Angle
+#####
 
+This operation measures angle defined by three nodes. The second of the specified nodes is a vertex of angle. 
 
+You can specify nodes either by clicking them in the Viewer or by typing their IDs in **Tree nodes** field. If the nodes are correctly specified, upon pressing **Compute** button the angle measure will be displayed and the angle will be shown in the Viewer.
+
+.. image:: ../images/angle_measure.png
+       :align: center
+
+
+**See Also** a sample TUI Script of :ref:`tui_measurements_page`.
index 45494b2..6c673f6 100644 (file)
@@ -168,6 +168,7 @@ Creating groups
    Mesh.MakeGroupByCriterion
    Mesh.MakeGroupByCriteria
    Mesh.MakeGroupByFilter
+   Mesh.FaceGroupsSeparatedByEdges
    Mesh.GetGroups
    Mesh.NbGroups
    Mesh.GetGroupNames
@@ -305,6 +306,7 @@ Measurements
    smeshBuilder.GetLength
    smeshBuilder.GetArea
    smeshBuilder.GetVolume
+   smeshBuilder.GetAngle
    Mesh.GetFreeBorders
    Mesh.MinDistance
    Mesh.GetMinDistance
@@ -315,6 +317,7 @@ Measurements
    Mesh.GetLength
    Mesh.GetArea
    Mesh.GetVolume
+   Mesh.GetAngle
 
 ****************
 Modifying meshes
index b3f5964..a4e0565 100644 (file)
@@ -33,3 +33,11 @@ Basic Properties
     :language: python
 
 :download:`Download this script <../../../examples/measurements_ex03.py>`
+
+Angle
+=====
+
+.. literalinclude:: ../../../examples/measurements_ex04.py
+    :language: python
+
+:download:`Download this script <../../../examples/measurements_ex04.py>`
index d3a9c77..bf8dc21 100644 (file)
@@ -74,6 +74,11 @@ module SMESH
      * gravity center of the source
      */
     PointStruct GravityCenter(in SMESH_IDSource source);
+
+    /*!
+     * angle in radians defined by 3 points <(p1,p2,p3)
+     */
+    double Angle(in PointStruct p1, in PointStruct p2, in PointStruct p3 );
   };
 };
 
index cb25bb1..c8c7bd7 100644 (file)
@@ -517,7 +517,7 @@ module SMESH
       raises (SALOME::SALOME_Exception);
 
     /*!
-     * Distribute all faces of the mesh between groups using sharp edges and optionally
+     * Distribute all faces of the mesh among groups using sharp edges and optionally
      * existing 1D elements as group boundaries.
      *  \param [in] sharpAngle - edge is considered sharp if an angle between normals of
      *              adjacent faces is more than \a sharpAngle in degrees.
index 2196b1b..638e99c 100755 (executable)
@@ -232,6 +232,7 @@ SET(SMESH_RESOURCES_FILES
   mesh_deflection.png
   mesh_offset.png
   mesh_face_groups_by_edges.png
+  mesh_angle_measure.png
 )
 
 INSTALL(FILES ${SMESH_RESOURCES_FILES} DESTINATION ${SALOME_SMESH_INSTALL_RES_DATA})
diff --git a/resources/mesh_angle_measure.png b/resources/mesh_angle_measure.png
new file mode 100644 (file)
index 0000000..d77b745
Binary files /dev/null and b/resources/mesh_angle_measure.png differ
index 6f4e1d7..b4a75cb 100644 (file)
@@ -3719,6 +3719,7 @@ bool SMESHGUI::OnGUIEvent( int theCommandID )
   case SMESHOp::OpPropertiesVolume:
   case SMESHOp::OpMinimumDistance:
   case SMESHOp::OpBoundingBox:
+  case SMESHOp::OpAngle:
     {
       int page = SMESHGUI_MeasureDlg::MinDistance;
       if ( theCommandID == SMESHOp::OpBoundingBox )
@@ -3729,6 +3730,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID )
         page = SMESHGUI_MeasureDlg::Area;
       else if ( theCommandID == SMESHOp::OpPropertiesVolume )
         page = SMESHGUI_MeasureDlg::Volume;
+      else if ( theCommandID == SMESHOp::OpAngle )
+        page = SMESHGUI_MeasureDlg::Angle;
 
       EmitSignalDeactivateDialog();
       SMESHGUI_MeasureDlg* dlg = new SMESHGUI_MeasureDlg( SMESHGUI::desktop(), page );
@@ -4063,6 +4066,7 @@ void SMESHGUI::initialize( CAM_Application* app )
   createSMESHAction( SMESHOp::OpPropertiesLength, "MEASURE_LENGTH",   "ICON_MEASURE_LENGTH" );
   createSMESHAction( SMESHOp::OpPropertiesArea,   "MEASURE_AREA",     "ICON_MEASURE_AREA" );
   createSMESHAction( SMESHOp::OpPropertiesVolume, "MEASURE_VOLUME",   "ICON_MEASURE_VOLUME" );
+  createSMESHAction( SMESHOp::OpAngle,            "MEASURE_ANGLE",    "ICON_MEASURE_ANGLE" );
 
   createSMESHAction( SMESHOp::OpHide,     "HIDE", "ICON_HIDE" );
   createSMESHAction( SMESHOp::OpShow,     "SHOW", "ICON_SHOW" );
@@ -4270,6 +4274,7 @@ void SMESHGUI::initialize( CAM_Application* app )
 
   createMenu( SMESHOp::OpMinimumDistance,  measureId,   -1 );
   createMenu( SMESHOp::OpBoundingBox,      measureId,   -1 );
+  createMenu( SMESHOp::OpAngle,            measureId,   -1 );
   createMenu( SMESHOp::OpPropertiesLength, basicPropId, -1 );
   createMenu( SMESHOp::OpPropertiesArea,   basicPropId, -1 );
   createMenu( SMESHOp::OpPropertiesVolume, basicPropId, -1 );
index 1f1d5aa..8e19cdd 100644 (file)
 #include "SMESHGUI.h"
 #include "SMESHGUI_IdValidator.h"
 #include "SMESHGUI_Utils.h"
+#include "SMESHGUI_MeshEditPreview.h"
 #include "SMESHGUI_VTKUtils.h"
 #include <SMESH_TypeFilter.hxx>
+#include <SMESH_MeshAlgos.hxx>
 #include <SMESH_LogicalFilter.hxx>
+#include <SMDS_Mesh.hxx>
+#include <SMDS_MeshNode.hxx>
 
 #include <LightApp_SelectionMgr.h>
 #include <SUIT_OverrideCursor.h>
@@ -59,6 +63,8 @@
 #include <VTKViewer_CellLocationsArray.h>
 #include <vtkProperty.h>
 
+#include <ElCLib.hxx>
+
 #include <SALOMEconfig.h>
 #include CORBA_SERVER_HEADER(SMESH_MeshEditor)
 #include CORBA_SERVER_HEADER(SMESH_Measurements)
@@ -1313,6 +1319,367 @@ void SMESHGUI_BasicProperties::clear()
 }
 
 /*!
+  \class SMESHGUI_Angle
+  \brief Angle measurement widget.
+  
+  Widget to calculate angle between 3 nodes.
+*/
+
+/*!
+  \brief Constructor.
+  \param parent parent widget
+*/
+SMESHGUI_Angle::SMESHGUI_Angle( QWidget* parent )
+  : QWidget( parent )
+{
+  // 3 nodes
+
+  QGroupBox* aNodesGrp = new QGroupBox( tr( "NODES_GROUP" ), this );
+  myNodes = new QLineEdit( aNodesGrp );
+  myNodes->setValidator( new SMESHGUI_IdValidator( this, 3 ));
+  QHBoxLayout* aNodesLayout = new QHBoxLayout( aNodesGrp );
+  aNodesLayout->addWidget( myNodes );
+  
+  // Compute button
+  QPushButton* aCompute = new QPushButton( tr( "COMPUTE" ), this );
+
+  // Angle
+
+  QGroupBox* aResultGrp = new QGroupBox( tr( "RESULT" ), this );
+
+  myResult = new QLineEdit;
+  myResult->setReadOnly( true );
+
+  QHBoxLayout* aResultLayout = new QHBoxLayout( aResultGrp );
+  aResultLayout->addWidget( myResult );
+  
+  // Layout
+
+  QGridLayout* aMainLayout = new QGridLayout( this );
+  aMainLayout->setMargin( MARGIN );
+  aMainLayout->setSpacing( SPACING );
+
+  aMainLayout->addWidget( aNodesGrp,   0, 0, 1, 2 );
+  aMainLayout->addWidget( aCompute,    1, 0 );
+  aMainLayout->addWidget( aResultGrp, 2, 0, 1, 2 );
+  aMainLayout->setColumnStretch( 1, 5 );
+  aMainLayout->setRowStretch( 3, 5 );
+
+  // Connections
+  connect( aCompute, SIGNAL( clicked() ), this, SLOT( compute() ));
+
+  // preview
+  myPreview = 0;
+  if ( SVTK_ViewWindow* aViewWindow = SMESH::GetViewWindow() )
+  {
+    myPreview = new SMESHGUI_MeshEditPreview( aViewWindow );
+    if ( myPreview && myPreview->GetActor() )
+      myPreview->GetActor()->GetProperty()->SetLineWidth( 5 );
+  }
+  myActor = 0;
+}
+
+SMESHGUI_Angle::~SMESHGUI_Angle()
+{
+  if ( myPreview )
+    delete myPreview;
+}
+
+/*!
+  \brief Setup selection mode
+*/
+void SMESHGUI_Angle::updateSelection()
+{
+  LightApp_SelectionMgr* selMgr = SMESHGUI::selectionMgr();
+
+  disconnect( selMgr, 0, this, 0 );
+  selMgr->clearFilters();
+
+  SMESH::SetPointRepresentation( true );
+  if ( SVTK_ViewWindow* aViewWindow = SMESH::GetViewWindow() )
+    aViewWindow->SetSelectionMode( NodeSelection );
+
+  connect( selMgr,  SIGNAL( currentSelectionChanged() ), this, SLOT( selectionChanged() ));
+  connect( myNodes, SIGNAL( textEdited( QString ) ),     this, SLOT( nodesEdited() ));
+
+  if ( myPoints.empty() )
+    selectionChanged();
+}
+
+/*!
+  \brief Called when selection is changed
+*/
+void SMESHGUI_Angle::selectionChanged()
+{
+  clear();
+  QString nodesString;
+
+  TColStd_IndexedMapOfInteger idsMap;
+  SALOME_ListIO selected;
+  SMESHGUI::selectionMgr()->selectedObjects( selected );
+  selected.Reverse(); // to keep order of selection
+
+  SALOME_ListIteratorOfListIO ioIterator( selected );
+  for ( ; ioIterator.More(); ioIterator.Next() )
+  {
+    Handle(SALOME_InteractiveObject) IO = ioIterator.Value();
+
+    idsMap.Clear();
+    if ( SVTK_Selector* selector = SMESH::GetViewWindow()->GetSelector() )
+      selector->GetIndex( IO, idsMap );
+
+    if ( SMESH_Actor* actor = SMESH::FindActorByEntry( IO->getEntry() ))
+    {
+      myActor = actor;
+      for ( int i = 1; i <= idsMap.Extent() && myPoints.size() < 3; ++i )
+        if ( addPointByActor( idsMap(i) ))
+          nodesString += QString(" %1").arg( idsMap(i) );
+      idsMap.Clear();
+    }
+    SMESH::SMESH_IDSource_var obj = SMESH::IObjectToInterface<SMESH::SMESH_IDSource>( IO );
+    if ( !CORBA::is_nil( obj ) )
+    {
+      myIDSrc = obj;
+      for ( int i = 1; i <= idsMap.Extent() && myPoints.size() < 3; ++i )
+        if ( addPointByIDSource( idsMap(i) ))
+          nodesString += QString(" %1").arg( idsMap(i) );
+    }
+  }
+
+  myNodes->setText( nodesString );
+}
+
+//=======================================================================
+//function : clear
+//purpose  : Erase preview and result
+//=======================================================================
+
+void SMESHGUI_Angle::clear()
+{
+  myPoints.clear();
+  myResult->clear();
+  if ( myPreview && myPreview->GetActor())
+  {
+    myPreview->GetActor()->SetVisibility( false );
+    if ( SVTK_ViewWindow* aViewWindow = SMESH::GetViewWindow() )
+      aViewWindow->Repaint();
+  }
+}
+
+//=======================================================================
+//function : addPointByActor
+//purpose  : append to myPoints XYZ got from myActor
+//=======================================================================
+
+bool SMESHGUI_Angle::addPointByActor( int id )
+{
+  size_t nbP = myPoints.size();
+
+  if ( myActor )
+  {
+    TVisualObjPtr obj = myActor->GetObject();
+    if ( SMDS_Mesh* mesh = obj->GetMesh() )
+      if ( const SMDS_MeshNode* node = mesh->FindNode( id ))
+      {
+        SMESH::PointStruct p = { node->X(), node->Y(), node->Z() };
+        myPoints.push_back( p );
+      }
+  }
+  return nbP < myPoints.size();
+}
+
+//=======================================================================
+//function : addPointByIDSource
+//purpose  : append to myPoints XYZ got from myIDSrc
+//=======================================================================
+
+bool SMESHGUI_Angle::addPointByIDSource( int id )
+{
+  size_t nbP = myPoints.size();
+
+  if ( !myIDSrc->_is_nil() )
+  {
+    SMESH::SMESH_Mesh_var mesh = myIDSrc->GetMesh();
+    if ( !mesh->_is_nil() )
+    {
+      SMESH::double_array_var xyz = mesh->GetNodeXYZ( id );
+      if ( xyz->length() == 3 )
+      {
+        SMESH::PointStruct p = { xyz[0], xyz[1], xyz[2] };
+        myPoints.push_back( p );
+      }
+    }
+  }
+  return nbP < myPoints.size();
+}
+
+//=======================================================================
+//function : nodesEdited
+//purpose  : SLOT called when the user types node IDs
+//=======================================================================
+
+void SMESHGUI_Angle::nodesEdited()
+{
+  clear();
+
+  TColStd_MapOfInteger ID;
+  QStringList ids = myNodes->text().split( " ", QString::SkipEmptyParts );
+  foreach ( QString idStr, ids )
+  {
+    int id = idStr.trimmed().toLong();
+    if (( !ID.Contains( id )) &&
+        ( addPointByActor( id ) || addPointByIDSource( id )))
+      ID.Add( id );
+  }
+
+  SVTK_Selector* selector = SMESH::GetViewWindow()->GetSelector();
+  if ( myActor && selector )
+  {
+    Handle(SALOME_InteractiveObject) IO = myActor->getIO();
+    selector->AddOrRemoveIndex( IO, ID, false );
+    if ( SVTK_ViewWindow* aViewWindow = SMESH::GetViewWindow() )
+      aViewWindow->highlight( IO, true, true );
+  }
+}
+
+//=======================================================================
+//function : compute
+//purpose  : SLOT. Compute angle and show preview
+//=======================================================================
+
+void SMESHGUI_Angle::compute()
+{
+  if ( myPoints.size() != 3 )
+    return;
+
+  // --------------
+  // compute angle
+  // --------------
+
+  SMESH::Measurements_var measure = SMESHGUI::GetSMESHGen()->CreateMeasurements();
+  double radians = measure->Angle( myPoints[0], myPoints[1], myPoints[2] );
+  measure->UnRegister();
+  if ( radians < 0 )
+    return;
+
+  int precision = SMESHGUI::resourceMgr()->integerValue( "SMESH", "length_precision", 6 );
+  myResult->setText( QString::number( radians * 180 / M_PI,
+                                      precision > 0 ? 'f' : 'g', qAbs( precision )));
+
+  // -------------
+  // show preview
+  // -------------
+
+  if ( !myPreview || !myPreview->GetActor() )
+    return;
+
+  SMESH::MeshPreviewStruct preveiwData;
+
+  const double     anglePerSeg = 5 * M_PI/180; // angle per an arc segment
+  const double arcRadiusFactor = 0.5;          // arc position, from p1
+
+  gp_Pnt p0 ( myPoints[0].x, myPoints[0].y, myPoints[0].z );
+  gp_Pnt p1 ( myPoints[1].x, myPoints[1].y, myPoints[1].z );
+  gp_Pnt p2 ( myPoints[2].x, myPoints[2].y, myPoints[2].z );
+  gp_Vec vec10( p1, p0 ), vec12( p1, p2 ), norm( vec10 ^ vec12 );
+
+  if ( norm.Magnitude() <= gp::Resolution() ) // 180 degrees
+    norm = getNormal( vec10 );
+
+  double     len10 = vec10.Magnitude();
+  double     len12 = vec12.Magnitude();
+  double    lenMax = Max( len10, len12 );
+  double arcRadius = arcRadiusFactor * lenMax;
+
+  p0 = p1.Translated( lenMax * vec10.Normalized() );
+  p2 = p1.Translated( lenMax * vec12.Normalized() );
+
+  gp_Circ arc( gp_Ax2( p1, norm, vec10 ), arcRadius );
+
+  int nbRadialSegmensts = ceil( radians / anglePerSeg ) + 1;
+  int           nbNodes = 3 + ( nbRadialSegmensts + 1 );
+
+  // coordinates
+  preveiwData.nodesXYZ.length( nbNodes );
+  int iP = 0;
+  for ( ; iP < nbRadialSegmensts + 1; ++iP )
+  {
+    double u = double( iP ) / nbRadialSegmensts * radians;
+    gp_Pnt p = ElCLib::Value( u, arc );
+    preveiwData.nodesXYZ[ iP ].x = p.X();
+    preveiwData.nodesXYZ[ iP ].y = p.Y();
+    preveiwData.nodesXYZ[ iP ].z = p.Z();
+  }
+  int iP0 = iP;
+  preveiwData.nodesXYZ[ iP ].x = p0.X();
+  preveiwData.nodesXYZ[ iP ].y = p0.Y();
+  preveiwData.nodesXYZ[ iP ].z = p0.Z();
+  int iP1 = ++iP;
+  preveiwData.nodesXYZ[ iP ].x = p1.X();
+  preveiwData.nodesXYZ[ iP ].y = p1.Y();
+  preveiwData.nodesXYZ[ iP ].z = p1.Z();
+  int iP2 = ++iP;
+  preveiwData.nodesXYZ[ iP ].x = p2.X();
+  preveiwData.nodesXYZ[ iP ].y = p2.Y();
+  preveiwData.nodesXYZ[ iP ].z = p2.Z();
+
+  // connectivity
+  preveiwData.elementConnectivities.length( 2 * ( 2 + nbRadialSegmensts ));
+  for ( int iSeg = 0; iSeg < nbRadialSegmensts; ++iSeg )
+  {
+    preveiwData.elementConnectivities[ iSeg * 2 + 0 ] = iSeg;
+    preveiwData.elementConnectivities[ iSeg * 2 + 1 ] = iSeg + 1;
+  }
+  int iSeg = nbRadialSegmensts;
+  preveiwData.elementConnectivities[ iSeg * 2 + 0 ] = iP0;
+  preveiwData.elementConnectivities[ iSeg * 2 + 1 ] = iP1;
+  ++iSeg;
+  preveiwData.elementConnectivities[ iSeg * 2 + 0 ] = iP1;
+  preveiwData.elementConnectivities[ iSeg * 2 + 1 ] = iP2;
+
+  // types
+  preveiwData.elementTypes.length( 2 + nbRadialSegmensts );
+  SMESH::ElementSubType type = { SMESH::EDGE, /*isPoly=*/false, /*nbNodesInElement=*/2 };
+  for ( CORBA::ULong i = 0; i < preveiwData.elementTypes.length(); ++i )
+    preveiwData.elementTypes[ i ] = type;
+
+  myPreview->SetData( preveiwData );
+}
+
+//================================================================================
+/*!
+ * \brief Return normal to a plane of drawing in the case of 180 degrees angle
+ */
+//================================================================================
+
+gp_Vec SMESHGUI_Angle::getNormal(const gp_Vec& vec10 )
+{
+  gp_XYZ norm;
+
+  // try to get normal by a face at the 2nd node
+  if ( myActor && myActor->GetObject()->GetMesh() )
+  {
+    QStringList ids = myNodes->text().split( " ", QString::SkipEmptyParts );
+    SMDS_Mesh* mesh = myActor->GetObject()->GetMesh();
+    if ( const SMDS_MeshNode* n = mesh->FindNode( ids[1].trimmed().toLong() ))
+    {
+      SMDS_ElemIteratorPtr faceIt = n->GetInverseElementIterator( SMDSAbs_Face );
+      while ( faceIt->more() )
+        if ( SMESH_MeshAlgos::FaceNormal( faceIt->next(), norm ))
+          return norm;
+    }
+  }
+  int iMinCoord = 1;
+  if ( vec10.Coord( iMinCoord ) > vec10.Y() ) iMinCoord = 2;
+  if ( vec10.Coord( iMinCoord ) > vec10.Z() ) iMinCoord = 3;
+
+  gp_Vec vec = vec10;
+  vec.SetCoord( iMinCoord, vec10.Coord( iMinCoord ) + 1. );
+
+  return vec ^ vec10;
+}
+
+/*!
   \class SMESHGUI_MeshInfoDlg
   \brief Centralized dialog box for the measurements
 */
@@ -1323,7 +1690,7 @@ void SMESHGUI_BasicProperties::clear()
   \param page specifies the dialog page to be shown at the start-up
 */
 SMESHGUI_MeasureDlg::SMESHGUI_MeasureDlg( QWidget* parent, int page )
-: QDialog( parent )
+  : QDialog( parent )
 {
   setModal( false );
   setAttribute( Qt::WA_DeleteOnClose, true );
@@ -1349,6 +1716,9 @@ SMESHGUI_MeasureDlg::SMESHGUI_MeasureDlg( QWidget* parent, int page )
   myBasicProps = new SMESHGUI_BasicProperties( myTabWidget );
   int aBasicPropInd = myTabWidget->addTab( myBasicProps, resMgr->loadPixmap( "SMESH", tr( "ICON_MEASURE_BASIC_PROPS" ) ), tr( "BASIC_PROPERTIES" ) );
 
+  myAngle = new SMESHGUI_Angle( myTabWidget );
+  int aAngleInd = myTabWidget->addTab( myAngle, resMgr->loadPixmap( "SMESH", tr( "ICON_MEASURE_ANGLE" ) ), tr( "ANGLE" ) );
+
   // buttons
   QPushButton* okBtn = new QPushButton( tr( "SMESH_BUT_OK" ), this );
   okBtn->setAutoDefault( true );
@@ -1376,6 +1746,8 @@ SMESHGUI_MeasureDlg::SMESHGUI_MeasureDlg( QWidget* parent, int page )
     anInd = aMinDistInd;
   } else if ( page == BoundingBox ) {
     anInd = aBndBoxInd;
+  } else if ( page == Angle ) {
+    anInd = aAngleInd;
   } else if ( page == Length || page == Area || page == Volume ) {
     myBasicProps->setMode( (SMESHGUI_BasicProperties::Mode)(page - Length) );
     anInd = aBasicPropInd;
@@ -1444,6 +1816,8 @@ void SMESHGUI_MeasureDlg::updateSelection()
     myMinDist->updateSelection();
   else if ( myTabWidget->currentIndex() == BoundingBox )
     myBndBox->updateSelection();
+  else if ( myTabWidget->currentWidget() == myAngle )
+    myAngle->updateSelection();
   else {
     myBndBox->erasePreview();
     myBasicProps->updateSelection();
@@ -1460,6 +1834,8 @@ void SMESHGUI_MeasureDlg::help()
     aHelpFile = "measurements.html#min-distance-anchor";
   } else if ( myTabWidget->currentIndex() == BoundingBox ) {
     aHelpFile = "measurements.html#bounding-box-anchor";
+  } else if ( myTabWidget->currentWidget() == myAngle ) {
+    aHelpFile = "measurements.html#angle-anchor";
   } else {
     aHelpFile = "measurements.html#basic-properties-anchor";
   }
index cadcf1f..3d583f0 100644 (file)
@@ -37,10 +37,13 @@ class SUIT_SelectionFilter;
 class SALOME_Actor;
 class SMESH_Actor;
 class SMESHGUI_IdValidator;
+class SMESHGUI_MeshEditPreview;
 
 #include <SALOMEconfig.h>
 #include CORBA_SERVER_HEADER(SMESH_Mesh)
 
+#include <gp_Vec.hxx>
+
 class SMESHGUI_EXPORT SMESHGUI_MinDistance : public QWidget
 {
   Q_OBJECT;
@@ -167,6 +170,40 @@ private:
   SUIT_SelectionFilter*     myFilter;
 };
 
+class SMESHGUI_EXPORT SMESHGUI_Angle : public QWidget
+{
+  Q_OBJECT;
+  
+public:
+
+  SMESHGUI_Angle( QWidget* = 0 );
+  ~SMESHGUI_Angle();
+
+  void deactivate();
+  void updateSelection();
+
+private slots:
+  void selectionChanged();
+  void nodesEdited();
+  void compute();
+  void clear();
+
+private:
+
+  bool addPointByActor( int id );
+  bool addPointByIDSource( int id );
+  gp_Vec getNormal(const gp_Vec& vec10 );
+
+  QLineEdit* myNodes;
+  QLineEdit* myResult;
+
+  SMESH::SMESH_IDSource_var myIDSrc;
+  SMESH_Actor*              myActor;
+
+  std::vector< SMESH::PointStruct > myPoints;
+  SMESHGUI_MeshEditPreview*         myPreview;
+};
+
 class SMESHGUI_EXPORT SMESHGUI_MeasureDlg : public QDialog
 { 
   Q_OBJECT;
@@ -180,7 +217,8 @@ public:
     BoundingBox,   //!< bounding box
     Length,        //!< length
     Area,          //!< area
-    Volume         //!< volume
+    Volume,        //!< volume
+    Angle
   };
 
   SMESHGUI_MeasureDlg( QWidget* = 0, int = MinDistance );
@@ -203,6 +241,7 @@ private:
   SMESHGUI_MinDistance* myMinDist;   
   SMESHGUI_BoundingBox* myBndBox;
   SMESHGUI_BasicProperties* myBasicProps;
+  SMESHGUI_Angle*       myAngle;
 };
 
 #endif // SMESHGUI_MEASUREMENTS_H
index b4ccb9d..97f7938 100644 (file)
@@ -190,6 +190,7 @@ namespace SMESHOp {
     OpPropertiesVolume       = 5002,   // MENU MEASUREMENTS - BASIC PROPERTIES - VOLUME
     OpMinimumDistance        = 5003,   // MENU MEASUREMENTS - MINIMUM DISTANCE
     OpBoundingBox            = 5004,   // MENU MEASUREMENTS - BOUNDING BOX
+    OpAngle                  = 5005,   // MENU MEASUREMENTS - ANGLE
     // Hypothesis ---------------------//--------------------------------
     OpEditHypothesis         = 6000,   // POPUP MENU - EDIT HYPOTHESIS
     OpUnassign               = 6001,   // POPUP MENU - UNASSIGN
index b74d29e..cf0e475 100644 (file)
@@ -923,7 +923,7 @@ namespace SMESH
         aCollection->InitTraversal();
         while ( vtkActor *anAct = aCollection->GetNextActor() ) {
           if ( SMESH_Actor *anActor = dynamic_cast<SMESH_Actor*>(anAct) ) {
-           anActor->UpdateSelectionProps();
+            anActor->UpdateSelectionProps();
           }
         }
       }
index 50b3388..0a09a43 100644 (file)
             <translation>mesh_bounding_box.png</translation>
         </message>
         <message>
+            <source>ICON_MEASURE_ANGLE</source>
+            <translation>mesh_angle_measure.png</translation>
+        </message>
+        <message>
             <source>ICON_SHOW</source>
             <translation>mesh_show.png</translation>
         </message>
index b6f1fa6..209a6bd 100644 (file)
         <translation>Volume</translation>
     </message>
     <message>
+        <source>MEN_MEASURE_ANGLE</source>
+        <translation>Angle</translation>
+    </message>
+    <message>
+        <source>STB_MEASURE_ANGLE</source>
+        <translation>Measure angle defined by three nodes</translation>
+    </message>
+    <message>
+        <source>TOP_MEASURE_ANGLE</source>
+        <translation>Angle</translation>
+    </message>
+    <message>
         <source>MEN_MOVE</source>
         <translation>Move Node</translation>
     </message>
@@ -3617,7 +3629,7 @@ Use Display Entity menu command to show them.
         <translation>Create groups of entities basing on nodes of other groups</translation>
     </message>
     <message>
-        <source>STB_UNDERLYING_ELEMS</source>
+        <source>STB_FACE_GROUPS_BY_EDGES</source>
         <translation>Create face groups separated by sharp edges</translation>
     </message>
     <message>
@@ -8109,6 +8121,21 @@ as they are of improper type:
     </message>
 </context>
 <context>
+    <name>SMESHGUI_Angle</name>
+    <message>
+        <source>NODES_GROUP</source>
+        <translation>Three nodes</translation>
+    </message>
+    <message>
+        <source>RESULT</source>
+        <translation>Angle in degrees</translation>
+    </message>
+    <message>
+        <source>COMPUTE</source>
+        <translation>Compute</translation>
+    </message>
+</context>
+<context>
     <name>SMESHGUI_CopyMeshDlg</name>
     <message>
         <source>OBJECT_NAME</source>
@@ -8164,6 +8191,10 @@ with red in the Object Browser.</translation>
         <source>BASIC_PROPERTIES</source>
         <translation>Basic Properties</translation>
     </message>
+    <message>
+        <source>ANGLE</source>
+        <translation>Angle</translation>
+    </message>
 </context>
 <context>
     <name>SMESHGUI_BoundingBox</name>
index cef6517..e76d1b3 100644 (file)
@@ -2478,7 +2478,7 @@ void _pyMeshEditor::Process( const Handle(_pyCommand)& theCommand)
       "MergeElements","MergeEqualElements","SewFreeBorders","SewConformFreeBorders",
       "FindCoincidentFreeBorders", "SewCoincidentFreeBorders",
       "SewBorderToSide","SewSideElements","ChangeElemNodes","GetLastCreatedNodes",
-      "GetLastCreatedElems",
+      "GetLastCreatedElems", "FaceGroupsSeparatedByEdges",
       "MirrorMakeMesh","MirrorObjectMakeMesh","TranslateMakeMesh","TranslateObjectMakeMesh",
       "Scale","ScaleMakeMesh","RotateMakeMesh","RotateObjectMakeMesh","MakeBoundaryMesh",
       "MakeBoundaryElements", "SplitVolumesIntoTetra","SplitHexahedraIntoPrisms",
index 2fcac5a..45f5f0b 100644 (file)
@@ -346,3 +346,30 @@ SMESH::PointStruct Measurements_i::GravityCenter(SMESH::SMESH_IDSource_ptr theSo
 
   return grCenter;
 }
+
+//=======================================================================
+//function : Angle
+//purpose  : Return angle in radians defined by 3 points <(p1,p2,p3)
+//=======================================================================
+
+CORBA::Double Measurements_i::Angle(const SMESH::PointStruct& p1,
+                                    const SMESH::PointStruct& p2,
+                                    const SMESH::PointStruct& p3 )
+{
+  gp_Vec v1( p1.x - p2.x, p1.y - p2.y, p1.z - p2.z );
+  gp_Vec v2( p3.x - p2.x, p3.y - p2.y, p3.z - p2.z );
+
+  double angle = -1;
+
+  try
+  {
+    angle = v1.Angle( v2 );
+  }
+  catch(...)
+  {
+  }
+  if ( isnan( angle ))
+    angle = -1;
+
+  return angle;
+}
index bb696eb..3ba642c 100644 (file)
@@ -78,6 +78,13 @@ namespace SMESH
      * gravity center of the source
      */
     SMESH::PointStruct GravityCenter(SMESH::SMESH_IDSource_ptr  theSource);
+
+    /*!
+     * angle in radians defined by 3 points <(p1,p2,p3)
+     */
+    CORBA::Double Angle(const SMESH::PointStruct& p1,
+                        const SMESH::PointStruct& p2,
+                        const SMESH::PointStruct& p3 );
   };
 }
 
index 7037fd2..35198fe 100755 (executable)
@@ -1443,6 +1443,28 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ):
         aMeasurements.UnRegister()
         return pointStruct.x, pointStruct.y, pointStruct.z
 
+    def GetAngle(self, p1, p2, p3 ):
+        """
+        Computes a radian measure of an angle defined by 3 points: <(p1,p2,p3)
+
+        Parameters:            
+                p1,p2,p3: coordinates of 3 points defined by either SMESH.PointStruct 
+                          or list [x,y,z]
+
+        Returns:        
+            Angle in radians
+        """
+        if isinstance( p1, list ): p1 = PointStruct(*p1)
+        if isinstance( p2, list ): p2 = PointStruct(*p2)
+        if isinstance( p3, list ): p3 = PointStruct(*p3)
+
+        aMeasurements = self.CreateMeasurements()
+        angle = aMeasurements.Angle(p1,p2,p3)
+        aMeasurements.UnRegister()
+
+        return angle
+
+
     pass # end of class smeshBuilder
 
 import omniORB
@@ -2887,7 +2909,7 @@ class Mesh(metaclass = MeshMeta):
 
     def FaceGroupsSeparatedByEdges( self, sharpAngle, createEdges=False, useExistingEdges=False ):
         """
-        Distribute all faces of the mesh between groups using sharp edges and optionally
+        Distribute all faces of the mesh among groups using sharp edges and optionally
         existing 1D elements as group boundaries.
 
         Parameters:
@@ -2896,7 +2918,7 @@ class Mesh(metaclass = MeshMeta):
                 createEdges (boolean): to create 1D elements for detected sharp edges.
                 useExistingEdges (boolean): to use existing edges as group boundaries
         Returns:
-                ListOfGroups - the created groups
+                ListOfGroups - the created :class:`groups <SMESH.SMESH_Group>`
         """
         sharpAngle,Parameters,hasVars = ParseParameters( sharpAngle )
         self.mesh.SetParameters(Parameters)
@@ -6826,6 +6848,20 @@ class Mesh(metaclass = MeshMeta):
             volume = self.FunctorValue(SMESH.FT_Volume3D, elemId)
         return volume
 
+    def GetAngle(self, node1, node2, node3 ):
+        """
+        Computes a radian measure of an angle defined by 3 nodes: <(node1,node2,node3)
+
+        Parameters:            
+                node1,node2,node3: IDs of the three nodes
+
+        Returns:        
+            Angle in radians
+        """
+        return self.smeshpyD.GetAngle( self.GetNodeXYZ( node1 ),
+                                       self.GetNodeXYZ( node2 ),
+                                       self.GetNodeXYZ( node3 ))
+
     def GetMaxElementLength(self, elemId):
         """
         Get maximum element length.