IPAL53011: Order of sub-mesh in meshing process does't work

make SMESHGUI_MeshOrderDlg modal

+ add missing filter examples
This commit is contained in:
eap 2016-03-21 15:03:25 +03:00
parent cc935c7f60
commit 1a88a8f665
14 changed files with 149 additions and 36 deletions

View File

@ -94,7 +94,11 @@ SET(GOOD_TESTS
filters_ex31.py filters_ex31.py
filters_ex33.py filters_ex33.py
filters_ex34.py filters_ex34.py
filters_ex35.py
filters_ex36.py filters_ex36.py
filters_ex37.py
filters_ex38.py
filters_ex39.py
filters_belong2group.py filters_belong2group.py
grouping_elements_ex01.py grouping_elements_ex01.py
grouping_elements_ex02.py grouping_elements_ex02.py

View File

@ -14,7 +14,7 @@ smesh = smeshBuilder.New(salome.myStudy)
box = geompy.MakeBoxDXDYDZ(10., 10., 10.) box = geompy.MakeBoxDXDYDZ(10., 10., 10.)
geompy.addToStudy(box, "Box") geompy.addToStudy(box, "Box")
# 1. Create a hexahedral mesh on the box # Create a hexahedral mesh on the box
hexa = smesh.Mesh(box, "Box : hexahedrical mesh") hexa = smesh.Mesh(box, "Box : hexahedrical mesh")
# create a Regular 1D algorithm for edges # create a Regular 1D algorithm for edges

View File

@ -24,7 +24,7 @@ faces = geompy.SubShapeAll(box, geompy.ShapeType["FACE"])
f1 = faces[2] f1 = faces[2]
f2 = faces[0] f2 = faces[0]
# face opposite to f2 # face opposite to f2
f2opp = faces[1] f2opp = geompy.GetOppositeFace( box, f2 )
# Get vertices used to specify how to associate sides of faces at projection # Get vertices used to specify how to associate sides of faces at projection
[v1F1, v2F1] = geompy.SubShapeAll(f1, geompy.ShapeType["VERTEX"])[:2] [v1F1, v2F1] = geompy.SubShapeAll(f1, geompy.ShapeType["VERTEX"])[:2]
@ -48,7 +48,7 @@ src_mesh = smesh.Mesh(box, "Source mesh")
src_mesh.Segment().NumberOfSegments(9,10) src_mesh.Segment().NumberOfSegments(9,10)
src_mesh.Quadrangle() src_mesh.Quadrangle()
src_mesh.Hexahedron() src_mesh.Hexahedron()
src_mesh.Triangle(f1) # triangular sumbesh src_mesh.Triangle(f1) # triangular sub-mesh
src_mesh.Compute() src_mesh.Compute()

View File

@ -27,7 +27,8 @@ geompy.addToStudy( box, 'box' )
geompy.addToStudyInFather( box, Face_1, 'Face_1' ) geompy.addToStudyInFather( box, Face_1, 'Face_1' )
geompy.addToStudyInFather( box, Face_2, 'Face_2' ) geompy.addToStudyInFather( box, Face_2, 'Face_2' )
# Make the source mesh with Netgem2D
# Make the source mesh triangulated by MEFISTO
src_mesh = smesh.Mesh(Face_1, "Source mesh") src_mesh = smesh.Mesh(Face_1, "Source mesh")
src_mesh.Segment().NumberOfSegments(15) src_mesh.Segment().NumberOfSegments(15)
src_mesh.Triangle() src_mesh.Triangle()

View File

@ -13,8 +13,7 @@ smesh = smeshBuilder.New(salome.myStudy)
# Create face from the wire and add to study # Create face from the wire and add to study
Face = geompy.MakeSketcher("Sketcher:F 0 0:TT 20 0:R 90:C 20 90:WF", [0, 0, 0, 1, 0, 0, 0, 0, 1]) Face = geompy.MakeSketcher("Sketcher:F 0 0:TT 20 0:R 90:C 20 90:WF", [0, 0, 0, 1, 0, 0, 0, 0, 1])
geompy.addToStudy(Face,"Face") geompy.addToStudy(Face,"Face")
edges = geompy.SubShapeAllSorted(Face, geompy.ShapeType["EDGE"]) circle, radius1, radius2 = geompy.SubShapeAllSorted(Face, geompy.ShapeType["EDGE"])
circle, radius1, radius2 = edges
geompy.addToStudyInFather(Face, radius1,"radius1") geompy.addToStudyInFather(Face, radius1,"radius1")
geompy.addToStudyInFather(Face, radius2,"radius2") geompy.addToStudyInFather(Face, radius2,"radius2")
geompy.addToStudyInFather(Face, circle,"circle") geompy.addToStudyInFather(Face, circle,"circle")

View File

@ -2,15 +2,16 @@
# create mesh # create mesh
from SMESH_mechanic import * from SMESH_mechanic import *
print
# create a group of all faces (quadrangles) generated on sub_face3 # create a group of all faces (quadrangles) generated on sub_face3
faces_on_face3 = mesh.MakeGroup("faces_on_face3", SMESH.FACE, SMESH.FT_BelongToGeom,'=',sub_face3) quads_on_face3 = mesh.MakeGroup("quads_on_face3", SMESH.FACE, SMESH.FT_BelongToGeom,'=',sub_face3)
print "There are %s quadrangles generated on '%s' and included in the group '%s'" % ( faces_on_face3.Size(), sub_face3.GetName(), faces_on_face3.GetName() ) print "There are %s quadrangles generated on '%s' and included in the group '%s'" % ( quads_on_face3.Size(), sub_face3.GetName(), quads_on_face3.GetName() )
# create a group of all the rest quadrangles, generated on other faces by combining 2 criteria: # create a group of all the rest quadrangles, generated on other faces by combining 2 criteria:
# - negated FT_BelongToMeshGroup to select elements not included in faces_on_face3 # - negated FT_BelongToMeshGroup to select elements not included in quads_on_face3
# - FT_ElemGeomType to select quadrangles # - FT_ElemGeomType to select quadrangles
not_on_face3 = smesh.GetCriterion( SMESH.FACE, SMESH.FT_BelongToMeshGroup,'=',faces_on_face3, SMESH.FT_LogicalNOT ) not_on_face3 = smesh.GetCriterion( SMESH.FACE, SMESH.FT_BelongToMeshGroup,'=',quads_on_face3, SMESH.FT_LogicalNOT )
quadrangles = smesh.GetCriterion( SMESH.FACE, SMESH.FT_ElemGeomType,'=',SMESH.Geom_QUADRANGLE ) quadrangles = smesh.GetCriterion( SMESH.FACE, SMESH.FT_ElemGeomType,'=',SMESH.Geom_QUADRANGLE )
rest_quads = mesh.MakeGroupByCriteria("rest_quads", [ not_on_face3, quadrangles ]) rest_quads = mesh.MakeGroupByCriteria("rest_quads", [ not_on_face3, quadrangles ])

View File

@ -4,14 +4,19 @@
# create mesh # create mesh
from SMESH_mechanic import * from SMESH_mechanic import *
# get faces with aspect ratio > 1.5 # get faces with aspect ratio > 2.5
filter = smesh.GetFilter(SMESH.FACE, SMESH.FT_AspectRatio, SMESH.FT_MoreThan, 1.5) filter = smesh.GetFilter(SMESH.FACE, SMESH.FT_AspectRatio, SMESH.FT_MoreThan, 2.5)
ids = mesh.GetIdsFromFilter(filter) ids = mesh.GetIdsFromFilter(filter)
print "Number of faces with aspect ratio > 2.5:", len(ids)
# get faces with aspect ratio > 1.5
filter = smesh.GetFilter(SMESH.FACE, SMESH.FT_AspectRatio, '>', 1.5, mesh=mesh)
ids = filter.GetIDs()
print "Number of faces with aspect ratio > 1.5:", len(ids) print "Number of faces with aspect ratio > 1.5:", len(ids)
# copy the faces with aspect ratio > 1.5 to another mesh; # copy the faces with aspect ratio > 1.5 to another mesh;
# this demostrates that a filter can be used where usually a group or sub-mesh is acceptable # this demostrates that a filter can be used where usually a group or sub-mesh is acceptable
filter.SetMesh( mesh.GetMesh() ) filter.SetMesh( mesh.GetMesh() ) # - actually non necessary as mesh is set at filter creation
mesh2 = smesh.CopyMesh( filter, "AR > 1.5" ) mesh2 = smesh.CopyMesh( filter, "AR > 1.5" )
print "Number of copied faces with aspect ratio > 1.5:", mesh2.NbFaces() print "Number of copied faces with aspect ratio > 1.5:", mesh2.NbFaces()

View File

@ -0,0 +1,15 @@
# Entity type
# create a mesh
from SMESH_mechanic import *
# make the mesh quadratic
mesh.ConvertToQuadratic()
# make some elements bi-quadratic
for face in SubFaceL[: len(SubFaceL)/2]:
mesh.ConvertToQuadratic( theSubMesh=mesh.Group( face ), theToBiQuad=True )
# get triangles with 7 nodes
filter_tri = smesh.GetFilter(SMESH.FACE, SMESH.FT_EntityType,'=', SMESH.Entity_BiQuad_Triangle )
ids_tri = mesh.GetIdsFromFilter(filter_tri)
print "Number of bi-quadratic triangles:", len(ids_tri)

View File

@ -0,0 +1,15 @@
# Ball diameter
# create a mesh
from SMESH_mechanic import *
# create several balls with increasing diameter
for i in range(1,10):
diameter = float( i )
mesh.AddBall( i, diameter )
pass
# get balls with diameter > 5.
diam_filter = smesh.GetFilter(SMESH.BALL, SMESH.FT_BallDiameter,'>', 5. )
ids = mesh.GetIdsFromFilter( diam_filter )
print "Number of balls with diameter > 5:", len(ids)

View File

@ -0,0 +1,45 @@
# Elements of a domain
import salome, SMESH
salome.salome_init()
from salome.geom import geomBuilder
geompy = geomBuilder.New(salome.myStudy)
from salome.smesh import smeshBuilder
smesh = smeshBuilder.New(salome.myStudy)
# create two boxes to have two domains in the mesh
box1 = geompy.MakeBoxDXDYDZ( 100,100,100 )
box2 = geompy.MakeTranslation( box1, 200, 0, 0 )
boxes = geompy.MakeCompound( [box1, box2] )
box1, box2 = geompy.SubShapeAll( boxes, geompy.ShapeType["SHAPE"], "box")
vertex = geompy.SubShape( box1, geompy.ShapeType["VERTEX"], [1] )
# create a mesh
mesh = smesh.Mesh( boxes )
mesh.Segment(box1).NumberOfSegments( 5 ) # to have different nb of elements on the boxes
mesh.Segment(box2).NumberOfSegments( 10 )
mesh.Quadrangle()
mesh.Hexahedron()
mesh.Compute()
# Create filters with FT_ConnectedElements criterion by pointing a domain in different ways:
# using point coordinates in box_1
nodeFilter = smesh.GetFilter( SMESH.NODE, SMESH.FT_ConnectedElements, "=", "1.,2,10", mesh=mesh )
print "Nb. nodes in box_1:", len( nodeFilter.GetIDs())
# using point coordinates in box_2
edgeFilter = smesh.GetFilter( SMESH.EDGE, SMESH.FT_ConnectedElements, "=", [202,1,1 ], mesh=mesh )
print "Nb. segments in box_2:", len( edgeFilter.GetIDs())
# using a geom vertex of box_1
faceFilter = smesh.GetFilter( SMESH.FACE, SMESH.FT_ConnectedElements, "=", vertex, mesh=mesh )
print "Nb. faces in box_1:", len( edgeFilter.GetIDs())
# using node ID in box_2
voluFilter = smesh.GetFilter( SMESH.VOLUME, SMESH.FT_ConnectedElements, "=", 10, mesh=mesh )
print "Nb. volumes in box_2:", len( voluFilter.GetIDs())

View File

@ -13,10 +13,11 @@ There are three types of groups different by their internal
organization:<ol> organization:<ol>
<li><b>Standalone group</b> is a static set of mesh entities. Its <li><b>Standalone group</b> is a static set of mesh entities. Its
contents can be explicitly controlled by the user. Upon removal of contents can be explicitly controlled by the user. Upon removal of
the entities included into the group, the group becomes empty and the entities included into the group, e.g. due to modification of
its content can be restored only manually. Hence it is meshing parameter, the group becomes empty and its content can be
reasonable to create standalone groups when the mesh generation is restored only manually. Hence it is reasonable to create standalone
finished and mesh quality is verified. groups when the mesh generation is finished and mesh quality is
verified.
\warning Creation and edition of large standalone groups in \warning Creation and edition of large standalone groups in
\ref creating_groups_page "Create group" dialog using manual edition \ref creating_groups_page "Create group" dialog using manual edition
is problematic due to poor performance of the dialog.</li> is problematic due to poor performance of the dialog.</li>
@ -27,11 +28,11 @@ organization:<ol>
established at group construction and cannot be changed. The group established at group construction and cannot be changed. The group
contents are always updated automatically, hence the contents are always updated automatically, hence the
group can be created even before mesh elements generation.</li> group can be created even before mesh elements generation.</li>
<li><b>Group on filter</b> encapsulates a filter, which is used to <li><b>Group on filter</b> encapsulates a \ref filters_page "filter",
select mesh entities composing the group from the whole which is used to select mesh entities composing the group from the
mesh. Criteria of the filter can be changed at any time. The whole mesh. Criteria of the filter can be changed at any time. The
group contents are always updated automatically, hence group contents are always updated automatically, hence the group can
the group can be created even before mesh elements generation.</li> be created even before mesh elements generation.</li>
</ol> </ol>
The group on geometry and group on filter can be converted to The group on geometry and group on filter can be converted to
a standalone group. a standalone group.

View File

@ -307,7 +307,7 @@ filters over-constrained volumes, whose all nodes are on the mesh boundary:
filters mesh entities (nodes or elements) included in a mesh group filters mesh entities (nodes or elements) included in a mesh group
defined by threshold value: defined by threshold value:
- element type can be any entity type, from \a SMESH.NODE to \a SMESH.VOLUME - element type can be any, from \a SMESH.NODE to \a SMESH.BALL
- functor type should be \a SMESH.FT_BelongToMeshGroup - functor type should be \a SMESH.FT_BelongToMeshGroup
- threshold is mesh group object - threshold is mesh group object
@ -317,7 +317,7 @@ defined by threshold value:
filters mesh entities (nodes or elements) which all nodes lie on the filters mesh entities (nodes or elements) which all nodes lie on the
shape defined by threshold value: shape defined by threshold value:
- element type can be any entity type, from \a SMESH.NODE to \a SMESH.VOLUME - element type can be any, from \a SMESH.NODE to \a SMESH.BALL
- functor type should be \a SMESH.FT_BelongToGeom - functor type should be \a SMESH.FT_BelongToGeom
- threshold is geometrical object - threshold is geometrical object
- tolerance is a distance between a node and the geometrical object; - tolerance is a distance between a node and the geometrical object;
@ -329,7 +329,7 @@ it is used if an node is not associated to any geometry.
filters mesh entities (nodes or elements) at least one node of which lies on the filters mesh entities (nodes or elements) at least one node of which lies on the
shape defined by threshold value: shape defined by threshold value:
- element type can be any entity type, from \a SMESH.NODE to \a SMESH.VOLUME - element type can be any, from \a SMESH.NODE to \a SMESH.BALL
- functor type should be \a SMESH.FT_LyingOnGeom - functor type should be \a SMESH.FT_LyingOnGeom
- threshold is geometrical object - threshold is geometrical object
- tolerance is a distance between a node and the geometrical object; - tolerance is a distance between a node and the geometrical object;
@ -341,7 +341,7 @@ it is used if an node is not associated to any geometry.
filters mesh entities (nodes or elements) which all nodes belong to the filters mesh entities (nodes or elements) which all nodes belong to the
plane defined by threshold value with the given tolerance: plane defined by threshold value with the given tolerance:
- element type can be: \a SMESH.NODE, \a SMESH.EDGE, \a SMESH.FACE - element type can be any except \a SMESH.VOLUME
- functor type should be \a SMESH.FT_BelongToPlane - functor type should be \a SMESH.FT_BelongToPlane
- threshold is geometrical object (plane) - threshold is geometrical object (plane)
- default tolerance is 1.0e-7 - default tolerance is 1.0e-7
@ -352,7 +352,7 @@ plane defined by threshold value with the given tolerance:
filters mesh entities (nodes or elements) which all nodes belong to the filters mesh entities (nodes or elements) which all nodes belong to the
cylindrical face defined by threshold value with the given tolerance: cylindrical face defined by threshold value with the given tolerance:
- element type can be: \a , \a SMESH.EDGE, \a SMESH.FACE - element type can be any except \a SMESH.VOLUME
- functor type should be \a SMESH.FT_BelongToCylinder - functor type should be \a SMESH.FT_BelongToCylinder
- threshold is geometrical object (cylindrical face) - threshold is geometrical object (cylindrical face)
- default tolerance is 1.0e-7 - default tolerance is 1.0e-7
@ -363,7 +363,7 @@ cylindrical face defined by threshold value with the given tolerance:
filters mesh entities (nodes or elements) which all nodes belong to the filters mesh entities (nodes or elements) which all nodes belong to the
arbitrary surface defined by threshold value with the given tolerance: arbitrary surface defined by threshold value with the given tolerance:
- element type can be: \a SMESH.NODE, \a SMESH.EDGE, \a SMESH.FACE - element type can be any except \a SMESH.VOLUME
- functor type should be \a SMESH.FT_BelongToGenSurface - functor type should be \a SMESH.FT_BelongToGenSurface
- threshold is geometrical object (arbitrary surface) - threshold is geometrical object (arbitrary surface)
- default tolerance is 1.0e-7 - default tolerance is 1.0e-7
@ -374,7 +374,7 @@ arbitrary surface defined by threshold value with the given tolerance:
filters mesh entities elements (nodes or elements) according to the filters mesh entities elements (nodes or elements) according to the
specified identifiers range: specified identifiers range:
- element type can be any entity type, from \a SMESH.NODE to \a SMESH.VOLUME - element type can be any, from \a SMESH.NODE to \a SMESH.BALL
- functor type is \a SMESH.FT_RangeOfIds - functor type is \a SMESH.FT_RangeOfIds
- threshold is string listing required IDs and/or ranges of IDs, e.g."1,2,3,50-60,63,67,70-78" - threshold is string listing required IDs and/or ranges of IDs, e.g."1,2,3,50-60,63,67,70-78"
@ -393,7 +393,7 @@ the point of view of MED convention.
\section filter_linear_or_quadratic Linear / quadratic \section filter_linear_or_quadratic Linear / quadratic
filters linear / quadratic mesh elements: filters linear / quadratic mesh elements:
- element type should be any element type, e.g.: \a SMESH.EDGE, \a SMESH.FACE, \a SMESH.VOLUME - element type should be either \a SMESH.EDGE, \a SMESH.FACE or \a SMESH.VOLUME
- functor type is \a SMESH.FT_LinearOrQuadratic - functor type is \a SMESH.FT_LinearOrQuadratic
- threshold is not required - threshold is not required
- if unary operator is set to SMESH.FT_LogicalNOT, the quadratic - if unary operator is set to SMESH.FT_LogicalNOT, the quadratic
@ -404,7 +404,7 @@ elements are selected, otherwise (by default) linear elements are selected
\section filter_group_color Group color \section filter_group_color Group color
filters mesh entities, belonging to the group with the color defined by the threshold value. filters mesh entities, belonging to the group with the color defined by the threshold value.
- element type can be any entity type, from \a SMESH.NODE to \a SMESH.VOLUME - element type can be any, from \a SMESH.NODE to \a SMESH.BALL
- functor type is \a SMESH.FT_GroupColor - functor type is \a SMESH.FT_GroupColor
- threshold should be of SALOMEDS.Color type - threshold should be of SALOMEDS.Color type
@ -415,12 +415,39 @@ filters mesh entities, belonging to the group with the color defined by the thre
filters mesh elements by the geometric type defined with the threshold filters mesh elements by the geometric type defined with the threshold
value. The list of available geometric types depends on the element value. The list of available geometric types depends on the element
entity type. entity type.
- element type should be any element type, e.g.: \a SMESH.EDGE, \a SMESH.FACE, \a SMESH.VOLUME - element type can be any, e.g.: \a SMESH.EDGE, \a SMESH.FACE, \a SMESH.VOLUME, etc.
- functor type should be \a SMESH.FT_ElemGeomType - functor type should be \a SMESH.FT_ElemGeomType
- threshold is of smesh.GeometryType value - threshold is either of smesh.GeometryType values. Type \a SMESH.GeometryType._items in the Python Console to see all geometric types.
\tui_script{filters_ex35.py} \tui_script{filters_ex35.py}
\section filter_entity_type Entity type
filters mesh elements by the geometric type and number of nodes.
- element type can be any, e.g.: \a SMESH.EDGE, \a SMESH.FACE, \a SMESH.VOLUME, etc.
- functor type should be \a SMESH.FT_EntityType
- threshold is either of SMESH.EntityType values. Type \a SMESH.EntityType._items in the Python Console to see all entity types.
\tui_script{filters_ex37.py}
\section filter_ball_diam Ball diameter
filters ball elements by diameter.
- element type should be \a SMESH.BALL
- functor type should be \a SMESH.FT_BallDiameter
- threshold is floating point value (ball diameter)
\tui_script{filters_ex38.py}
\section filter_domain Elements of a domain
filters elements of a specified domain.
- element type can be any, e.g.: \a SMESH.EDGE, \a SMESH.FACE, \a SMESH.VOLUME, etc.
- functor type should be \a SMESH.FT_ConnectedElements
- threshold is either (1) node ID or (2) geometrical vertex or (3) 3 coordinates of a point.
\tui_script{filters_ex39.py}
\section combining_filters How to combine several criteria into a filter? \section combining_filters How to combine several criteria into a filter?
Several criteria can be combined into a filter. Several criteria can be combined into a filter.

View File

@ -293,7 +293,7 @@ bool SMESHGUI_MeshOrderBox:: IsOrderChanged() const
// ========================================================================================= // =========================================================================================
SMESHGUI_MeshOrderDlg::SMESHGUI_MeshOrderDlg(QWidget* theParent) SMESHGUI_MeshOrderDlg::SMESHGUI_MeshOrderDlg(QWidget* theParent)
: SMESHGUI_Dialog( theParent, false, false, OK | Cancel | Help ) : SMESHGUI_Dialog( theParent, true, false, OK | Cancel | Help )
{ {
setWindowTitle( tr( "SMESH_MESHORDER_TITLE") ); setWindowTitle( tr( "SMESH_MESHORDER_TITLE") );
QFrame* main = mainFrame(); QFrame* main = mainFrame();

View File

@ -911,7 +911,7 @@ Handle(_pyCommand) _pyGen::AddCommand( const TCollection_AsciiString& theCommand
{ {
// set SMESH.GeometryType instead of a numerical Threshold // set SMESH.GeometryType instead of a numerical Threshold
const int nbTypes = SMESH::Geom_LAST; const int nbTypes = SMESH::Geom_LAST;
const char* types[nbTypes] = { const char* types[] = {
"Geom_POINT", "Geom_EDGE", "Geom_TRIANGLE", "Geom_QUADRANGLE", "Geom_POLYGON", "Geom_POINT", "Geom_EDGE", "Geom_TRIANGLE", "Geom_QUADRANGLE", "Geom_POLYGON",
"Geom_TETRA", "Geom_PYRAMID", "Geom_HEXA", "Geom_PENTA", "Geom_HEXAGONAL_PRISM", "Geom_TETRA", "Geom_PYRAMID", "Geom_HEXA", "Geom_PENTA", "Geom_HEXAGONAL_PRISM",
"Geom_POLYHEDRA", "Geom_BALL" }; "Geom_POLYHEDRA", "Geom_BALL" };
@ -926,7 +926,7 @@ Handle(_pyCommand) _pyGen::AddCommand( const TCollection_AsciiString& theCommand
{ {
// set SMESH.EntityType instead of a numerical Threshold // set SMESH.EntityType instead of a numerical Threshold
const int nbTypes = SMESH::Entity_Last; const int nbTypes = SMESH::Entity_Last;
const char* types[nbTypes] = { const char* types[] = {
"Entity_Node", "Entity_0D", "Entity_Edge", "Entity_Quad_Edge", "Entity_Node", "Entity_0D", "Entity_Edge", "Entity_Quad_Edge",
"Entity_Triangle", "Entity_Quad_Triangle", "Entity_BiQuad_Triangle", "Entity_Triangle", "Entity_Quad_Triangle", "Entity_BiQuad_Triangle",
"Entity_Quadrangle", "Entity_Quad_Quadrangle", "Entity_BiQuad_Quadrangle", "Entity_Quadrangle", "Entity_Quad_Quadrangle", "Entity_BiQuad_Quadrangle",