From 1c1bbf6798782cb24bcab45d519817cff125ae5c Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 11 Nov 2016 15:14:46 +0300 Subject: [PATCH] 23315: [CEA 1929] Too much memory used to display a mesh in shading and wireframe Deactivate ID mapping in all DeviceActor's except the pickable one. The ID mapping needed to show IDs in the Viewer is computed when needed via VTKViewer_ExtractUnstructuredGrid::BuildOut2InMap() + IPAL53796: Clipping related bugs --- doc/salome/examples/filters_ex33.py | 15 ++++--- src/MEFISTO2/aptrte.cxx | 68 ++++++++++++++-------------- src/OBJECT/SMESH_Actor.cxx | 16 ++++--- src/OBJECT/SMESH_CellLabelActor.cxx | 35 ++++++++++----- src/OBJECT/SMESH_DeviceActor.cxx | 22 ++++++--- src/SMDS/SMDS_Mesh.cxx | 70 +++++++++-------------------- src/SMESH/SMESH_Mesh.cxx | 1 + src/SMESH_I/SMESH_Mesh_i.cxx | 1 - src/SMESH_SWIG/smeshBuilder.py | 6 +-- 9 files changed, 116 insertions(+), 118 deletions(-) diff --git a/doc/salome/examples/filters_ex33.py b/doc/salome/examples/filters_ex33.py index c844e9063..7ffb5548f 100644 --- a/doc/salome/examples/filters_ex33.py +++ b/doc/salome/examples/filters_ex33.py @@ -2,16 +2,19 @@ # create mesh from SMESH_mechanic import * -# get number of linear and quadratic edges -filter_linear = smesh.GetFilter(SMESH.EDGE, SMESH.FT_LinearOrQuadratic) + +# get linear and quadratic edges +filter_linear = smesh.GetFilter(SMESH.EDGE, SMESH.FT_LinearOrQuadratic) filter_quadratic = smesh.GetFilter(SMESH.EDGE, SMESH.FT_LinearOrQuadratic, SMESH.FT_LogicalNOT) -ids_linear = mesh.GetIdsFromFilter(filter_linear) +ids_linear = mesh.GetIdsFromFilter(filter_linear) ids_quadratic = mesh.GetIdsFromFilter(filter_quadratic) print "Number of linear edges:", len(ids_linear), "; number of quadratic edges:", len(ids_quadratic) + # convert mesh to quadratic print "Convert to quadratic..." -mesh.ConvertToQuadratic(True) -# get number of linear and quadratic edges -ids_linear = mesh.GetIdsFromFilter(filter_linear) +mesh.ConvertToQuadratic() + +# get linear and quadratic edges +ids_linear = mesh.GetIdsFromFilter(filter_linear) ids_quadratic = mesh.GetIdsFromFilter(filter_quadratic) print "Number of linear edges:", len(ids_linear), "; number of quadratic edges:", len(ids_quadratic) diff --git a/src/MEFISTO2/aptrte.cxx b/src/MEFISTO2/aptrte.cxx index 6fd9db1fe..3743dcf06 100755 --- a/src/MEFISTO2/aptrte.cxx +++ b/src/MEFISTO2/aptrte.cxx @@ -182,10 +182,10 @@ void aptrte( Z nutysu, R aretmx, // majoration empirique du nombre de sommets de la triangulation i = 4*nbarfr/10; mxsomm = Max( 20000, 64*nbpti+i*i ); - MESSAGE( "APTRTE: Debut de la triangulation plane avec " ); - MESSAGE( "nutysu=" << nutysu << " aretmx=" << aretmx - << " mxsomm=" << mxsomm ); - MESSAGE( nbarfr << " sommets sur la frontiere et " << nbpti << " points internes"); + // MESSAGE( "APTRTE: Debut de la triangulation plane avec " ); + // MESSAGE( "nutysu=" << nutysu << " aretmx=" << aretmx + // << " mxsomm=" << mxsomm ); + // MESSAGE( nbarfr << " sommets sur la frontiere et " << nbpti << " points internes"); NEWDEPART: //mnpxyd( 3, mxsomm ) les coordonnees UV des sommets et la taille d'arete aux sommets @@ -366,9 +366,9 @@ void aptrte( Z nutysu, R aretmx, //fin ajout 9/11/2006 ................................................. - MESSAGE("Sur le bord: arete min=" << aremin << " arete max=" << aremax ); - MESSAGE("Triangulation: arete mx=" << aretmx - << " triangle aire mx=" << airemx ); + // MESSAGE("Sur le bord: arete min=" << aremin << " arete max=" << aremax ); + // MESSAGE("Triangulation: arete mx=" << aretmx + // << " triangle aire mx=" << airemx ); //chainage des aretes frontalieres : la derniere arete frontaliere mnsoar[ mosoar * noar - mosoar + 5 ] = 0; @@ -408,7 +408,7 @@ void aptrte( Z nutysu, R aretmx, mxtree = 2 * mxsomm; NEWTREE: //en cas de saturation de l'un des tableaux, on boucle - MESSAGE( "Debut triangulation avec mxsomm=" << mxsomm ); + //MESSAGE( "Debut triangulation avec mxsomm=" << mxsomm ); if( mntree != NULL ) delete [] mntree; nbsomm = nbarpi; mntree = new Z[motree*(1+mxtree)]; @@ -426,13 +426,13 @@ void aptrte( Z nutysu, R aretmx, //saturation de letree => sa taille est augmentee et relance mxtree = mxtree * 2; ierr = 0; - MESSAGE( "Nouvelle valeur de mxtree=" << mxtree ); + //MESSAGE( "Nouvelle valeur de mxtree=" << mxtree ); goto NEWTREE; } deltacpu_( d ); tcpu += d; - MESSAGE( "Temps de l'ajout arbre-4 des Triangles Equilateraux=" << d << " secondes" ); + //MESSAGE( "Temps de l'ajout arbre-4 des Triangles Equilateraux=" << d << " secondes" ); if( ierr != 0 ) goto ERREUR; //ici le tableau mnpxyd contient les sommets des te et les points frontaliers et internes @@ -452,8 +452,8 @@ void aptrte( Z nutysu, R aretmx, deltacpu_( d ); tcpu += d; - MESSAGE("Temps de l'adaptation et l'homogeneisation de l'arbre-4 des TE=" - << d << " secondes"); + //MESSAGE("Temps de l'adaptation et l'homogeneisation de l'arbre-4 des TE=" + // << d << " secondes"); if( ierr != 0 ) { //destruction du tableau auxiliaire et de l'arbre @@ -461,7 +461,7 @@ void aptrte( Z nutysu, R aretmx, { //letree sature mxtree = mxtree * 2; - MESSAGE( "Redemarrage avec la valeur de mxtree=" << mxtree ); + //MESSAGE( "Redemarrage avec la valeur de mxtree=" << mxtree ); ierr = 0; goto NEWTREE; } @@ -484,7 +484,7 @@ void aptrte( Z nutysu, R aretmx, //Temps calcul deltacpu_( d ); tcpu += d; - MESSAGE( "Temps de la triangulation des TE=" << d << " secondes" ); +//MESSAGE( "Temps de la triangulation des TE=" << d << " secondes" ); // ierr =0 si pas d'erreur // =1 si le tableau mnsoar est sature @@ -506,11 +506,11 @@ void aptrte( Z nutysu, R aretmx, mosoar, mxsoar, n1soar, mnsoar, na, moartr, mxartr, n1artr, mnartr, n ); - MESSAGE( "Nombre d'echanges des diagonales de 2 triangles=" << n ); +//MESSAGE( "Nombre d'echanges des diagonales de 2 triangles=" << n ); deltacpu_( d ); tcpu += d; - MESSAGE("Temps de la triangulation Delaunay par echange des diagonales=" - << d << " secondes"); + // MESSAGE("Temps de la triangulation Delaunay par echange des diagonales=" + // << d << " secondes"); //qualites de la triangulation actuelle qualitetrte( mnpxyd, mosoar, mxsoar, mnsoar, moartr, mxartr, mnartr, @@ -539,11 +539,11 @@ void aptrte( Z nutysu, R aretmx, mxarcf, mn1arcf, mnarcf, mnarcf1, mnarcf2, n, ierr ); - MESSAGE( "Restauration de " << n << " aretes perdues de la frontiere ierr=" << ierr ); +//MESSAGE( "Restauration de " << n << " aretes perdues de la frontiere ierr=" << ierr ); deltacpu_( d ); tcpu += d; - MESSAGE("Temps de la recuperation des aretes perdues de la frontiere=" - << d << " secondes"); +//MESSAGE("Temps de la recuperation des aretes perdues de la frontiere=" +// << d << " secondes"); if( ierr != 0 ) goto ERREUR; @@ -585,7 +585,7 @@ void aptrte( Z nutysu, R aretmx, deltacpu_( d ); tcpu += d; - MESSAGE( "Temps de la suppression des triangles externes=" << d << "ierr=" << ierr ); +//MESSAGE( "Temps de la suppression des triangles externes=" << d << "ierr=" << ierr ); if( ierr != 0 ) goto ERREUR; //qualites de la triangulation actuelle @@ -619,7 +619,7 @@ void aptrte( Z nutysu, R aretmx, deltacpu_( d ); tcpu += d; - MESSAGE( "Temps de l'amelioration de la qualite de la triangulation=" << d ); +//MESSAGE( "Temps de l'amelioration de la qualite de la triangulation=" << d ); if( ierr == -13 ) ierr=0; //6/10/2006 arret de l'amelioration apres boucle infinie dans caetoi if( ierr != 0 ) goto ERREUR; @@ -713,11 +713,11 @@ void aptrte( Z nutysu, R aretmx, } } nbt /= nbsttria; //le nombre final de triangles de la surface - MESSAGE( "APTRTE: Fin de la triangulation plane avec "<AddActor(myBaseActor); theRenderer->AddActor(myNodeExtActor); @@ -1972,6 +1972,8 @@ void SMESH_ActorDef::Update() { if(MYDEBUG) MESSAGE("SMESH_ActorDef::Update"); + myVisualObj->Update(); + if(GetControlMode() != eNone) { unsigned long aTime = myTimeStamp->GetMTime(); unsigned long anObjTime = myVisualObj->GetUnstructuredGrid()->GetMTime(); diff --git a/src/OBJECT/SMESH_CellLabelActor.cxx b/src/OBJECT/SMESH_CellLabelActor.cxx index 5bca6fc0a..afd31449e 100644 --- a/src/OBJECT/SMESH_CellLabelActor.cxx +++ b/src/OBJECT/SMESH_CellLabelActor.cxx @@ -25,6 +25,8 @@ // #include "SMESH_CellLabelActor.h" +#include "SMESH_ExtractGeometry.h" + #include #include #include @@ -47,8 +49,9 @@ vtkStandardNewMacro(SMESH_CellLabelActor); /*! Constructor. */ -SMESH_CellLabelActor::SMESH_CellLabelActor() { - //Definition of cells numbering pipeline +SMESH_CellLabelActor::SMESH_CellLabelActor() +{ + //Definition of cells numbering pipeline //--------------------------------------- myCellsNumDataSet = vtkUnstructuredGrid::New(); @@ -58,18 +61,18 @@ SMESH_CellLabelActor::SMESH_CellLabelActor() { myClsMaskPoints = vtkMaskPoints::New(); myClsMaskPoints->SetInputConnection(myCellCenters->GetOutputPort()); myClsMaskPoints->SetOnRatio(1); - + myClsSelectVisiblePoints = vtkSelectVisiblePoints::New(); myClsSelectVisiblePoints->SetInputConnection(myClsMaskPoints->GetOutputPort()); myClsSelectVisiblePoints->SelectInvisibleOff(); myClsSelectVisiblePoints->SetTolerance(0.1); - + myClsLabeledDataMapper = vtkLabeledDataMapper::New(); myClsLabeledDataMapper->SetInputConnection(myClsSelectVisiblePoints->GetOutputPort()); myClsLabeledDataMapper->SetLabelFormat("%d"); myClsLabeledDataMapper->SetLabelModeToLabelScalars(); - + myClsTextProp = vtkTextProperty::New(); myClsTextProp->SetFontFamilyToTimes(); myClsTextProp->SetFontSize(12); @@ -98,7 +101,8 @@ SMESH_CellLabelActor::SMESH_CellLabelActor() { /*! Destructor. */ -SMESH_CellLabelActor::~SMESH_CellLabelActor() { +SMESH_CellLabelActor::~SMESH_CellLabelActor() +{ //Deleting of cells numbering pipeline //--------------------------------------- myCellsNumDataSet->Delete(); @@ -139,22 +143,29 @@ void SMESH_CellLabelActor::SetFontProperties( SMESH::LabelFont family, int size, myClsTextProp->SetColor( r, g, b ); } -void SMESH_CellLabelActor::SetCellsLabeled(bool theIsCellsLabeled) { +void SMESH_CellLabelActor::SetCellsLabeled(bool theIsCellsLabeled) +{ myTransformFilter->Update(); vtkUnstructuredGrid* aGrid = vtkUnstructuredGrid::SafeDownCast(myTransformFilter->GetOutput()); - if(!aGrid) + if ( !aGrid ) return; myIsCellsLabeled = theIsCellsLabeled && aGrid->GetNumberOfPoints(); - if(myIsCellsLabeled){ + if ( myIsCellsLabeled ) + { myCellsNumDataSet->ShallowCopy(aGrid); vtkUnstructuredGrid *aDataSet = myCellsNumDataSet; int aNbElem = aDataSet->GetNumberOfCells(); vtkIntArray *anArray = vtkIntArray::New(); anArray->SetNumberOfValues(aNbElem); - for(int anId = 0; anId < aNbElem; anId++){ - vtkIdType id = myExtractUnstructuredGrid->GetInputId(anId); - id = (id >=0) ? id : anId; + myExtractUnstructuredGrid->BuildOut2InMap(); + for(int anId = 0; anId < aNbElem; anId++) + { + vtkIdType id = anId; + if(IsImplicitFunctionUsed()) + id = myExtractGeometry->GetElemObjId(id); + id = myExtractUnstructuredGrid->GetInputId(id); + id = (id >=0) ? id : anId; int aSMDSId = myVisualObj->GetElemObjId(id); anArray->SetValue(anId,aSMDSId); } diff --git a/src/OBJECT/SMESH_DeviceActor.cxx b/src/OBJECT/SMESH_DeviceActor.cxx index 2d632782d..66cb811bf 100644 --- a/src/OBJECT/SMESH_DeviceActor.cxx +++ b/src/OBJECT/SMESH_DeviceActor.cxx @@ -142,7 +142,7 @@ SMESH_DeviceActor if(MYDEBUG) MESSAGE("~SMESH_DeviceActor - "<Delete(); - myPlaneCollection->Delete(); + // myPlaneCollection->Delete(); -- it is vtkSmartPointer myProperty->Delete(); myExtractGeometry->Delete(); @@ -172,7 +172,8 @@ SMESH_DeviceActor ::SetStoreGemetryMapping(bool theStoreMapping) { myGeomFilter->SetStoreMapping(theStoreMapping); - SetStoreClippingMapping(theStoreMapping); + // for optimization, switch the mapping explicitly in each filter/algorithm + //SetStoreClippingMapping(theStoreMapping); } @@ -182,7 +183,10 @@ SMESH_DeviceActor { myStoreClippingMapping = theStoreMapping; myExtractGeometry->SetStoreMapping(theStoreMapping && myIsImplicitFunctionUsed); - SetStoreIDMapping(theStoreMapping); + // EAP, 23315 + // Mapping in myExtractUnstructuredGrid and myGeomFilter is ON in the pickable DeviceActor only. + // To show labels, the mapping is computed explicitly via myExtractUnstructuredGrid->BuildOut2InMap(); + //SetStoreIDMapping(theStoreMapping); } @@ -301,8 +305,8 @@ SMESH_DeviceActor if(anIsInitialized){ vtkUnstructuredGrid* aDataSet = vtkUnstructuredGrid::New(); - SetStoreIDMapping(true); - myExtractUnstructuredGrid->Update(); + // SetStoreIDMapping(true); + // myExtractUnstructuredGrid->Update(); vtkUnstructuredGrid* aGrid = myExtractUnstructuredGrid->GetOutput(); aDataSet->ShallowCopy(aGrid); @@ -318,7 +322,9 @@ SMESH_DeviceActor using namespace SMESH::Controls; if(NumericalFunctor* aNumericalFunctor = dynamic_cast(theFunctor.get())) { - for(vtkIdType i = 0; i < aNbCells; i++){ + myExtractUnstructuredGrid->BuildOut2InMap(); + for(vtkIdType i = 0; i < aNbCells; i++) + { vtkIdType anId = myExtractUnstructuredGrid->GetInputId(i); vtkIdType anObjId = myVisualObj->GetElemObjId(anId); double aValue = aNumericalFunctor->GetValue(anObjId); @@ -334,7 +340,9 @@ SMESH_DeviceActor } else if(Predicate* aPredicate = dynamic_cast(theFunctor.get())) { - for(vtkIdType i = 0; i < aNbCells; i++){ + myExtractUnstructuredGrid->BuildOut2InMap(); + for(vtkIdType i = 0; i < aNbCells; i++) + { vtkIdType anId = myExtractUnstructuredGrid->GetInputId(i); vtkIdType anObjId = myVisualObj->GetElemObjId(anId); bool aValue = aPredicate->IsSatisfy(anObjId); diff --git a/src/SMDS/SMDS_Mesh.cxx b/src/SMDS/SMDS_Mesh.cxx index 9fd5499b8..74148811f 100644 --- a/src/SMDS/SMDS_Mesh.cxx +++ b/src/SMDS/SMDS_Mesh.cxx @@ -2671,52 +2671,26 @@ SMDS_Mesh::~SMDS_Mesh() void SMDS_Mesh::Clear() { if (myParent!=NULL) - { + { SMDS_ElemIteratorPtr eIt = elementsIterator(); while ( eIt->more() ) - { - const SMDS_MeshElement *elem = eIt->next(); - myElementIDFactory->ReleaseID(elem->GetID(), elem->getVtkId()); - } + { + const SMDS_MeshElement *elem = eIt->next(); + myElementIDFactory->ReleaseID(elem->GetID(), elem->getVtkId()); + } SMDS_NodeIteratorPtr itn = nodesIterator(); while (itn->more()) - { - const SMDS_MeshNode *node = itn->next(); - myNodeIDFactory->ReleaseID(node->GetID(), node->getVtkId()); - } + { + const SMDS_MeshNode *node = itn->next(); + myNodeIDFactory->ReleaseID(node->GetID(), node->getVtkId()); } + } else - { + { myNodeIDFactory->Clear(); myElementIDFactory->Clear(); - } + } - // SMDS_ElemIteratorPtr itv = elementsIterator(); - // while (itv->more()) - // { - // SMDS_MeshElement* elem = (SMDS_MeshElement*)(itv->next()); - // SMDSAbs_ElementType aType = elem->GetType(); - // switch (aType) - // { - // case SMDSAbs_0DElement: - // delete elem; - // break; - // case SMDSAbs_Edge: - // myEdgePool->destroy(static_cast(elem)); - // break; - // case SMDSAbs_Face: - // myFacePool->destroy(static_cast(elem)); - // break; - // case SMDSAbs_Volume: - // myVolumePool->destroy(static_cast(elem)); - // break; - // case SMDSAbs_Ball: - // myBallPool->destroy(static_cast(elem)); - // break; - // default: - // break; - // } - // } myVolumePool->clear(); myFacePool->clear(); myEdgePool->clear(); @@ -2727,11 +2701,11 @@ void SMDS_Mesh::Clear() SMDS_NodeIteratorPtr itn = nodesIterator(); while (itn->more()) - { - SMDS_MeshNode *node = (SMDS_MeshNode*)(itn->next()); - node->SetPosition(SMDS_SpacePosition::originSpacePosition()); - //myNodePool->destroy(node); - } + { + SMDS_MeshNode *node = (SMDS_MeshNode*)(itn->next()); + node->SetPosition(SMDS_SpacePosition::originSpacePosition()); + //myNodePool->destroy(node); + } myNodePool->clear(); clearVector( myNodes ); @@ -2753,7 +2727,7 @@ void SMDS_Mesh::Clear() // rnv: to fix bug "21125: EDF 1233 SMESH: Degrardation of precision in a test case for quadratic conversion" // using double type for storing coordinates of nodes instead float. points->SetDataType(VTK_DOUBLE); - points->SetNumberOfPoints(0 /*SMDS_Mesh::chunkSize*/); + points->SetNumberOfPoints( 0 ); myGrid->SetPoints( points ); points->Delete(); myGrid->DeleteLinks(); @@ -2766,7 +2740,7 @@ void SMDS_Mesh::Clear() /////////////////////////////////////////////////////////////////////////////// bool SMDS_Mesh::hasConstructionEdges() { - return myHasConstructionEdges; + return myHasConstructionEdges; } /////////////////////////////////////////////////////////////////////////////// @@ -2778,7 +2752,7 @@ bool SMDS_Mesh::hasConstructionEdges() /////////////////////////////////////////////////////////////////////////////// bool SMDS_Mesh::hasConstructionFaces() { - return myHasConstructionFaces; + return myHasConstructionFaces; } /////////////////////////////////////////////////////////////////////////////// @@ -2787,7 +2761,7 @@ bool SMDS_Mesh::hasConstructionFaces() /////////////////////////////////////////////////////////////////////////////// bool SMDS_Mesh::hasInverseElements() { - return myHasInverseElements; + return myHasInverseElements; } /////////////////////////////////////////////////////////////////////////////// @@ -2796,7 +2770,7 @@ bool SMDS_Mesh::hasInverseElements() /////////////////////////////////////////////////////////////////////////////// void SMDS_Mesh::setConstructionEdges(bool b) { - myHasConstructionEdges=b; + myHasConstructionEdges=b; } /////////////////////////////////////////////////////////////////////////////// @@ -2805,7 +2779,7 @@ void SMDS_Mesh::setConstructionEdges(bool b) /////////////////////////////////////////////////////////////////////////////// void SMDS_Mesh::setConstructionFaces(bool b) { - myHasConstructionFaces=b; + myHasConstructionFaces=b; } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/SMESH/SMESH_Mesh.cxx b/src/SMESH/SMESH_Mesh.cxx index bcd4b4185..094dd4fee 100644 --- a/src/SMESH/SMESH_Mesh.cxx +++ b/src/SMESH/SMESH_Mesh.cxx @@ -438,6 +438,7 @@ void SMESH_Mesh::Clear() sm->ComputeSubMeshStateEngine( SMESH_subMesh::CHECK_COMPUTE_STATE ); } } + GetMeshDS()->Modified(); _isModified = false; } diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 3794e7231..44195b8c8 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -299,7 +299,6 @@ void SMESH_Mesh_i::Clear() throw (SALOME::SALOME_Exception) catch(SALOME_Exception & S_ex) { THROW_SALOME_CORBA_EXCEPTION(S_ex.what(), SALOME::BAD_PARAM); } - _impl->GetMeshDS()->Modified(); TPythonDump() << SMESH::SMESH_Mesh_var(_this()) << ".Clear()"; } diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index cec7eb822..47146139f 100644 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -2518,7 +2518,7 @@ class Mesh: return self.mesh.GetElementShape(id) ## Returns the list of submesh elements IDs - # @param Shape a geom object(sub-shape) IOR + # @param Shape a geom object(sub-shape) # Shape must be the sub-shape of a ShapeToMesh() # @return the list of integer values # @ingroup l1_meshinfo @@ -2530,7 +2530,7 @@ class Mesh: return self.mesh.GetSubMeshElementsId(ShapeID) ## Returns the list of submesh nodes IDs - # @param Shape a geom object(sub-shape) IOR + # @param Shape a geom object(sub-shape) # Shape must be the sub-shape of a ShapeToMesh() # @param all If true, gives all nodes of submesh elements, otherwise gives only submesh nodes # @return the list of integer values @@ -2543,7 +2543,7 @@ class Mesh: return self.mesh.GetSubMeshNodesId(ShapeID, all) ## Returns type of elements on given shape - # @param Shape a geom object(sub-shape) IOR + # @param Shape a geom object(sub-shape) # Shape must be a sub-shape of a ShapeToMesh() # @return element type # @ingroup l1_meshinfo -- 2.30.2