Salome HOME
Update copyrights
[modules/smesh.git] / src / SMESHGUI / SMESHGUI_Measurements.cxx
index ff9394490d5982e07b2b8f9d5bc1abd4f11a2bea..7c9cecec3127f26636729255121706a221434b7b 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2012  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2019  CEA/DEN, EDF R&D, OPEN CASCADE
 //
 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
@@ -6,7 +6,7 @@
 // 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.
+// 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
 #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>
 #include <SUIT_ResourceMgr.h>
 #include <SVTK_ViewWindow.h>
-#include <SALOME_ListIteratorOfListIO.hxx>
+#include <SALOME_ListIO.hxx>
 
 #include <QButtonGroup>
 #include <QGridLayout>
@@ -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)
@@ -167,11 +173,11 @@ SMESHGUI_MinDistance::SMESHGUI_MinDistance( QWidget* parent )
   aSOrigin->setChecked( true );
 #ifndef MINDIST_ENABLE_ELEMENT
   aFElem->setEnabled( false );   // NOT AVAILABLE YET
-  aSElem->setEnabled( false );   // NOT AVAILABLE YET
+  //aSElem->setEnabled( false );   // NOT AVAILABLE YET
 #endif
 #ifndef MINDIST_ENABLE_OBJECT
   aFObject->setEnabled( false ); // NOT AVAILABLE YET
-  aSObject->setEnabled( false ); // NOT AVAILABLE YET
+  //aSObject->setEnabled( false ); // NOT AVAILABLE YET
 #endif
   myDX->setReadOnly( true );
   myDY->setReadOnly( true );
@@ -198,6 +204,7 @@ SMESHGUI_MinDistance::SMESHGUI_MinDistance( QWidget* parent )
   clear();
 
   //setTarget( FirstTgt );
+  selectionChanged();
 }
 
 /*!
@@ -362,11 +369,12 @@ void SMESHGUI_MinDistance::createPreview( double x1, double y1, double z1, doubl
   aCells->Delete();
   // create actor
   vtkDataSetMapper* aMapper = vtkDataSetMapper::New();
-  aMapper->SetInput( aGrid );
+  aMapper->SetInputData( aGrid );
   aGrid->Delete();
   myPreview = SALOME_Actor::New();
   myPreview->PickableOff();
   myPreview->SetMapper( aMapper );
+  myPreview->SetResolveCoincidentTopology(true);
   aMapper->Delete();
   vtkProperty* aProp = vtkProperty::New();
   aProp->SetRepresentationToWireframe();
@@ -500,13 +508,21 @@ void SMESHGUI_MinDistance::secondEdited()
   setTarget( SecondTgt );
   if ( sender() == mySecondTgt )
     clear();
+  QString text = mySecondTgt->text();
+  if ( !mySecondActor )
+  {
+    selectionChanged();
+    mySecondTgt->setText( text );
+  }
   SVTK_Selector* selector = SMESH::GetViewWindow()->GetSelector();
   if ( mySecondActor && selector ) {
     Handle(SALOME_InteractiveObject) IO = mySecondActor->getIO();
     if ( mySecond->checkedId() == NodeTgt || mySecond->checkedId() == ElementTgt ) {
-      TColStd_MapOfInteger ID;
-      ID.Add( mySecondTgt->text().toLong() );
-      selector->AddOrRemoveIndex( IO, ID, false );
+      if ( !text.isEmpty() ) {
+        TColStd_MapOfInteger ID;
+        ID.Add( text.toLong() );
+        selector->AddOrRemoveIndex( IO, ID, false );
+      }
     }
     if ( SVTK_ViewWindow* aViewWindow = SMESH::GetViewWindow() )
       aViewWindow->highlight( IO, true, true );
@@ -519,8 +535,8 @@ void SMESHGUI_MinDistance::secondEdited()
 void SMESHGUI_MinDistance::compute()
 {
   SUIT_OverrideCursor wc;
-  SMESH::SMESH_IDSource_var s1;
-  SMESH::SMESH_IDSource_var s2;
+  SMESH::IDSource_wrap s1;
+  SMESH::IDSource_wrap s2;
   bool isOrigin = mySecond->checkedId() == OriginTgt;
 
   // process first target
@@ -538,6 +554,7 @@ void SMESHGUI_MinDistance::compute()
     }
     else {
       s1 = myFirstSrc;
+      s1->Register();
     }
   }
 
@@ -556,6 +573,7 @@ void SMESHGUI_MinDistance::compute()
     }
     else {
       s2 = mySecondSrc;
+      s2->Register();
     }
   }
 
@@ -577,10 +595,14 @@ void SMESHGUI_MinDistance::compute()
     if ( isOrigin ) {
       x2 = y2 = z2 = 0.;
     }
-    else {
+    else if ( mySecond->checkedId() == NodeTgt ) {
       coord = s2->GetMesh()->GetNodeXYZ( result.node2 );
       x2 = coord[0]; y2 = coord[1]; z2 = coord[2];
     }
+    else
+    {
+      x2 = result.maxX; y2 = result.maxY; z2 = result.maxZ; 
+    }
     createPreview( x1, y1, z1, x2, y2, z2 );
     displayPreview();
   }
@@ -758,7 +780,8 @@ void SMESHGUI_BoundingBox::updateSelection()
 
   sourceEdited();
 
-  //selectionChanged();
+  if ( mySource->text().isEmpty() )
+    selectionChanged();
 }
 
 /*!
@@ -879,7 +902,7 @@ void SMESHGUI_BoundingBox::createPreview( double minX, double maxX, double minY,
   aCells->Delete();
   // create actor
   vtkDataSetMapper* aMapper = vtkDataSetMapper::New();
-  aMapper->SetInput( aGrid );
+  aMapper->SetInputData( aGrid );
   aGrid->Delete();
   myPreview = SALOME_Actor::New();
   myPreview->PickableOff();
@@ -1035,14 +1058,17 @@ void SMESHGUI_BoundingBox::compute()
   }
   else {
     srcList->length( mySrc.count() );
-    for( int i = 0; i < mySrc.count(); i++ )
+    for( int i = 0; i < mySrc.count(); i++ ) {
       srcList[i] = mySrc[i];
+      mySrc[i]->Register();
+    }
   }
   if ( srcList->length() > 0 ) {
     // compute bounding box
     int precision = SMESHGUI::resourceMgr()->integerValue( "SMESH", "length_precision", 6 );
     SMESH::Measurements_var measure = SMESHGUI::GetSMESHGen()->CreateMeasurements();
     SMESH::Measure result = measure->BoundingBox( srcList.in() );
+    SALOME::UnRegister( srcList );
     measure->UnRegister();
     myXmin->setText( QString::number( result.minX, precision > 0 ? 'f' : 'g', qAbs( precision ) ) );
     myXmax->setText( QString::number( result.maxX, precision > 0 ? 'f' : 'g', qAbs( precision ) ) );
@@ -1080,6 +1106,583 @@ void SMESHGUI_BoundingBox::clear()
   erasePreview();
 }
 
+/*!
+  \class SMESHGUI_BasicProperties
+  \brief basic properties measurement widget.
+  
+  Widget to calculate length, area or volume for the selected object(s).
+*/
+
+/*!
+  \brief Constructor.
+  \param parent parent widget
+*/
+SMESHGUI_BasicProperties::SMESHGUI_BasicProperties( QWidget* parent )
+: QWidget( parent )
+{
+  // Property (length, area or volume)
+  QGroupBox* aPropertyGrp = new QGroupBox( tr( "PROPERTY" ), this );
+
+  QRadioButton* aLength = new QRadioButton( tr( "LENGTH" ), aPropertyGrp );
+  QRadioButton* anArea = new QRadioButton( tr( "AREA" ), aPropertyGrp );
+  QRadioButton* aVolume = new QRadioButton( tr( "VOLUME" ), aPropertyGrp );
+
+  myMode = new QButtonGroup( this );
+  myMode->addButton( aLength, Length );
+  myMode->addButton( anArea, Area );
+  myMode->addButton( aVolume, Volume );
+
+  QHBoxLayout* aPropertyLayout = new QHBoxLayout;
+  aPropertyLayout->addWidget( aLength );
+  aPropertyLayout->addWidget( anArea );
+  aPropertyLayout->addWidget( aVolume );
+
+  aPropertyGrp->setLayout( aPropertyLayout );
+
+  // Source object
+  QGroupBox* aSourceGrp = new QGroupBox( tr( "SOURCE_MESH_SUBMESH_GROUP" ), this );
+
+  mySource = new QLineEdit( aSourceGrp );
+  mySource->setReadOnly( true );
+    
+  QHBoxLayout* aSourceLayout = new QHBoxLayout;
+  aSourceLayout->addWidget( mySource );
+  
+  aSourceGrp->setLayout( aSourceLayout );
+
+  // Compute button
+  QPushButton* aCompute = new QPushButton( tr( "COMPUTE" ), this );
+
+  // Result of computation (length, area or volume)
+  myResultGrp = new QGroupBox( this );
+
+  myResult = new QLineEdit;
+  myResult->setReadOnly( true );
+
+  QHBoxLayout* aResultLayout = new QHBoxLayout;
+  aResultLayout->addWidget( myResult );
+  
+  myResultGrp->setLayout( aResultLayout );
+
+  // Layout
+  QGridLayout* aMainLayout = new QGridLayout( this );
+  aMainLayout->setMargin( MARGIN );
+  aMainLayout->setSpacing( SPACING );
+
+  aMainLayout->addWidget( aPropertyGrp, 0, 0, 1, 2 );
+  aMainLayout->addWidget( aSourceGrp, 1, 0, 1, 2 );
+  aMainLayout->addWidget( aCompute,   2, 0 );
+  aMainLayout->addWidget( myResultGrp, 3, 0, 1, 2 );
+  aMainLayout->setColumnStretch( 1, 5 );
+  aMainLayout->setRowStretch( 4, 5 );
+
+  // Initial state
+  setMode( Length );
+  
+  // Connections
+  connect( myMode, SIGNAL( buttonClicked( int ) ),  this, SLOT( modeChanged( int ) ) );
+  connect( aCompute, SIGNAL( clicked() ), this, SLOT( compute() ) );
+  
+  // Selection filter
+  QList<SUIT_SelectionFilter*> filters;
+  filters.append( new SMESH_TypeFilter( SMESH::MESHorSUBMESH ) );
+  filters.append( new SMESH_TypeFilter( SMESH::GROUP ) );
+  myFilter = new SMESH_LogicalFilter( filters, SMESH_LogicalFilter::LO_OR );
+}
+
+/*!
+  \brief Destructor
+*/
+SMESHGUI_BasicProperties::~SMESHGUI_BasicProperties()
+{
+}
+
+/*!
+  \brief Sets the measurement mode.
+  \param theMode the mode to set (length, area or volume meausurement)
+*/
+void SMESHGUI_BasicProperties::setMode( const Mode theMode )
+{
+  QRadioButton* aButton = qobject_cast<QRadioButton*>( myMode->button( theMode ) );
+  if ( aButton ) {
+    aButton->setChecked( true );
+    modeChanged( theMode );
+  }
+}
+
+/*!
+  \brief Setup the selection mode.
+*/
+void SMESHGUI_BasicProperties::updateSelection()
+{
+  LightApp_SelectionMgr* selMgr = SMESHGUI::selectionMgr();
+
+  disconnect( selMgr, 0, this, 0 );
+  selMgr->clearFilters();
+  
+  SMESH::SetPointRepresentation( false );
+  if ( SVTK_ViewWindow* aViewWindow = SMESH::GetViewWindow() ) {
+    aViewWindow->SetSelectionMode( ActorSelection );
+  }
+  selMgr->installFilter( myFilter );
+  
+  connect( selMgr, SIGNAL( currentSelectionChanged() ), this, SLOT( selectionChanged() ) );
+
+  if ( mySource->text().isEmpty() )
+    selectionChanged();
+}
+
+/*!
+  \brief Deactivate widget
+*/
+void SMESHGUI_BasicProperties::deactivate()
+{
+  disconnect( SMESHGUI::selectionMgr(), 0, this, 0 );
+}
+
+/*!
+  \brief Called when selection is changed
+*/
+void SMESHGUI_BasicProperties::selectionChanged()
+{
+  SUIT_OverrideCursor wc;
+
+  SALOME_ListIO selected;
+  SMESHGUI::selectionMgr()->selectedObjects( selected );
+
+  if ( selected.Extent() == 1 ) {
+    Handle(SALOME_InteractiveObject) IO = selected.First();
+    SMESH::SMESH_IDSource_var obj = SMESH::IObjectToInterface<SMESH::SMESH_IDSource>( IO );
+    if ( !CORBA::is_nil( obj ) ) {
+      mySrc = obj;
+
+      QString aName;
+      SMESH::GetNameOfSelectedIObjects( SMESHGUI::selectionMgr(), aName );
+      mySource->setText( aName );
+    }
+  }
+
+  clear();
+}
+
+/*!
+  \brief Called when the measurement mode selection is changed.
+  \param theMode the selected mode
+*/
+void SMESHGUI_BasicProperties::modeChanged( int theMode )
+{
+  clear();
+
+  if ( theMode == Length ) {
+    myResultGrp->setTitle( tr("LENGTH") );
+  } else if ( theMode == Area ) {
+    myResultGrp->setTitle( tr("AREA") );
+  } else if ( theMode == Volume ) {
+    myResultGrp->setTitle( tr("VOLUME") );
+  }
+}
+
+/*!
+  \brief Calculate length, area or volume for the selected object(s)
+*/
+void SMESHGUI_BasicProperties::compute()
+{
+  SUIT_OverrideCursor wc;
+
+  SMESH::SMESH_IDSource_var source;
+
+  if ( !CORBA::is_nil( mySrc ) ) {
+    // compute
+    int precision = SMESHGUI::resourceMgr()->integerValue( "SMESH", "length_precision", 6 );
+    SMESH::Measurements_var measure = SMESHGUI::GetSMESHGen()->CreateMeasurements();
+
+    double result = 0;
+
+    if ( myMode->checkedId() == Length ) {
+      result = measure->Length( mySrc.in() );
+    } else if ( myMode->checkedId() == Area ) {
+      result = measure->Area( mySrc.in() );
+    } else if ( myMode->checkedId() == Volume ) {
+      result = measure->Volume( mySrc.in() );
+    }
+    
+    measure->UnRegister();
+
+    myResult->setText( QString::number( result, precision > 0 ? 'f' : 'g', qAbs( precision ) ) );
+  } else {
+    clear();
+  }
+}
+
+/*!
+  \brief Reset the widget to the initial state (nullify the result field)
+*/
+void SMESHGUI_BasicProperties::clear()
+{
+  myResult->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
@@ -1091,7 +1694,7 @@ void SMESHGUI_BoundingBox::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 );
@@ -1105,12 +1708,20 @@ SMESHGUI_MeasureDlg::SMESHGUI_MeasureDlg( QWidget* parent, int page )
   // min distance
 
   myMinDist = new SMESHGUI_MinDistance( myTabWidget );
-  myTabWidget->addTab( myMinDist, resMgr->loadPixmap( "SMESH", tr( "ICON_MEASURE_MIN_DIST" ) ), tr( "MIN_DIST" ) );
+  int aMinDistInd = myTabWidget->addTab( myMinDist, resMgr->loadPixmap( "SMESH", tr( "ICON_MEASURE_MIN_DIST" ) ), tr( "MIN_DIST" ) );
 
   // bounding box
   
   myBndBox = new SMESHGUI_BoundingBox( myTabWidget );
-  myTabWidget->addTab( myBndBox, resMgr->loadPixmap( "SMESH", tr( "ICON_MEASURE_BND_BOX" ) ), tr( "BND_BOX" ) );
+  int aBndBoxInd = myTabWidget->addTab( myBndBox, resMgr->loadPixmap( "SMESH", tr( "ICON_MEASURE_BND_BOX" ) ), tr( "BND_BOX" ) );
+
+  // basic properties
+  
+  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 );
@@ -1134,7 +1745,21 @@ SMESHGUI_MeasureDlg::SMESHGUI_MeasureDlg( QWidget* parent, int page )
   l->addStretch();
   l->addLayout( btnLayout );
 
-  myTabWidget->setCurrentIndex( qMax( (int)MinDistance, qMin( (int)BoundingBox, page ) ) );
+  int anInd = -1;
+  if ( page == MinDistance ) {
+    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;
+  }
+
+  if ( anInd >= 0 ) {
+    myTabWidget->setCurrentIndex( anInd );
+  }
 
   connect( okBtn,       SIGNAL( clicked() ),              this, SLOT( reject() ) );
   connect( helpBtn,     SIGNAL( clicked() ),              this, SLOT( help() ) );
@@ -1195,7 +1820,12 @@ 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();
+  }
 }
 
 /*!
@@ -1203,9 +1833,18 @@ void SMESHGUI_MeasureDlg::updateSelection()
 */
 void SMESHGUI_MeasureDlg::help()
 {
-  SMESH::ShowHelpFile( myTabWidget->currentIndex() == MinDistance ?
-                       "measurements_page.html#min_distance_anchor" : 
-                       "measurements_page.html#bounding_box_anchor" );
+  QString aHelpFile;
+  if ( myTabWidget->currentIndex() == MinDistance ) {
+    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";
+  }
+
+  SMESH::ShowHelpFile( aHelpFile );
 }
 
 /*!
@@ -1224,6 +1863,7 @@ void SMESHGUI_MeasureDlg::activate()
 */
 void SMESHGUI_MeasureDlg::deactivate()
 {
+  myBasicProps->deactivate();
   myMinDist->deactivate();
   myBndBox->deactivate();
   myTabWidget->setEnabled( false );