From: eap Date: Thu, 29 Nov 2018 14:31:43 +0000 (+0300) Subject: IMP 23612: EDF 14143 - Compute angle from 3 points X-Git-Tag: V9_3_0a1~38 X-Git-Url: http://git.salome-platform.org/gitweb/?p=modules%2Fsmesh.git;a=commitdiff_plain;h=48b83422af7a5230409e39f4f2ece322b199128b IMP 23612: EDF 14143 - Compute angle from 3 points --- diff --git a/doc/salome/examples/measurements_ex04.py b/doc/salome/examples/measurements_ex04.py new file mode 100644 index 000000000..6524ac97a --- /dev/null +++ b/doc/salome/examples/measurements_ex04.py @@ -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 ) + diff --git a/doc/salome/examples/tests.set b/doc/salome/examples/tests.set index f8940611d..29910eda8 100644 --- a/doc/salome/examples/tests.set +++ b/doc/salome/examples/tests.set @@ -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 index 000000000..80a2116e3 Binary files /dev/null and b/doc/salome/gui/SMESH/images/angle_measure.png differ diff --git a/doc/salome/gui/SMESH/images/basic_props.png b/doc/salome/gui/SMESH/images/basic_props.png index bba200d09..abf3aa32c 100644 Binary files a/doc/salome/gui/SMESH/images/basic_props.png and b/doc/salome/gui/SMESH/images/basic_props.png differ diff --git a/doc/salome/gui/SMESH/images/bnd_box.png b/doc/salome/gui/SMESH/images/bnd_box.png index d079eaaa4..4c0bbd3e6 100644 Binary files a/doc/salome/gui/SMESH/images/bnd_box.png and b/doc/salome/gui/SMESH/images/bnd_box.png differ diff --git a/doc/salome/gui/SMESH/images/min_distance.png b/doc/salome/gui/SMESH/images/min_distance.png index 486dab376..294178f2a 100644 Binary files a/doc/salome/gui/SMESH/images/min_distance.png and b/doc/salome/gui/SMESH/images/min_distance.png differ diff --git a/doc/salome/gui/SMESH/input/face_groups_by_sharp_edges.rst b/doc/salome/gui/SMESH/input/face_groups_by_sharp_edges.rst index 5fdee17c5..eed6996b4 100644 --- a/doc/salome/gui/SMESH/input/face_groups_by_sharp_edges.rst +++ b/doc/salome/gui/SMESH/input/face_groups_by_sharp_edges.rst @@ -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. diff --git a/doc/salome/gui/SMESH/input/grouping_elements.rst b/doc/salome/gui/SMESH/input/grouping_elements.rst index 57dcfdb8c..2846fa94b 100644 --- a/doc/salome/gui/SMESH/input/grouping_elements.rst +++ b/doc/salome/gui/SMESH/input/grouping_elements.rst @@ -27,7 +27,7 @@ The following ways of group creation are possible: * :ref:`Create group ` dialog allows creation of a group of any type: :ref:`Standalone group`, :ref:`Group on geometry ` and :ref:`Group on filter ` using dedicated tabs. * :ref:`Create Groups from Geometry ` 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 ` dialog. * Standalone groups can be created by applying :ref:`Boolean operations ` to other groups. diff --git a/doc/salome/gui/SMESH/input/measurements.rst b/doc/salome/gui/SMESH/input/measurements.rst index ed1426a0a..248ea4f02 100644 --- a/doc/salome/gui/SMESH/input/measurements.rst +++ b/doc/salome/gui/SMESH/input/measurements.rst @@ -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`. diff --git a/doc/salome/gui/SMESH/input/modules.rst b/doc/salome/gui/SMESH/input/modules.rst index 45494b2a5..6c673f6db 100644 --- a/doc/salome/gui/SMESH/input/modules.rst +++ b/doc/salome/gui/SMESH/input/modules.rst @@ -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 diff --git a/doc/salome/gui/SMESH/input/tui_measurements.rst b/doc/salome/gui/SMESH/input/tui_measurements.rst index b3f596450..a4e056531 100644 --- a/doc/salome/gui/SMESH/input/tui_measurements.rst +++ b/doc/salome/gui/SMESH/input/tui_measurements.rst @@ -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>` diff --git a/idl/SMESH_Measurements.idl b/idl/SMESH_Measurements.idl index d3a9c77f4..bf8dc2152 100644 --- a/idl/SMESH_Measurements.idl +++ b/idl/SMESH_Measurements.idl @@ -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 ); }; }; diff --git a/idl/SMESH_Mesh.idl b/idl/SMESH_Mesh.idl index cb25bb183..c8c7bd786 100644 --- a/idl/SMESH_Mesh.idl +++ b/idl/SMESH_Mesh.idl @@ -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. diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 2196b1b8c..638e99c30 100755 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -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 index 000000000..d77b7451d Binary files /dev/null and b/resources/mesh_angle_measure.png differ diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 6f4e1d7cb..b4a75cb4d 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -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 ); diff --git a/src/SMESHGUI/SMESHGUI_Measurements.cxx b/src/SMESHGUI/SMESHGUI_Measurements.cxx index 1f1d5aa8a..8e19cddca 100644 --- a/src/SMESHGUI/SMESHGUI_Measurements.cxx +++ b/src/SMESHGUI/SMESHGUI_Measurements.cxx @@ -28,9 +28,13 @@ #include "SMESHGUI.h" #include "SMESHGUI_IdValidator.h" #include "SMESHGUI_Utils.h" +#include "SMESHGUI_MeshEditPreview.h" #include "SMESHGUI_VTKUtils.h" #include +#include #include +#include +#include #include #include @@ -59,6 +63,8 @@ #include #include +#include + #include #include CORBA_SERVER_HEADER(SMESH_MeshEditor) #include CORBA_SERVER_HEADER(SMESH_Measurements) @@ -1312,6 +1318,367 @@ 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( 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"; } diff --git a/src/SMESHGUI/SMESHGUI_Measurements.h b/src/SMESHGUI/SMESHGUI_Measurements.h index cadcf1f55..3d583f06a 100644 --- a/src/SMESHGUI/SMESHGUI_Measurements.h +++ b/src/SMESHGUI/SMESHGUI_Measurements.h @@ -37,10 +37,13 @@ class SUIT_SelectionFilter; class SALOME_Actor; class SMESH_Actor; class SMESHGUI_IdValidator; +class SMESHGUI_MeshEditPreview; #include #include CORBA_SERVER_HEADER(SMESH_Mesh) +#include + 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 diff --git a/src/SMESHGUI/SMESHGUI_Operations.h b/src/SMESHGUI/SMESHGUI_Operations.h index b4ccb9d02..97f79382e 100644 --- a/src/SMESHGUI/SMESHGUI_Operations.h +++ b/src/SMESHGUI/SMESHGUI_Operations.h @@ -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 diff --git a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx index b74d29e50..cf0e475d4 100644 --- a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx +++ b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx @@ -923,7 +923,7 @@ namespace SMESH aCollection->InitTraversal(); while ( vtkActor *anAct = aCollection->GetNextActor() ) { if ( SMESH_Actor *anActor = dynamic_cast(anAct) ) { - anActor->UpdateSelectionProps(); + anActor->UpdateSelectionProps(); } } } diff --git a/src/SMESHGUI/SMESH_images.ts b/src/SMESHGUI/SMESH_images.ts index 50b33888f..0a09a43ec 100644 --- a/src/SMESHGUI/SMESH_images.ts +++ b/src/SMESHGUI/SMESH_images.ts @@ -651,6 +651,10 @@ ICON_MEASURE_BND_BOX mesh_bounding_box.png + + ICON_MEASURE_ANGLE + mesh_angle_measure.png + ICON_SHOW mesh_show.png diff --git a/src/SMESHGUI/SMESH_msg_en.ts b/src/SMESHGUI/SMESH_msg_en.ts index b6f1fa619..209a6bd88 100644 --- a/src/SMESHGUI/SMESH_msg_en.ts +++ b/src/SMESHGUI/SMESH_msg_en.ts @@ -848,6 +848,18 @@ TOP_MEASURE_VOLUME Volume + + MEN_MEASURE_ANGLE + Angle + + + STB_MEASURE_ANGLE + Measure angle defined by three nodes + + + TOP_MEASURE_ANGLE + Angle + MEN_MOVE Move Node @@ -3617,7 +3629,7 @@ Use Display Entity menu command to show them. Create groups of entities basing on nodes of other groups - STB_UNDERLYING_ELEMS + STB_FACE_GROUPS_BY_EDGES Create face groups separated by sharp edges @@ -8108,6 +8120,21 @@ as they are of improper type: Compute + + SMESHGUI_Angle + + NODES_GROUP + Three nodes + + + RESULT + Angle in degrees + + + COMPUTE + Compute + + SMESHGUI_CopyMeshDlg @@ -8164,6 +8191,10 @@ with red in the Object Browser. BASIC_PROPERTIES Basic Properties + + ANGLE + Angle + SMESHGUI_BoundingBox diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index cef6517ce..e76d1b343 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -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", diff --git a/src/SMESH_I/SMESH_Measurements_i.cxx b/src/SMESH_I/SMESH_Measurements_i.cxx index 2fcac5a7f..45f5f0b7f 100644 --- a/src/SMESH_I/SMESH_Measurements_i.cxx +++ b/src/SMESH_I/SMESH_Measurements_i.cxx @@ -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; +} diff --git a/src/SMESH_I/SMESH_Measurements_i.hxx b/src/SMESH_I/SMESH_Measurements_i.hxx index bb696eb92..3ba642ca6 100644 --- a/src/SMESH_I/SMESH_Measurements_i.hxx +++ b/src/SMESH_I/SMESH_Measurements_i.hxx @@ -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 ); }; } diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index 7037fd2eb..35198fe59 100755 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -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 ` """ 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.