From c7e6dcf72a5b8649cdbd97499ef8d3c1ff4708dd Mon Sep 17 00:00:00 2001 From: vsv Date: Mon, 9 Dec 2019 16:09:31 +0300 Subject: [PATCH 01/60] Modification of SMESH_Gen interface --- src/SMESH_I/SMESH_Filter_i.cxx | 12 +--- src/SMESH_I/SMESH_Gen_i.cxx | 95 +++++++++++++++---------------- src/SMESH_I/SMESH_Gen_i.hxx | 5 +- src/SMESH_I/SMESH_Gen_i_1.cxx | 9 +-- src/SMESH_I/SMESH_Mesh_i.cxx | 6 +- src/SMESH_I/SMESH_PythonDump.cxx | 17 ++++-- src/SMESH_SWIG/smeshBuilder.py | 74 +++++++++++------------- src/SMESH_SWIG/smesh_algorithm.py | 3 +- 8 files changed, 103 insertions(+), 118 deletions(-) diff --git a/src/SMESH_I/SMESH_Filter_i.cxx b/src/SMESH_I/SMESH_Filter_i.cxx index de808d62e..c5cc12916 100644 --- a/src/SMESH_I/SMESH_Filter_i.cxx +++ b/src/SMESH_I/SMESH_Filter_i.cxx @@ -903,9 +903,7 @@ void BelongToGeom_i::SetGeom( GEOM::GEOM_Object_ptr theGeom ) { if ( theGeom->_is_nil() ) return; - SMESH_Gen_i* aSMESHGen = SMESH_Gen_i::GetSMESHGen(); - GEOM::GEOM_Gen_ptr aGEOMGen = SMESH_Gen_i::GetGeomEngine(); - TopoDS_Shape aLocShape = aSMESHGen->GetShapeReader()->GetShape( aGEOMGen, theGeom ); + TopoDS_Shape aLocShape = SMESH_Gen_i::GetSMESHGen()->GeomObjectToShape( theGeom ); myBelongToGeomPtr->SetGeom( aLocShape ); TPythonDump()<_is_nil() ) return; - SMESH_Gen_i* aSMESHGen = SMESH_Gen_i::GetSMESHGen(); - GEOM::GEOM_Gen_ptr aGEOMGen = SMESH_Gen_i::GetGeomEngine(); - TopoDS_Shape aLocShape = aSMESHGen->GetShapeReader()->GetShape( aGEOMGen, theGeom ); + TopoDS_Shape aLocShape = SMESH_Gen_i::GetSMESHGen()->GeomObjectToShape( theGeom ); if ( aLocShape.ShapeType() == TopAbs_FACE ) { @@ -1173,9 +1169,7 @@ void LyingOnGeom_i::SetGeom( GEOM::GEOM_Object_ptr theGeom ) { if ( theGeom->_is_nil() ) return; - SMESH_Gen_i* aSMESHGen = SMESH_Gen_i::GetSMESHGen(); - GEOM::GEOM_Gen_ptr aGEOMGen = SMESH_Gen_i::GetGeomEngine(); - TopoDS_Shape aLocShape = aSMESHGen->GetShapeReader()->GetShape( aGEOMGen, theGeom ); + TopoDS_Shape aLocShape = SMESH_Gen_i::GetSMESHGen()->GeomObjectToShape( theGeom ); myLyingOnGeomPtr->SetGeom( aLocShape ); TPythonDump()<FindOrLoad_Component("FactoryServer","GEOM") ); - //CCRT return aGeomEngine._retn(); - if(CORBA::is_nil(myGeomGen)) - { - Engines::EngineComponent_ptr temp=GetLCC()->FindOrLoad_Component("FactoryServer","GEOM"); - myGeomGen=GEOM::GEOM_Gen::_narrow(temp); - } + +GEOM::GEOM_Gen_var SMESH_Gen_i::GetGeomEngine( bool isShaper ) +{ + Engines::EngineComponent_ptr temp = + GetLCC()->FindOrLoad_Component( isShaper ? "FactoryServerPy" : "FactoryServer", + isShaper ? "SHAPERSTUDY" : "GEOM" ); + myGeomGen = GEOM::GEOM_Gen::_narrow( temp ); + return myGeomGen; } +//============================================================================= +/*! + * GetGeomEngine [ static ] + * + * Get GEOM::GEOM_Gen reference + */ +//============================================================================= + +GEOM::GEOM_Gen_var SMESH_Gen_i::GetGeomEngine( GEOM::GEOM_Object_ptr go ) +{ + GEOM::GEOM_Gen_var gen; + if ( !CORBA::is_nil( go )) + gen = go->GetGen(); + return gen; +} + //============================================================================= /*! * SMESH_Gen_i::SMESH_Gen_i @@ -687,11 +703,18 @@ void SMESH_Gen_i::UpdateStudy() myStudyContext = new StudyContext; SALOMEDS::Study_var aStudy = getStudyServant(); - if ( !CORBA::is_nil( aStudy ) ) { + if ( !CORBA::is_nil( aStudy ) ) + { SALOMEDS::StudyBuilder_var aStudyBuilder = aStudy->NewBuilder(); + SALOMEDS::SComponent_wrap GEOM_var = aStudy->FindComponent( "GEOM" ); if( !GEOM_var->_is_nil() ) - aStudyBuilder->LoadWith( GEOM_var, GetGeomEngine() ); + aStudyBuilder->LoadWith( GEOM_var, GetGeomEngine( /*isShaper=*/false ) ); + + GEOM_var = aStudy->FindComponent( "SHAPERSTUDY" ); + if( !GEOM_var->_is_nil() ) + aStudyBuilder->LoadWith( GEOM_var, GetGeomEngine( /*isShaper=*/true ) ); + // NPAL16168, issue 0020210 // Let meshes update their data depending on GEOM groups that could change CORBA::String_var compDataType = ComponentDataType(); @@ -2305,7 +2328,7 @@ SMESH_Gen_i::GetGeometryByMeshElement( SMESH::SMESH_Mesh_ptr theMesh, GEOM::GEOM_Object_wrap geom = FindGeometryByMeshElement(theMesh, theElementID); if ( !geom->_is_nil() ) { GEOM::GEOM_Object_var mainShape = theMesh->GetShapeToMesh(); - GEOM::GEOM_Gen_ptr geomGen = GetGeomEngine(); + GEOM::GEOM_Gen_ptr geomGen = GetGeomEngine( geom ); // try to find the corresponding SObject SALOMEDS::SObject_wrap SObj = ObjectToSObject( geom.in() ); @@ -2369,7 +2392,7 @@ SMESH_Gen_i::FindGeometryByMeshElement( SMESH::SMESH_Mesh_ptr theMesh, THROW_SALOME_CORBA_EXCEPTION( "bad Mesh reference", SALOME::BAD_PARAM ); GEOM::GEOM_Object_var mainShape = theMesh->GetShapeToMesh(); - GEOM::GEOM_Gen_ptr geomGen = GetGeomEngine(); + GEOM::GEOM_Gen_ptr geomGen = GetGeomEngine( mainShape ); // get a core mesh DS SMESH_Mesh_i* meshServant = SMESH::DownCast( theMesh ); @@ -3052,7 +3075,7 @@ namespace // utils for CopyMeshWithGeom() std::string newMainEntry = newEntry.in(); SALOMEDS::Study_var study = myGen_i->getStudyServant(); - GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine(); + GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine( mainShapeNew ); GEOM::GEOM_IShapesOperations_wrap op = geomGen->GetIShapesOperations(); mySubshapes = op->GetExistingSubObjects( mainShapeNew, /*groupsOnly=*/false ); @@ -3107,7 +3130,7 @@ namespace // utils for CopyMeshWithGeom() return GEOM::GEOM_Object::_duplicate( oldShape ); // shape independent of the old shape GEOM::GEOM_Object_var mainShapeNew = myNewMesh_i->GetShapeToMesh(); - GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine(); + GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine( mainShapeNew ); // try to find by entry or name if ( myToPublish ) @@ -3329,7 +3352,7 @@ namespace // utils for CopyMeshWithGeom() GEOM::GEOM_Object_var mainShapeNew = myNewMesh_i->GetShapeToMesh(); GEOM::GEOM_Object_var mainShapeOld = mySrcMesh_i->GetShapeToMesh(); - GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine(); + GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine( mainShapeNew ); GEOM::GEOM_IShapesOperations_wrap op = geomGen->GetIShapesOperations(); try { @@ -3351,7 +3374,7 @@ namespace // utils for CopyMeshWithGeom() GEOM::GEOM_Object_var newShape; GEOM::GEOM_Object_var mainShapeNew = myNewMesh_i->GetShapeToMesh(); - GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine(); + GEOM::GEOM_Gen_var geomGen = myGen_i->GetGeomEngine( mainShapeNew ); GEOM::GEOM_IShapesOperations_wrap op = geomGen->GetIShapesOperations(); try { @@ -5021,23 +5044,6 @@ SALOMEDS::TMPFile* SMESH_Gen_i::SaveASCII( SALOMEDS::SComponent_ptr theComponent return anAsciiStreamFile._retn(); } -//============================================================================= -/*! - * SMESH_Gen_i::loadGeomData - * - * Load GEOM module data - */ -//============================================================================= - -void SMESH_Gen_i::loadGeomData( SALOMEDS::SComponent_ptr theCompRoot ) -{ - if ( theCompRoot->_is_nil() ) - return; - - SALOMEDS::StudyBuilder_var aStudyBuilder = getStudyServant()->NewBuilder(); - aStudyBuilder->LoadWith( theCompRoot, GetGeomEngine() ); -} - //============================================================================= /*! * SMESH_Gen_i::Load @@ -5051,17 +5057,10 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, const char* theURL, bool isMultiFile ) { - // localizing + UpdateStudy(); // load geom data Kernel_Utils::Localizer loc; - //if (!myStudyContext) - UpdateStudy(); SALOMEDS::Study_var aStudy = getStudyServant(); - /* if( !theComponent->_is_nil() ) - { - if( !aStudy->FindComponent( "GEOM" )->_is_nil() ) - loadGeomData( aStudy->FindComponent( "GEOM" ) ); - }*/ // Get temporary files location TCollection_AsciiString tmpDir = @@ -5383,11 +5382,7 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, aDataset->CloseOnDisk(); if ( strlen( refFromFile ) > 0 ) { SALOMEDS::SObject_wrap shapeSO = aStudy->FindObjectID( refFromFile ); - - // Make sure GEOM data are loaded first - //loadGeomData( shapeSO->GetFatherComponent() ); - - CORBA::Object_var shapeObject = SObjectToObject( shapeSO ); + CORBA::Object_var shapeObject = SObjectToObject( shapeSO ); if ( !CORBA::is_nil( shapeObject ) ) { aShapeObject = GEOM::GEOM_Object::_narrow( shapeObject ); if ( !aShapeObject->_is_nil() ) diff --git a/src/SMESH_I/SMESH_Gen_i.hxx b/src/SMESH_I/SMESH_Gen_i.hxx index 52cd172b4..93359e4bf 100644 --- a/src/SMESH_I/SMESH_Gen_i.hxx +++ b/src/SMESH_I/SMESH_Gen_i.hxx @@ -109,7 +109,8 @@ public: // Get SALOME_LifeCycleCORBA object static SALOME_LifeCycleCORBA* GetLCC(); // Retrieve and get GEOM engine reference - static GEOM::GEOM_Gen_var GetGeomEngine(); + static GEOM::GEOM_Gen_var GetGeomEngine( bool isShaper ); + static GEOM::GEOM_Gen_var GetGeomEngine( GEOM::GEOM_Object_ptr ); // Get object of the CORBA reference static PortableServer::ServantBase_var GetServant( CORBA::Object_ptr theObject ); // Get CORBA object corresponding to the SALOMEDS::SObject @@ -635,8 +636,6 @@ private: void highLightInvalid( SALOMEDS::SObject_ptr theSObject, bool isInvalid ); - static void loadGeomData( SALOMEDS::SComponent_ptr theCompRoot ); - SMESH::mesh_array* CreateMeshesFromMEDorSAUV( const char* theFileName, SMESH::DriverMED_ReadStatus& theStatus, const char* theCommandNameForPython, diff --git a/src/SMESH_I/SMESH_Gen_i_1.cxx b/src/SMESH_I/SMESH_Gen_i_1.cxx index 1d7301ffe..1e0d6187b 100644 --- a/src/SMESH_I/SMESH_Gen_i_1.cxx +++ b/src/SMESH_I/SMESH_Gen_i_1.cxx @@ -252,9 +252,10 @@ GEOM::GEOM_Object_ptr SMESH_Gen_i::ShapeToGeomObject (const TopoDS_Shape& theSha TopoDS_Shape SMESH_Gen_i::GeomObjectToShape(GEOM::GEOM_Object_ptr theGeomObject) { TopoDS_Shape S; - if ( !theGeomObject->_is_nil() && !theGeomObject->_non_existent() ) { - GEOM_Client* aClient = GetShapeReader(); - GEOM::GEOM_Gen_ptr aGeomEngine = GetGeomEngine(); + if ( !theGeomObject->_is_nil() && !theGeomObject->_non_existent() ) + { + GEOM_Client* aClient = GetShapeReader(); + GEOM::GEOM_Gen_ptr aGeomEngine = GetGeomEngine( theGeomObject ); if ( aClient && !aGeomEngine->_is_nil () ) S = aClient->GetShape( aGeomEngine, theGeomObject ); } @@ -263,7 +264,7 @@ TopoDS_Shape SMESH_Gen_i::GeomObjectToShape(GEOM::GEOM_Object_ptr theGeomObject) //======================================================================= //function : publish -//purpose : +//purpose : //======================================================================= static SALOMEDS::SObject_ptr publish(CORBA::Object_ptr theIOR, diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index f0dfcda69..244f75147 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -1980,7 +1980,7 @@ void SMESH_Mesh_i::addGeomGroupData(GEOM::GEOM_Object_ptr theGeomObj, if ( groupSO->_is_nil() ) return; // group indices - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine(); + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( theGeomObj ); GEOM::GEOM_IGroupOperations_wrap groupOp = geomGen->GetIGroupOperations(); GEOM::ListOfLong_var ids = groupOp->GetObjects( theGeomObj ); @@ -2039,7 +2039,7 @@ TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData) // get indices of group items set curIndices; - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine(); + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); GEOM::GEOM_IGroupOperations_wrap groupOp = geomGen->GetIGroupOperations(); GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); @@ -2227,7 +2227,7 @@ void SMESH_Mesh_i::CheckGeomModif() GEOM_Client* geomClient = _gen_i->GetShapeReader(); if ( !geomClient ) return; - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine(); + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( mainGO ); if ( geomGen->_is_nil() ) return; CORBA::String_var ior = geomGen->GetStringFromIOR( mainGO ); diff --git a/src/SMESH_I/SMESH_PythonDump.cxx b/src/SMESH_I/SMESH_PythonDump.cxx index 93559ab36..0687ddc0c 100644 --- a/src/SMESH_I/SMESH_PythonDump.cxx +++ b/src/SMESH_I/SMESH_PythonDump.cxx @@ -1061,12 +1061,15 @@ TCollection_AsciiString SMESH_Gen_i::DumpPython_impl isHistoricalDump ); bool importGeom = false; - GEOM::GEOM_Gen_ptr geom = GetGeomEngine(); + GEOM::GEOM_Gen_ptr geom[2]; + for ( int isShaper = 0; isShaper < 2; ++isShaper ) { + geom[ isShaper ] = GetGeomEngine( isShaper ); + if ( CORBA::is_nil( geom[ isShaper ])) + continue; // Add names of GEOM objects to theObjectNames to exclude same names of SMESH objects - GEOM::string_array_var aGeomNames = geom->GetAllDumpNames(); - int ign = 0, nbgn = aGeomNames->length(); - for (; ign < nbgn; ign++) { + GEOM::string_array_var aGeomNames = geom[ isShaper ]->GetAllDumpNames(); + for ( CORBA::ULong ign = 0; ign < aGeomNames->length(); ign++) { TCollection_AsciiString aName = aGeomNames[ign].in(); theObjectNames.Bind(aName, "1"); } @@ -1100,7 +1103,11 @@ TCollection_AsciiString SMESH_Gen_i::DumpPython_impl anUpdatedScript += aLine.SubString( aStart, aSeq->Value(i) - 1 ); // line part before i-th entry anEntry = aLine.SubString( aSeq->Value(i), aSeq->Value(i + 1) ); // is a GEOM object? - CORBA::String_var geomName = geom->GetDumpName( anEntry.ToCString() ); + CORBA::String_var geomName; + if ( !CORBA::is_nil( geom[0] )) + geomName = geom[0]->GetDumpName( anEntry.ToCString() ); + if (( !geomName.in() || !geomName.in()[0] ) && !CORBA::is_nil( geom[1] )) + geomName = geom[1]->GetDumpName( anEntry.ToCString() ); if ( !geomName.in() || !geomName.in()[0] ) { // is a SMESH object if ( theObjectNames.IsBound( anEntry )) { diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index df65f685f..8fd7b6166 100644 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -318,27 +318,13 @@ def AssureGeomPublished(mesh, geom, name=''): mesh.geompyD.addToStudyInFather( mesh.geom, geom, name ) return -def FirstVertexOnCurve(mesh, edge): - """ - Returns: - the first vertex of a geometrical edge by ignoring orientation - """ - vv = mesh.geompyD.SubShapeAll( edge, geomBuilder.geomBuilder.ShapeType["VERTEX"]) - if not vv: - raise TypeError("Given object has no vertices") - if len( vv ) == 1: return vv[0] - v0 = mesh.geompyD.MakeVertexOnCurve(edge,0.) - xyz = mesh.geompyD.PointCoordinates( v0 ) # coords of the first vertex - xyz1 = mesh.geompyD.PointCoordinates( vv[0] ) - xyz2 = mesh.geompyD.PointCoordinates( vv[1] ) - dist1, dist2 = 0,0 - for i in range(3): - dist1 += abs( xyz[i] - xyz1[i] ) - dist2 += abs( xyz[i] - xyz2[i] ) - if dist1 < dist2: - return vv[0] - else: - return vv[1] +# def FirstVertexOnCurve(mesh, edge): +# """ +# Returns: +# the first vertex of a geometrical edge by ignoring orientation +# """ +# return mesh.geompyD.GetVertexByIndex( edge, 0, False ) + smeshInst = None """ @@ -531,8 +517,8 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): Returns: :class:`SMESH.PointStruct` """ - - [x, y, z] = self.geompyD.PointCoordinates(theVertex) + geompyD = theVertex.GetGen() + [x, y, z] = geompyD.PointCoordinates(theVertex) return PointStruct(x,y,z) def GetDirStruct(self,theVector): @@ -545,13 +531,13 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): Returns: :class:`SMESH.DirStruct` """ - - vertices = self.geompyD.SubShapeAll( theVector, geomBuilder.geomBuilder.ShapeType["VERTEX"] ) + geompyD = theVector.GetGen() + vertices = geompyD.SubShapeAll( theVector, geomBuilder.geomBuilder.ShapeType["VERTEX"] ) if(len(vertices) != 2): print("Error: vector object is incorrect.") return None - p1 = self.geompyD.PointCoordinates(vertices[0]) - p2 = self.geompyD.PointCoordinates(vertices[1]) + p1 = geompyD.PointCoordinates(vertices[0]) + p2 = geompyD.PointCoordinates(vertices[1]) pnt = PointStruct(p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]) dirst = DirStruct(pnt) return dirst @@ -581,28 +567,29 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): :class:`SMESH.AxisStruct` """ import GEOM - edges = self.geompyD.SubShapeAll( theObj, geomBuilder.geomBuilder.ShapeType["EDGE"] ) + geompyD = theObj.GetGen() + edges = geompyD.SubShapeAll( theObj, geomBuilder.geomBuilder.ShapeType["EDGE"] ) axis = None if len(edges) > 1: - vertex1, vertex2 = self.geompyD.SubShapeAll( edges[0], geomBuilder.geomBuilder.ShapeType["VERTEX"] ) - vertex3, vertex4 = self.geompyD.SubShapeAll( edges[1], geomBuilder.geomBuilder.ShapeType["VERTEX"] ) - vertex1 = self.geompyD.PointCoordinates(vertex1) - vertex2 = self.geompyD.PointCoordinates(vertex2) - vertex3 = self.geompyD.PointCoordinates(vertex3) - vertex4 = self.geompyD.PointCoordinates(vertex4) + vertex1, vertex2 = geompyD.SubShapeAll( edges[0], geomBuilder.geomBuilder.ShapeType["VERTEX"] ) + vertex3, vertex4 = geompyD.SubShapeAll( edges[1], geomBuilder.geomBuilder.ShapeType["VERTEX"] ) + vertex1 = geompyD.PointCoordinates(vertex1) + vertex2 = geompyD.PointCoordinates(vertex2) + vertex3 = geompyD.PointCoordinates(vertex3) + vertex4 = geompyD.PointCoordinates(vertex4) v1 = [vertex2[0]-vertex1[0], vertex2[1]-vertex1[1], vertex2[2]-vertex1[2]] v2 = [vertex4[0]-vertex3[0], vertex4[1]-vertex3[1], vertex4[2]-vertex3[2]] normal = [ v1[1]*v2[2]-v2[1]*v1[2], v1[2]*v2[0]-v2[2]*v1[0], v1[0]*v2[1]-v2[0]*v1[1] ] axis = AxisStruct(vertex1[0], vertex1[1], vertex1[2], normal[0], normal[1], normal[2]) axis._mirrorType = SMESH.SMESH_MeshEditor.PLANE elif len(edges) == 1: - vertex1, vertex2 = self.geompyD.SubShapeAll( edges[0], geomBuilder.geomBuilder.ShapeType["VERTEX"] ) - p1 = self.geompyD.PointCoordinates( vertex1 ) - p2 = self.geompyD.PointCoordinates( vertex2 ) + vertex1, vertex2 = geompyD.SubShapeAll( edges[0], geomBuilder.geomBuilder.ShapeType["VERTEX"] ) + p1 = geompyD.PointCoordinates( vertex1 ) + p2 = geompyD.PointCoordinates( vertex2 ) axis = AxisStruct(p1[0], p1[1], p1[2], p2[0]-p1[0], p2[1]-p1[1], p2[2]-p1[2]) axis._mirrorType = SMESH.SMESH_MeshEditor.AXIS elif theObj.GetShapeType() == GEOM.VERTEX: - x,y,z = self.geompyD.PointCoordinates( theObj ) + x,y,z = geompyD.PointCoordinates( theObj ) axis = AxisStruct( x,y,z, 1,0,0,) axis._mirrorType = SMESH.SMESH_MeshEditor.POINT return axis @@ -972,7 +959,8 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): name = aCriterion.ThresholdStr if not name: name = "%s_%s"%(aThreshold.GetShapeType(), id(aThreshold)%10000) - aCriterion.ThresholdID = self.geompyD.addToStudy( aThreshold, name ) + geompyD = aThreshold.GetGen() + aCriterion.ThresholdID = geompyD.addToStudy( aThreshold, name ) # or a name of GEOM object elif isinstance( aThreshold, str ): aCriterion.ThresholdStr = aThreshold @@ -1023,7 +1011,8 @@ class smeshBuilder( SMESH._objref_SMESH_Gen, object ): name = aThreshold.GetName() if not name: name = "%s_%s"%(aThreshold.GetShapeType(), id(aThreshold)%10000) - aCriterion.ThresholdID = self.geompyD.addToStudy( aThreshold, name ) + geompyD = aThreshold.GetGen() + aCriterion.ThresholdID = geompyD.addToStudy( aThreshold, name ) elif isinstance(aThreshold, int): # node id aCriterion.Threshold = aThreshold elif isinstance(aThreshold, list): # 3 point coordinates @@ -1633,14 +1622,15 @@ class Mesh(metaclass = MeshMeta): Parameters: theMesh: a :class:`SMESH.SMESH_Mesh` object """ - - # do not call Register() as this prevents mesh servant deletion at closing study #if self.mesh: self.mesh.UnRegister() self.mesh = theMesh if self.mesh: #self.mesh.Register() self.geom = self.mesh.GetShapeToMesh() + if self.geom: + self.geompyD = self.geom.GetGen() + pass pass def GetMesh(self): diff --git a/src/SMESH_SWIG/smesh_algorithm.py b/src/SMESH_SWIG/smesh_algorithm.py index f14fa318d..d962b4548 100644 --- a/src/SMESH_SWIG/smesh_algorithm.py +++ b/src/SMESH_SWIG/smesh_algorithm.py @@ -392,7 +392,6 @@ class Mesh_Algorithm: into a list acceptable to SetReversedEdges() of some 1D hypotheses """ - from salome.smesh.smeshBuilder import FirstVertexOnCurve resList = [] geompy = self.mesh.geompyD for i in reverseList: @@ -417,7 +416,7 @@ class Mesh_Algorithm: if e.GetShapeType() != geomBuilder.GEOM.EDGE or \ v.GetShapeType() != geomBuilder.GEOM.VERTEX: raise TypeError("A list item must be a tuple (edge, 1st_vertex_of_edge)") - vFirst = FirstVertexOnCurve( self.mesh, e ) + vFirst = geompy.GetVertexByIndex( e, 0, False ) tol = geompy.Tolerance( vFirst )[-1] if geompy.MinDistance( v, vFirst ) > 1.5*tol: resList.append( geompy.GetSubShapeID(self.mesh.geom, e )) From b6155253f8dcf165edf0b6d0d65165594abf61fb Mon Sep 17 00:00:00 2001 From: mpv Date: Tue, 17 Dec 2019 14:37:02 +0300 Subject: [PATCH 02/60] Fixes for SMESH working with SHAPERSTUDY --- src/SMESH_I/SMESH_Gen_i.cxx | 4 ++-- src/SMESH_I/SMESH_Gen_i_1.cxx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index e87641eed..3003a0a52 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -278,7 +278,7 @@ SALOME_LifeCycleCORBA* SMESH_Gen_i::GetLCC() GEOM::GEOM_Gen_var SMESH_Gen_i::GetGeomEngine( bool isShaper ) { Engines::EngineComponent_ptr temp = - GetLCC()->FindOrLoad_Component( isShaper ? "FactoryServerPy" : "FactoryServer", + GetLCC()->FindOrLoad_Component( isShaper ? "FactoryServer" : "FactoryServer", isShaper ? "SHAPERSTUDY" : "GEOM" ); myGeomGen = GEOM::GEOM_Gen::_narrow( temp ); @@ -295,7 +295,7 @@ GEOM::GEOM_Gen_var SMESH_Gen_i::GetGeomEngine( bool isShaper ) GEOM::GEOM_Gen_var SMESH_Gen_i::GetGeomEngine( GEOM::GEOM_Object_ptr go ) { - GEOM::GEOM_Gen_var gen; + GEOM::GEOM_Gen_ptr gen; if ( !CORBA::is_nil( go )) gen = go->GetGen(); return gen; diff --git a/src/SMESH_I/SMESH_Gen_i_1.cxx b/src/SMESH_I/SMESH_Gen_i_1.cxx index 1e0d6187b..0b258bf08 100644 --- a/src/SMESH_I/SMESH_Gen_i_1.cxx +++ b/src/SMESH_I/SMESH_Gen_i_1.cxx @@ -255,7 +255,7 @@ TopoDS_Shape SMESH_Gen_i::GeomObjectToShape(GEOM::GEOM_Object_ptr theGeomObject) if ( !theGeomObject->_is_nil() && !theGeomObject->_non_existent() ) { GEOM_Client* aClient = GetShapeReader(); - GEOM::GEOM_Gen_ptr aGeomEngine = GetGeomEngine( theGeomObject ); + GEOM::GEOM_Gen_var aGeomEngine = GetGeomEngine( theGeomObject ); if ( aClient && !aGeomEngine->_is_nil () ) S = aClient->GetShape( aGeomEngine, theGeomObject ); } From c56d066c64504e0bf05d4d2e0596de5c60bc532b Mon Sep 17 00:00:00 2001 From: mpv Date: Thu, 19 Dec 2019 18:24:56 +0300 Subject: [PATCH 03/60] Implementation of the automatic breaking the link if the shape is modified or deleted --- src/SMESH_I/SMESH_Mesh_i.cxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 244f75147..c2cf46593 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -2241,6 +2241,13 @@ void SMESH_Mesh_i::CheckGeomModif() if ( newShape.IsNull() ) return; + // for the SHAPER-STUDY: the geometry may be updated, so, add a warning icon + if (_mainShapeTick != mainGO->GetTick()) { + SALOMEDS::SObject_wrap meshSO = _gen_i->ObjectToSObject( me ); + if ( !meshSO->_is_nil()) + _gen_i->SetPixMap(meshSO, "ICON_SMESH_TREE_MESH_WARN"); + } + _mainShapeTick = mainGO->GetTick(); SMESHDS_Mesh * meshDS = _impl->GetMeshDS(); From 779967e2f5d9692aa1992e34f327379cf285c143 Mon Sep 17 00:00:00 2001 From: vsv Date: Fri, 20 Dec 2019 13:04:00 +0300 Subject: [PATCH 04/60] Create "Break link" pop-up menu item --- src/SMESHGUI/SMESHGUI.cxx | 33 ++++++++++++++++++++++++++++++ src/SMESHGUI/SMESHGUI_Operations.h | 2 ++ src/SMESHGUI/SMESH_msg_en.ts | 13 ++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 82d159350..4a612b59d 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -1382,6 +1382,31 @@ namespace } } + // Break link with Shaper model + void breakShaperLink() + { + LightApp_SelectionMgr *aSel = SMESHGUI::selectionMgr(); + SALOME_ListIO selected; + if (aSel) { + aSel->selectedObjects(selected); + if (selected.Extent()) { + Handle(SALOME_InteractiveObject) anIObject = selected.First(); + _PTR(Study) aStudy = SMESH::getStudy(); + _PTR(SObject) aSObj = aStudy->FindObjectID(anIObject->getEntry()); + if (aSObj) { + std::string aName = aSObj->GetName(); + QMessageBox::StandardButton aRes = SUIT_MessageBox::warning(SMESHGUI::desktop(), + QObject::tr("SMESH_WRN_WARNING"), + QObject::tr("MSG_BREAK_SHAPER_LINK").arg(aName.c_str()), + SUIT_MessageBox::Yes | SUIT_MessageBox::No, SUIT_MessageBox::No); + if (aRes == SUIT_MessageBox::Yes) { + // Remove link here + } + } + } + } + } + void SetDisplayMode(int theCommandID, VTK::MarkerMap& theMarkerMap) { SALOME_ListIO selected; @@ -3751,6 +3776,9 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpSortChild: ::sortChildren(); break; + case SMESHOp::OpBreakLink: + ::breakShaperLink(); + break; } @@ -4084,6 +4112,8 @@ void SMESHGUI::initialize( CAM_Application* app ) createSMESHAction( SMESHOp::OpSortChild, "SORT_CHILD_ITEMS" ); + createSMESHAction( SMESHOp::OpBreakLink, "BREAK_SHAPER_LINK" ); + QList aCtrlActions; aCtrlActions << SMESHOp::OpFreeNode << SMESHOp::OpEqualNode << SMESHOp::OpNodeConnectivityNb // node controls @@ -4857,6 +4887,9 @@ void SMESHGUI::initialize( CAM_Application* app ) popupMgr()->setRule( action( SMESHOp::OpSortChild ), "$component={'SMESH'} and client='ObjectBrowser' and isContainer and nbChildren>1", QtxPopupMgr::VisibleRule ); popupMgr()->insert( separator(), -1, -1 ); + popupMgr()->insert( action( SMESHOp::OpBreakLink), -1, -1 ); + popupMgr()->setRule( action( SMESHOp::OpBreakLink), "$component={'SHAPERSTUDY'} and client='ObjectBrowser'", QtxPopupMgr::VisibleRule ); + connect( application(), SIGNAL( viewManagerActivated( SUIT_ViewManager* ) ), this, SLOT( onViewManagerActivated( SUIT_ViewManager* ) ) ); diff --git a/src/SMESHGUI/SMESHGUI_Operations.h b/src/SMESHGUI/SMESHGUI_Operations.h index f66ba0f57..c7b7425d2 100644 --- a/src/SMESHGUI/SMESHGUI_Operations.h +++ b/src/SMESHGUI/SMESHGUI_Operations.h @@ -230,6 +230,8 @@ namespace SMESHOp { OpClipping = 6100, // POPUP MENU - CLIPPING // SortChild ----------------------//-------------------------------- OpSortChild = 6110, // POPUP MENU - SORT CHILDREN + // Break link with Shaper object --//-------------------------------- + OpBreakLink = 6120, // POPUP MENU - Break link with Shaper // Advanced -----------------------//-------------------------------- OpAdvancedNoOp = 10000, // NO OPERATION (advanced operations base) //@@ insert new functions before this line @@ do not remove this line @@// diff --git a/src/SMESHGUI/SMESH_msg_en.ts b/src/SMESHGUI/SMESH_msg_en.ts index 725431f1c..635580697 100644 --- a/src/SMESHGUI/SMESH_msg_en.ts +++ b/src/SMESHGUI/SMESH_msg_en.ts @@ -4503,6 +4503,19 @@ It can't be deleted STB_SORT_CHILD_ITEMS Sort child items + + MEN_BREAK_SHAPER_LINK + Break link + + + STB_BREAK_SHAPER_LINK + Break link with Shaper model + + + MSG_BREAK_SHAPER_LINK + A link with Shaper model for object %1 will be broken. +Continue? + SMESH_ADVANCED Advanced From b8ddb1330cad42ad5381912dd267d1e39182daae Mon Sep 17 00:00:00 2001 From: vsv Date: Fri, 20 Dec 2019 15:53:23 +0300 Subject: [PATCH 05/60] Define canBreakLink parameter --- src/SMESHGUI/SMESHGUI.cxx | 2 +- src/SMESHGUI/SMESHGUI_Selection.cxx | 15 +++++++++++++++ src/SMESHGUI/SMESHGUI_Selection.h | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 4a612b59d..5a3747d0c 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -4888,7 +4888,7 @@ void SMESHGUI::initialize( CAM_Application* app ) popupMgr()->insert( separator(), -1, -1 ); popupMgr()->insert( action( SMESHOp::OpBreakLink), -1, -1 ); - popupMgr()->setRule( action( SMESHOp::OpBreakLink), "$component={'SHAPERSTUDY'} and client='ObjectBrowser'", QtxPopupMgr::VisibleRule ); + popupMgr()->setRule( action( SMESHOp::OpBreakLink), "$component={'SHAPERSTUDY'} and client='ObjectBrowser' and canBreakLink", QtxPopupMgr::VisibleRule ); connect( application(), SIGNAL( viewManagerActivated( SUIT_ViewManager* ) ), this, SLOT( onViewManagerActivated( SUIT_ViewManager* ) ) ); diff --git a/src/SMESHGUI/SMESHGUI_Selection.cxx b/src/SMESHGUI/SMESHGUI_Selection.cxx index d0cd5c4dc..d4b79054e 100644 --- a/src/SMESHGUI/SMESHGUI_Selection.cxx +++ b/src/SMESHGUI/SMESHGUI_Selection.cxx @@ -140,6 +140,7 @@ QVariant SMESHGUI_Selection::parameter( const int ind, const QString& p ) const else if ( p=="nbChildren") val = QVariant( nbChildren( ind ) ); else if ( p=="isContainer") val = QVariant( isContainer( ind ) ); else if ( p=="guiState") val = QVariant( guiState() ); + else if ( p=="canBreakLink") val = QVariant( canBreakLink(ind) ); if ( val.isValid() ) return val; @@ -605,6 +606,20 @@ bool SMESHGUI_Selection::hasGeomReference( int ind ) const return false; } +//======================================================================= +//function : canBreakLink +//purpose : returns true if selected object is a Shaper object and it can break link +//======================================================================= + +bool SMESHGUI_Selection::canBreakLink( int ind ) const +{ + if ( ind >= 0 && ind < myTypes.count()) { + _PTR(SObject) so = SMESH::getStudy()->FindObjectID( entry( ind ).toUtf8().data() ); + return true; + } + return false; +} + //======================================================================= //function : isEditableHyp //purpose : diff --git a/src/SMESHGUI/SMESHGUI_Selection.h b/src/SMESHGUI/SMESHGUI_Selection.h index 7ebf07a26..6ca2c0cb1 100644 --- a/src/SMESHGUI/SMESHGUI_Selection.h +++ b/src/SMESHGUI/SMESHGUI_Selection.h @@ -61,6 +61,7 @@ public: virtual bool hasGeomReference( int ) const; virtual bool isEditableHyp( int ) const; virtual bool isVisible( int ) const; + virtual bool canBreakLink(int) const; virtual bool isQuadratic( int ) const; virtual QString quadratic2DMode( int ) const; From c47c00f480879555202a9fc5403de887dad3ad99 Mon Sep 17 00:00:00 2001 From: vsv Date: Fri, 20 Dec 2019 16:13:06 +0300 Subject: [PATCH 06/60] Call BreakLinks method --- src/SMESHGUI/SMESHGUI.cxx | 4 +++- src/SMESHGUI/SMESHGUI_Selection.cxx | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 5a3747d0c..776e7653c 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -1400,7 +1400,9 @@ namespace QObject::tr("MSG_BREAK_SHAPER_LINK").arg(aName.c_str()), SUIT_MessageBox::Yes | SUIT_MessageBox::No, SUIT_MessageBox::No); if (aRes == SUIT_MessageBox::Yes) { - // Remove link here + GEOM::GEOM_Object_var aObject = SMESH::SObjectToInterface(aSObj); + if (!aObject->_is_nil()) + aObject->BreakLinks(); } } } diff --git a/src/SMESHGUI/SMESHGUI_Selection.cxx b/src/SMESHGUI/SMESHGUI_Selection.cxx index d4b79054e..c79334554 100644 --- a/src/SMESHGUI/SMESHGUI_Selection.cxx +++ b/src/SMESHGUI/SMESHGUI_Selection.cxx @@ -614,8 +614,10 @@ bool SMESHGUI_Selection::hasGeomReference( int ind ) const bool SMESHGUI_Selection::canBreakLink( int ind ) const { if ( ind >= 0 && ind < myTypes.count()) { - _PTR(SObject) so = SMESH::getStudy()->FindObjectID( entry( ind ).toUtf8().data() ); - return true; + _PTR(SObject) aSObject = SMESH::getStudy()->FindObjectID( entry( ind ).toUtf8().data() ); + GEOM::GEOM_Object_var aObject = SMESH::SObjectToInterface(aSObject); + if (!aObject->_is_nil()) + return aObject->IsParametrical(); } return false; } From 96a86667a9ffe5128ffeb08720a9ced6f8b3954b Mon Sep 17 00:00:00 2001 From: vsv Date: Mon, 23 Dec 2019 14:57:39 +0300 Subject: [PATCH 07/60] Transfer BreakLink from Object to Gen --- src/SMESHGUI/SMESHGUI.cxx | 27 +++++++++++++++++++++++---- src/SMESHGUI/SMESHGUI_Selection.cxx | 23 +++++++++++++++++++---- src/SMESHGUI/SMESHGUI_Selection.h | 2 ++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 776e7653c..e70e976fa 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -172,6 +172,7 @@ #include #include #include "utilities.h" +#include // OCCT includes #include @@ -1392,7 +1393,8 @@ namespace if (selected.Extent()) { Handle(SALOME_InteractiveObject) anIObject = selected.First(); _PTR(Study) aStudy = SMESH::getStudy(); - _PTR(SObject) aSObj = aStudy->FindObjectID(anIObject->getEntry()); + std::string aEntry = anIObject->getEntry(); + _PTR(SObject) aSObj = aStudy->FindObjectID(aEntry); if (aSObj) { std::string aName = aSObj->GetName(); QMessageBox::StandardButton aRes = SUIT_MessageBox::warning(SMESHGUI::desktop(), @@ -1400,9 +1402,26 @@ namespace QObject::tr("MSG_BREAK_SHAPER_LINK").arg(aName.c_str()), SUIT_MessageBox::Yes | SUIT_MessageBox::No, SUIT_MessageBox::No); if (aRes == SUIT_MessageBox::Yes) { - GEOM::GEOM_Object_var aObject = SMESH::SObjectToInterface(aSObj); - if (!aObject->_is_nil()) - aObject->BreakLinks(); + SUIT_DataOwnerPtrList aList; + aSel->selected(aList, "ObjectBrowser", true); + SUIT_DataOwner* aOwn = aList.first(); + LightApp_DataOwner* sowner = dynamic_cast(aOwn); + QString aREntry = sowner->entry(); + + static GEOM::GEOM_Gen_var geomGen; + if (CORBA::is_nil(geomGen)) { + SalomeApp_Application* app = dynamic_cast + (SUIT_Session::session()->activeApplication()); + if (app) { + SALOME_LifeCycleCORBA* ls = new SALOME_LifeCycleCORBA(app->namingService()); + Engines::EngineComponent_var comp = + ls->FindOrLoad_Component("FactoryServer", "SHAPERSTUDY"); + geomGen = GEOM::GEOM_Gen::_narrow(comp); + } + } + if (!CORBA::is_nil(geomGen)) { + geomGen->BreakLink(aREntry.toStdString().c_str()); + } } } } diff --git a/src/SMESHGUI/SMESHGUI_Selection.cxx b/src/SMESHGUI/SMESHGUI_Selection.cxx index c79334554..ab4a1ca8e 100644 --- a/src/SMESHGUI/SMESHGUI_Selection.cxx +++ b/src/SMESHGUI/SMESHGUI_Selection.cxx @@ -43,6 +43,8 @@ #include #include #include +#include +#include // IDL includes #include @@ -77,6 +79,8 @@ void SMESHGUI_Selection::init( const QString& client, LightApp_SelectionMgr* mgr if( mgr ) { + myOwners.clear(); + mgr->selected(myOwners, client); for( int i=0, n=count(); i= 0 && ind < myTypes.count()) { - _PTR(SObject) aSObject = SMESH::getStudy()->FindObjectID( entry( ind ).toUtf8().data() ); - GEOM::GEOM_Object_var aObject = SMESH::SObjectToInterface(aSObject); - if (!aObject->_is_nil()) - return aObject->IsParametrical(); + if (isReference(ind)) { + SUIT_DataOwner* aOwn = myOwners.at(ind); + LightApp_DataOwner* sowner = dynamic_cast(aOwn); + QString aEntry = sowner->entry(); + _PTR(SObject) aSObject = SMESH::getStudy()->FindObjectID(aEntry.toStdString()); + _PTR(SObject) aFatherObj = aSObject->GetFather(); + _PTR(SComponent) aComponent = aFatherObj->GetFatherComponent(); + if (aComponent->ComponentDataType() == "SMESH") { + QString aObjEntry = entry(ind); + _PTR(SObject) aGeomSObject = SMESH::getStudy()->FindObjectID(aObjEntry.toStdString()); + GEOM::GEOM_Object_var aObject = SMESH::SObjectToInterface(aGeomSObject); + if (!aObject->_is_nil()) + return aObject->IsParametrical(); + } + } } return false; } diff --git a/src/SMESHGUI/SMESHGUI_Selection.h b/src/SMESHGUI/SMESHGUI_Selection.h index 6ca2c0cb1..3ec17a9a8 100644 --- a/src/SMESHGUI/SMESHGUI_Selection.h +++ b/src/SMESHGUI/SMESHGUI_Selection.h @@ -32,6 +32,7 @@ // SALOME GUI includes #include +#include // SALOME KERNEL includes #include @@ -96,6 +97,7 @@ private: QStringList myTypes; QStringList myControls; QList myActors; + SUIT_DataOwnerPtrList myOwners; }; #endif // SMESHGUI_SELECTION_H From 0ed56e5073cbee776a668e76822f46a5fab1b8cd Mon Sep 17 00:00:00 2001 From: vsv Date: Mon, 23 Dec 2019 18:52:42 +0300 Subject: [PATCH 08/60] Translation on French --- src/SMESHGUI/SMESH_msg_fr.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/SMESHGUI/SMESH_msg_fr.ts b/src/SMESHGUI/SMESH_msg_fr.ts index 562418f91..23f5a8d89 100644 --- a/src/SMESHGUI/SMESH_msg_fr.ts +++ b/src/SMESHGUI/SMESH_msg_fr.ts @@ -4471,6 +4471,21 @@ Il ne peut pas être supprimé. STB_SORT_CHILD_ITEMS Trier les items enfants + + MEN_BREAK_SHAPER_LINK + Rompre le lien + + + STB_BREAK_SHAPER_LINK + Rupture du lien avec le modèle Shaper + + + MSG_BREAK_SHAPER_LINK + + Un lien avec le modèle Shaper pour l'objet %1 sera rompu. + Continuer? + + SMESH_ADVANCED Avancé From 68d2446f69246b775850f680a677a1bc04ee8459 Mon Sep 17 00:00:00 2001 From: vsv Date: Thu, 26 Dec 2019 14:55:44 +0300 Subject: [PATCH 09/60] Update object browser --- src/SMESHGUI/SMESHGUI.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index e70e976fa..1ae2f8db9 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -1421,6 +1421,7 @@ namespace } if (!CORBA::is_nil(geomGen)) { geomGen->BreakLink(aREntry.toStdString().c_str()); + SMESHGUI::GetSMESHGUI()->updateObjBrowser(); } } } From 26999fa04d737b9d3a2a46744e693954094cd64b Mon Sep 17 00:00:00 2001 From: vsv Date: Thu, 26 Dec 2019 16:55:21 +0300 Subject: [PATCH 10/60] Update views on the module activation --- src/SMESHGUI/SMESHGUI.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 1ae2f8db9..661a7b09e 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -4984,8 +4984,10 @@ bool SMESHGUI::activateModule( SUIT_Study* study ) if ( aDesk ) { QList wndList = aDesk->windows(); SUIT_ViewWindow* wnd; - foreach ( wnd, wndList ) - connectView( wnd ); + foreach(wnd, wndList) { + connectView(wnd); + wnd->update(); + } } Py_XDECREF(pluginsmanager); From 00b30e0d6c8319e0579d59a8cd1d3ad01b83a271 Mon Sep 17 00:00:00 2001 From: vsv Date: Fri, 27 Dec 2019 16:22:48 +0300 Subject: [PATCH 11/60] Replace shape in Mesh object --- idl/SMESH_Mesh.idl | 6 ++++++ src/SMESH/SMESH_Mesh.hxx | 3 +++ src/SMESH_I/SMESH_Mesh_i.cxx | 16 ++++++++++++++++ src/SMESH_I/SMESH_Mesh_i.hxx | 3 +++ 4 files changed, 28 insertions(+) diff --git a/idl/SMESH_Mesh.idl b/idl/SMESH_Mesh.idl index a18a7bd0c..b4ec14dc8 100644 --- a/idl/SMESH_Mesh.idl +++ b/idl/SMESH_Mesh.idl @@ -347,6 +347,12 @@ module SMESH GEOM::GEOM_Object GetShapeToMesh() raises (SALOME::SALOME_Exception); + /*! + * Replaces a shape in the mesh + */ + void ReplaceShape(in GEOM::GEOM_Object theNewGeom, in GEOM::GEOM_Object theOldGeom) + raises (SALOME::SALOME_Exception); + /*! * Return false if the mesh is not yet fully loaded from the study file */ diff --git a/src/SMESH/SMESH_Mesh.hxx b/src/SMESH/SMESH_Mesh.hxx index e20d578ce..5eadb13c2 100644 --- a/src/SMESH/SMESH_Mesh.hxx +++ b/src/SMESH/SMESH_Mesh.hxx @@ -86,6 +86,9 @@ class SMESH_EXPORT SMESH_Mesh * \brief Return true if there is a geometry to be meshed, not PseudoShape() */ bool HasShapeToMesh() const { return _isShapeToMesh; } + + void UndefShapeToMesh() { _isShapeToMesh = false; } + /*! * \brief Return diagonal size of bounding box of shape to mesh. */ diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index c2cf46593..1740de242 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -254,6 +254,22 @@ GEOM::GEOM_Object_ptr SMESH_Mesh_i::GetShapeToMesh() return aShapeObj._retn(); } +//================================================================================ +/*! +* \brief Replaces a shape in the mesh +*/ +//================================================================================ +void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom, GEOM::GEOM_Object_ptr theOldGeom) + throw (SALOME::SALOME_Exception) +{ + GEOM_Client* geomClient = _gen_i->GetShapeReader(); + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine(theOldGeom); + CORBA::String_var groupIOR = geomGen->GetStringFromIOR(theOldGeom); + geomClient->RemoveShapeFromBuffer(groupIOR.in()); + _impl->UndefShapeToMesh(); + SetShape(theNewGeom); +} + //================================================================================ /*! * \brief Return false if the mesh is not yet fully loaded from the study file diff --git a/src/SMESH_I/SMESH_Mesh_i.hxx b/src/SMESH_I/SMESH_Mesh_i.hxx index 27c6142b5..9c564e176 100644 --- a/src/SMESH_I/SMESH_Mesh_i.hxx +++ b/src/SMESH_I/SMESH_Mesh_i.hxx @@ -74,6 +74,9 @@ public: GEOM::GEOM_Object_ptr GetShapeToMesh() throw (SALOME::SALOME_Exception); + virtual void ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom, GEOM::GEOM_Object_ptr theOldGeom) + throw (SALOME::SALOME_Exception); + CORBA::Boolean IsLoaded() throw (SALOME::SALOME_Exception); From c2a62ff550708341eb9ad22cbdecf3e3b625c290 Mon Sep 17 00:00:00 2001 From: vsv Date: Fri, 27 Dec 2019 18:26:04 +0300 Subject: [PATCH 12/60] Improve ReplaceShape interface --- idl/SMESH_Mesh.idl | 2 +- src/SMESH_I/SMESH_Mesh_i.cxx | 10 ++++++---- src/SMESH_I/SMESH_Mesh_i.hxx | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/idl/SMESH_Mesh.idl b/idl/SMESH_Mesh.idl index b4ec14dc8..8cda08472 100644 --- a/idl/SMESH_Mesh.idl +++ b/idl/SMESH_Mesh.idl @@ -350,7 +350,7 @@ module SMESH /*! * Replaces a shape in the mesh */ - void ReplaceShape(in GEOM::GEOM_Object theNewGeom, in GEOM::GEOM_Object theOldGeom) + void ReplaceShape(in GEOM::GEOM_Object theNewGeom) raises (SALOME::SALOME_Exception); /*! diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 1740de242..0f7ce9646 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -259,13 +259,15 @@ GEOM::GEOM_Object_ptr SMESH_Mesh_i::GetShapeToMesh() * \brief Replaces a shape in the mesh */ //================================================================================ -void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom, GEOM::GEOM_Object_ptr theOldGeom) +void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) throw (SALOME::SALOME_Exception) { + TopoDS_Shape S = _impl->GetMeshDS()->ShapeToMesh(); GEOM_Client* geomClient = _gen_i->GetShapeReader(); - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine(theOldGeom); - CORBA::String_var groupIOR = geomGen->GetStringFromIOR(theOldGeom); - geomClient->RemoveShapeFromBuffer(groupIOR.in()); + TCollection_AsciiString aIOR; + if (geomClient->Find(S, aIOR)) { + geomClient->RemoveShapeFromBuffer(aIOR); + } _impl->UndefShapeToMesh(); SetShape(theNewGeom); } diff --git a/src/SMESH_I/SMESH_Mesh_i.hxx b/src/SMESH_I/SMESH_Mesh_i.hxx index 9c564e176..535931d93 100644 --- a/src/SMESH_I/SMESH_Mesh_i.hxx +++ b/src/SMESH_I/SMESH_Mesh_i.hxx @@ -74,7 +74,7 @@ public: GEOM::GEOM_Object_ptr GetShapeToMesh() throw (SALOME::SALOME_Exception); - virtual void ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom, GEOM::GEOM_Object_ptr theOldGeom) + virtual void ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) throw (SALOME::SALOME_Exception); CORBA::Boolean IsLoaded() From 2b3c553f4facafcea7cb1b2f23ab4b91b64771df Mon Sep 17 00:00:00 2001 From: mpv Date: Mon, 30 Dec 2019 17:13:14 +0300 Subject: [PATCH 13/60] Implementation of Groups support by the SHAPER-STUDY module --- src/PluginUtils/GeomSelectionTools.cxx | 2 +- src/SMESHGUI/SMESHGUI_GroupDlg.cxx | 6 +++--- src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx | 2 +- src/SMESHGUI/SMESHGUI_MeshOp.cxx | 6 +++--- src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx | 2 +- src/SMESH_I/SMESH_Gen_i.cxx | 2 +- src/SMESH_I/SMESH_Mesh_i.cxx | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PluginUtils/GeomSelectionTools.cxx b/src/PluginUtils/GeomSelectionTools.cxx index 936afcb31..ddf87e5ea 100644 --- a/src/PluginUtils/GeomSelectionTools.cxx +++ b/src/PluginUtils/GeomSelectionTools.cxx @@ -213,7 +213,7 @@ TopAbs_ShapeEnum GeomSelectionTools::entryToShapeType(std::string entry){ // if the Geom Object is a group if (aShape->GetType() == GEOM_GROUP){ // MESSAGE("It's a group"); - GEOM::GEOM_IGroupOperations_wrap aGroupOp = + GEOM::GEOM_IGroupOperations_ptr aGroupOp = _geomEngine->GetIGroupOperations(); ShapeType= (TopAbs_ShapeEnum)aGroupOp->GetType(aShape); } diff --git a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx index eb3ad132b..ad5bddd51 100644 --- a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx @@ -1064,7 +1064,7 @@ bool SMESHGUI_GroupDlg::onApply() if (geomGen->_is_nil()) return false; - GEOM::GEOM_IGroupOperations_wrap op = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_ptr op = geomGen->GetIGroupOperations(); if (op->_is_nil()) return false; @@ -1411,7 +1411,7 @@ void SMESHGUI_GroupDlg::onObjectSelectionChanged() // The main shape of the group GEOM::GEOM_Object_var aGroupMainShape; if (aGeomGroup->GetType() == 37) { - GEOM::GEOM_IGroupOperations_wrap anOp = + GEOM::GEOM_IGroupOperations_ptr anOp = SMESH::GetGEOMGen()->GetIGroupOperations(); aGroupMainShape = anOp->GetMainShape(aGeomGroup); // aGroupMainShape is an existing servant => GEOM_Object_var not GEOM_Object_wrap @@ -2001,7 +2001,7 @@ void SMESHGUI_GroupDlg::onAdd() onListSelectionChanged(); } else if (myCurrentLineEdit == myGeomGroupLine && myGeomObjects->length() == 1) { - GEOM::GEOM_IGroupOperations_wrap aGroupOp = + GEOM::GEOM_IGroupOperations_ptr aGroupOp = SMESH::GetGEOMGen()->GetIGroupOperations(); SMESH::ElementType aGroupType = SMESH::ALL; diff --git a/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx b/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx index 6a0f17d33..be3d90ff4 100644 --- a/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx @@ -224,7 +224,7 @@ static SMESH::ElementType elementType(GEOM::GEOM_Object_var geom) SMESH::GetGEOMGen()->GetIShapesOperations(); if ( geom->GetType() == 37 ) { // geom group - GEOM::GEOM_IGroupOperations_wrap aGroupOp = + GEOM::GEOM_IGroupOperations_ptr aGroupOp = SMESH::GetGEOMGen()->GetIGroupOperations(); if ( !aGroupOp->_is_nil() ) { // mainShape is an existing servant => GEOM_Object_var not GEOM_Object_wrap diff --git a/src/SMESHGUI/SMESHGUI_MeshOp.cxx b/src/SMESHGUI/SMESHGUI_MeshOp.cxx index 443a8a86f..9e30fe25a 100644 --- a/src/SMESHGUI/SMESHGUI_MeshOp.cxx +++ b/src/SMESHGUI/SMESHGUI_MeshOp.cxx @@ -338,10 +338,10 @@ bool SMESHGUI_MeshOp::isSubshapeOk() const myDlg->selectedObject(SMESHGUI_MeshDlg::Geom, aGEOMs); if (aGEOMs.count() > 0) { - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); + GEOM::GEOM_Gen_var geomGen = mainGeom->GetGen(); if (geomGen->_is_nil()) return false; - GEOM::GEOM_IGroupOperations_wrap op = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_ptr op = geomGen->GetIGroupOperations(); if (op->_is_nil()) return false; // check all selected shapes @@ -1856,7 +1856,7 @@ bool SMESHGUI_MeshOp::createSubMesh( QString& theMess, QStringList& theEntryList // create a GEOM group GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); if (!geomGen->_is_nil()) { - GEOM::GEOM_IGroupOperations_wrap op = + GEOM::GEOM_IGroupOperations_ptr op = geomGen->GetIGroupOperations(); if (!op->_is_nil()) { // check and add all selected GEOM objects: they must be diff --git a/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx b/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx index 6647f4cdc..4ce31d65b 100644 --- a/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx @@ -362,7 +362,7 @@ void SMESHGUI_ShapeByMeshOp::commitOperation() } else if (aNumberOfGO > 1) { - GEOM::GEOM_IGroupOperations_wrap aGroupOp = + GEOM::GEOM_IGroupOperations_ptr aGroupOp = geomGen->GetIGroupOperations(); if(aGroupOp->_is_nil()) return; diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index 3003a0a52..2255f5823 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -3190,7 +3190,7 @@ namespace // utils for CopyMeshWithGeom() { int groupType = getShapeType( myNewMesh_i, newIndices[0] ); - GEOM::GEOM_IGroupOperations_wrap grOp = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_ptr grOp = geomGen->GetIGroupOperations(); newShape = grOp->CreateGroup( mainShapeNew, groupType ); GEOM::ListOfLong_var newIndicesList = new GEOM::ListOfLong(); diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 0f7ce9646..cb57e77f4 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -1999,7 +1999,7 @@ void SMESH_Mesh_i::addGeomGroupData(GEOM::GEOM_Object_ptr theGeomObj, return; // group indices GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( theGeomObj ); - GEOM::GEOM_IGroupOperations_wrap groupOp = + GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); GEOM::ListOfLong_var ids = groupOp->GetObjects( theGeomObj ); @@ -2058,7 +2058,7 @@ TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData) // get indices of group items set curIndices; GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); - GEOM::GEOM_IGroupOperations_wrap groupOp = + GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); for ( CORBA::ULong i = 0; i < ids->length(); ++i ) From 07ac4da8b237a9f7b8b87aa47e2fe0a8c2c8dbaf Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 15 Jan 2020 16:12:51 +0300 Subject: [PATCH 14/60] BUG: SMESH::GetGEOMGen() returns GEOM_Engine instead of SHAPERSTUDY 1) GetGEOMGen() -> GetGEOMGen( GEOM::GEOM_Object_ptr go ) 2) some formatting (whitespace changes) --- src/SMESHGUI/SMESHGUI_ComputeDlg.cxx | 2 +- src/SMESHGUI/SMESHGUI_FieldSelectorWdg.cxx | 6 ++-- src/SMESHGUI/SMESHGUI_GEOMGenUtils.cxx | 26 +++++++------- src/SMESHGUI/SMESHGUI_GEOMGenUtils.h | 4 +-- src/SMESHGUI/SMESHGUI_GroupDlg.cxx | 41 +++++++++++++--------- src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx | 13 +++---- src/SMESHGUI/SMESHGUI_MeshOp.cxx | 10 +++--- src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx | 21 +++++------ 8 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx b/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx index 10e4b196f..14e05427d 100644 --- a/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx @@ -1251,8 +1251,8 @@ void SMESHGUI_BaseComputeOp::stopOperation() void SMESHGUI_BaseComputeOp::onPublishShape() { - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); GEOM::GEOM_Object_var meshShape = myMesh->GetShapeToMesh(); + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( meshShape ); QStringList entryList; QList rows; diff --git a/src/SMESHGUI/SMESHGUI_FieldSelectorWdg.cxx b/src/SMESHGUI/SMESHGUI_FieldSelectorWdg.cxx index a9d6ad891..e7f05170e 100644 --- a/src/SMESHGUI/SMESHGUI_FieldSelectorWdg.cxx +++ b/src/SMESHGUI/SMESHGUI_FieldSelectorWdg.cxx @@ -91,9 +91,6 @@ GetAllFields(const QList< QPair< SMESH::SMESH_IDSource_var, QString > >& meshes, { myFields = & fields; myTree->clear(); - - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); - GEOM::GEOM_IFieldOperations_wrap fieldOp = geomGen->GetIFieldOperations(); for ( int iM = 0; iM < meshes.count(); ++iM ) { @@ -109,6 +106,9 @@ GetAllFields(const QList< QPair< SMESH::SMESH_IDSource_var, QString > >& meshes, QTreeWidgetItem* meshItem = createItem( myTree, meshes[iM].second, iM ); GEOM::GEOM_Object_var shape = mesh->GetShapeToMesh(); + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( shape ); + GEOM::GEOM_IFieldOperations_wrap fieldOp = geomGen->GetIFieldOperations(); + fields = fieldOp->GetFields( shape ); for ( size_t iF = 0; iF < fields->length(); ++iF ) { diff --git a/src/SMESHGUI/SMESHGUI_GEOMGenUtils.cxx b/src/SMESHGUI/SMESHGUI_GEOMGenUtils.cxx index 05a912582..8be766f2f 100644 --- a/src/SMESHGUI/SMESHGUI_GEOMGenUtils.cxx +++ b/src/SMESHGUI/SMESHGUI_GEOMGenUtils.cxx @@ -44,16 +44,12 @@ namespace SMESH { - GEOM::GEOM_Gen_var GetGEOMGen() + GEOM::GEOM_Gen_var GetGEOMGen( GEOM::GEOM_Object_ptr go ) { - static GEOM::GEOM_Gen_var aGEOMGen; - - if(CORBA::is_nil(aGEOMGen)) { - if ( GeometryGUI::GetGeomGen()->_is_nil() ) - GeometryGUI::InitGeomGen(); - aGEOMGen = GeometryGUI::GetGeomGen(); - } - return aGEOMGen; + GEOM::GEOM_Gen_ptr gen; + if ( !CORBA::is_nil( go )) + gen = go->GetGen(); + return gen; } GEOM::GEOM_Object_var GetShapeOnMeshOrSubMesh(_PTR(SObject) theMeshOrSubmesh, @@ -155,14 +151,18 @@ namespace SMESH GEOM::GEOM_Object_ptr GetSubShape (GEOM::GEOM_Object_ptr theMainShape, long theID) { - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); + if ( CORBA::is_nil( theMainShape )) + return GEOM::GEOM_Object::_nil(); + + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( theMainShape ); if (geomGen->_is_nil()) return GEOM::GEOM_Object::_nil(); - GEOM::GEOM_IShapesOperations_wrap aShapesOp = - geomGen->GetIShapesOperations(); + + GEOM::GEOM_IShapesOperations_wrap aShapesOp = geomGen->GetIShapesOperations(); if (aShapesOp->_is_nil()) return GEOM::GEOM_Object::_nil(); - GEOM::GEOM_Object_wrap subShape = aShapesOp->GetSubShape (theMainShape,theID); + + GEOM::GEOM_Object_wrap subShape = aShapesOp->GetSubShape( theMainShape, theID ); return subShape._retn(); } diff --git a/src/SMESHGUI/SMESHGUI_GEOMGenUtils.h b/src/SMESHGUI/SMESHGUI_GEOMGenUtils.h index 08b643d51..8afed3981 100644 --- a/src/SMESHGUI/SMESHGUI_GEOMGenUtils.h +++ b/src/SMESHGUI/SMESHGUI_GEOMGenUtils.h @@ -45,7 +45,7 @@ class QString; namespace SMESH { - SMESHGUI_EXPORT GEOM::GEOM_Gen_var GetGEOMGen(); + SMESHGUI_EXPORT GEOM::GEOM_Gen_var GetGEOMGen( GEOM::GEOM_Object_ptr go ); SMESHGUI_EXPORT GEOM::GEOM_Object_var GetShapeOnMeshOrSubMesh( _PTR(SObject), bool* isMesh=0 ); @@ -53,7 +53,7 @@ namespace SMESH SMESHGUI_EXPORT GEOM::GEOM_Object_var GetGeom( Handle(SALOME_InteractiveObject) io ); - SMESHGUI_EXPORT char* GetGeomName( _PTR(SObject) smeshSO ); + SMESHGUI_EXPORT char* GetGeomName( _PTR(SObject) smeshSO ); SMESHGUI_EXPORT GEOM::GEOM_Object_ptr GetSubShape( GEOM::GEOM_Object_ptr, long ); diff --git a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx index ad5bddd51..62666c97f 100644 --- a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx @@ -1055,12 +1055,11 @@ bool SMESHGUI_GroupDlg::onApply() } else { SMESH::SMESH_Gen_var aSMESHGen = SMESHGUI::GetSMESHGen(); - if ( aSMESHGen->_is_nil() ) + if ( aSMESHGen->_is_nil() || myGeomObjects->length() == 0 ) return false; // create a geometry group - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); - + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( myGeomObjects[0] ); if (geomGen->_is_nil()) return false; @@ -1071,11 +1070,12 @@ bool SMESHGUI_GroupDlg::onApply() // check and add all selected GEOM objects: they must be // a sub-shapes of the main GEOM and must be of one type TopAbs_ShapeEnum aGroupType = TopAbs_SHAPE; - for ( int i =0; i < (int)myGeomObjects->length(); i++) { + for ( CORBA::ULong i =0; i < myGeomObjects->length(); i++) + { TopAbs_ShapeEnum aSubShapeType = (TopAbs_ShapeEnum)myGeomObjects[i]->GetShapeType(); - if (i == 0) + if ( i == 0 ) aGroupType = aSubShapeType; - else if (aSubShapeType != aGroupType) { + else if ( aSubShapeType != aGroupType ) { aGroupType = TopAbs_SHAPE; break; } @@ -1106,7 +1106,7 @@ bool SMESHGUI_GroupDlg::onApply() resultGroup = SMESH::SMESH_GroupBase::_narrow( myGroupOnGeom ); isCreation = false; - } + } anIsOk = true; } if (myGrpTypeId == 2) // group on filter @@ -1410,13 +1410,15 @@ void SMESHGUI_GroupDlg::onObjectSelectionChanged() // The main shape of the group GEOM::GEOM_Object_var aGroupMainShape; - if (aGeomGroup->GetType() == 37) { + if (aGeomGroup->GetType() == 37) + { GEOM::GEOM_IGroupOperations_ptr anOp = - SMESH::GetGEOMGen()->GetIGroupOperations(); - aGroupMainShape = anOp->GetMainShape(aGeomGroup); + SMESH::GetGEOMGen( aGeomGroup )->GetIGroupOperations(); + aGroupMainShape = anOp->GetMainShape( aGeomGroup ); // aGroupMainShape is an existing servant => GEOM_Object_var not GEOM_Object_wrap } - else { + else + { aGroupMainShape = aGeomGroup; aGroupMainShape->Register(); } @@ -1867,7 +1869,8 @@ void SMESHGUI_GroupDlg::onAdd() QListWidgetItem* anItem = 0; QList listItemsToSel; - if (myCurrentLineEdit == 0) { + if ( myCurrentLineEdit == 0 ) + { //if (aNbSel != 1) { myIsBusy = false; return; } QString aListStr = ""; int aNbItems = 0; @@ -1910,7 +1913,9 @@ void SMESHGUI_GroupDlg::onAdd() onListSelectionChanged(); listItemsToSel.clear(); } - } else if (myCurrentLineEdit == mySubMeshLine) { + } + else if ( myCurrentLineEdit == mySubMeshLine ) + { //SALOME_ListIteratorOfListIO anIt (mySelectionMgr->StoredIObjects()); SALOME_ListIO aList; @@ -1958,7 +1963,9 @@ void SMESHGUI_GroupDlg::onAdd() myIsBusy = false; onListSelectionChanged(); - } else if (myCurrentLineEdit == myGroupLine) { + } + else if ( myCurrentLineEdit == myGroupLine ) + { //SALOME_ListIteratorOfListIO anIt (mySelectionMgr->StoredIObjects()); SALOME_ListIO aList; mySelectionMgr->selectedObjects( aList ); @@ -2000,9 +2007,11 @@ void SMESHGUI_GroupDlg::onAdd() myIsBusy = false; onListSelectionChanged(); - } else if (myCurrentLineEdit == myGeomGroupLine && myGeomObjects->length() == 1) { + } + else if ( myCurrentLineEdit == myGeomGroupLine && myGeomObjects->length() == 1 ) + { GEOM::GEOM_IGroupOperations_ptr aGroupOp = - SMESH::GetGEOMGen()->GetIGroupOperations(); + SMESH::GetGEOMGen( myGeomObjects[0] )->GetIGroupOperations(); SMESH::ElementType aGroupType = SMESH::ALL; switch(aGroupOp->GetType(myGeomObjects[0])) { diff --git a/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx b/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx index be3d90ff4..f7be40487 100644 --- a/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx @@ -220,12 +220,12 @@ static SMESH::ElementType elementType(GEOM::GEOM_Object_var geom) case GEOM::COMPOUND: break; default: return SMESH::ALL; } - GEOM::GEOM_IShapesOperations_wrap aShapeOp = - SMESH::GetGEOMGen()->GetIShapesOperations(); + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( geom ); + GEOM::GEOM_IShapesOperations_wrap aShapeOp = geomGen->GetIShapesOperations(); - if ( geom->GetType() == 37 ) { // geom group - GEOM::GEOM_IGroupOperations_ptr aGroupOp = - SMESH::GetGEOMGen()->GetIGroupOperations(); + if ( geom->GetType() == 37 ) // geom group + { + GEOM::GEOM_IGroupOperations_ptr aGroupOp = geomGen->GetIGroupOperations(); if ( !aGroupOp->_is_nil() ) { // mainShape is an existing servant => GEOM_Object_var not GEOM_Object_wrap GEOM::GEOM_Object_var mainShape = aGroupOp->GetMainShape( geom ); @@ -236,7 +236,8 @@ static SMESH::ElementType elementType(GEOM::GEOM_Object_var geom) } } } - else if ( !aShapeOp->_is_nil() ) { // just a compoud shape + else if ( !aShapeOp->_is_nil() ) // just a compoud shape + { GEOM::ListOfLong_var ids = aShapeOp->SubShapeAllIDs( geom, GEOM::SHAPE, false ); if ( ids->length() ) { GEOM::GEOM_Object_wrap member = aShapeOp->GetSubShape( geom, ids[0] ); diff --git a/src/SMESHGUI/SMESHGUI_MeshOp.cxx b/src/SMESHGUI/SMESHGUI_MeshOp.cxx index 9e30fe25a..36b530a39 100644 --- a/src/SMESHGUI/SMESHGUI_MeshOp.cxx +++ b/src/SMESHGUI/SMESHGUI_MeshOp.cxx @@ -1854,11 +1854,11 @@ bool SMESHGUI_MeshOp::createSubMesh( QString& theMess, QStringList& theEntryList else if (aGEOMs.count() > 1) { // create a GEOM group - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); - if (!geomGen->_is_nil()) { - GEOM::GEOM_IGroupOperations_ptr op = - geomGen->GetIGroupOperations(); - if (!op->_is_nil()) { + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( mainGeom ); + if ( !geomGen->_is_nil() ) { + GEOM::GEOM_IGroupOperations_ptr op = geomGen->GetIGroupOperations(); + if ( !op->_is_nil() ) + { // check and add all selected GEOM objects: they must be // a sub-shapes of the main GEOM and must be of one type int iSubSh = 0; diff --git a/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx b/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx index 4ce31d65b..19397b871 100644 --- a/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx @@ -318,14 +318,14 @@ void SMESHGUI_ShapeByMeshOp::commitOperation() } else { - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); + GEOM::GEOM_Object_var aMeshShape = myMesh->GetShapeToMesh(); - if (geomGen->_is_nil()) + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( aMeshShape ); + if ( geomGen->_is_nil() ) return; - GEOM::GEOM_IShapesOperations_wrap aShapesOp = - geomGen->GetIShapesOperations(); - if (aShapesOp->_is_nil() ) + GEOM::GEOM_IShapesOperations_wrap aShapesOp = geomGen->GetIShapesOperations(); + if ( aShapesOp->_is_nil() ) return; TopAbs_ShapeEnum aGroupType = TopAbs_SHAPE; @@ -333,8 +333,6 @@ void SMESHGUI_ShapeByMeshOp::commitOperation() std::map aGeomObjectsMap; GEOM::GEOM_Object_wrap aGeomObject; - GEOM::GEOM_Object_var aMeshShape = myMesh->GetShapeToMesh(); - for ( int i = 0; i < aListId.count(); i++ ) { aGeomObject = // received object need UnRegister()! @@ -362,9 +360,8 @@ void SMESHGUI_ShapeByMeshOp::commitOperation() } else if (aNumberOfGO > 1) { - GEOM::GEOM_IGroupOperations_ptr aGroupOp = - geomGen->GetIGroupOperations(); - if(aGroupOp->_is_nil()) + GEOM::GEOM_IGroupOperations_ptr aGroupOp = geomGen->GetIGroupOperations(); + if ( aGroupOp->_is_nil() ) return; GEOM::ListOfGO_var aGeomObjects = new GEOM::ListOfGO(); @@ -379,7 +376,7 @@ void SMESHGUI_ShapeByMeshOp::commitOperation() aGeomObject = aGroupOp->CreateGroup(aMeshShape, aGroupType); aGroupOp->UnionList(aGeomObject, aGeomObjects); - if (!aGroupOp->IsDone()) + if ( !aGroupOp->IsDone() ) return; } @@ -419,7 +416,7 @@ void SMESHGUI_ShapeByMeshOp::onSelectionDone() try { SALOME_ListIO aList; selectionMgr()->selectedObjects(aList); - if (!myIsMultipleAllowed && aList.Extent() != 1) + if ( aList.IsEmpty() || ( !myIsMultipleAllowed && aList.Extent() != 1) ) return; SMESH::SMESH_Mesh_var aMesh = SMESH::GetMeshByIO(aList.First()); From 8df5094ecdbb94c1f048e09082f81a7c672d77cb Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 15 Jan 2020 16:13:38 +0300 Subject: [PATCH 15/60] Fix SMESH_Mesh_i::ReplaceShape() --- src/SMESH_I/SMESH_Mesh_i.cxx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index cb57e77f4..965696f48 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -262,14 +262,16 @@ GEOM::GEOM_Object_ptr SMESH_Mesh_i::GetShapeToMesh() void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) throw (SALOME::SALOME_Exception) { - TopoDS_Shape S = _impl->GetMeshDS()->ShapeToMesh(); + TopoDS_Shape S = _impl->GetShapeToMesh(); GEOM_Client* geomClient = _gen_i->GetShapeReader(); TCollection_AsciiString aIOR; if (geomClient->Find(S, aIOR)) { geomClient->RemoveShapeFromBuffer(aIOR); } - _impl->UndefShapeToMesh(); - SetShape(theNewGeom); + + // re-assign global hypotheses to the new shape + _mainShapeTick = theNewGeom->GetTick() + 1; + CheckGeomModif(); } //================================================================================ From 174c7bbfbf1e7ccbdaa09ac065ea8261ea7ff099 Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 15 Jan 2020 17:01:31 +0300 Subject: [PATCH 16/60] BUG: SMESH::GetGEOMGen() returns GEOM_Engine instead of SHAPERSTUDY another place of SMESH::GetGEOMGen() usage --- src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx index dc1f6b19e..f52914bd4 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx @@ -422,7 +422,7 @@ bool StdMeshersGUI_StdHypothesisCreator::checkParams( QString& msg ) const // then the FACE must have only one VERTEX GEOM::GEOM_Object_var face = w->GetObject< GEOM::GEOM_Object >(); - GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen(); + GEOM::GEOM_Gen_var geomGen = SMESH::GetGEOMGen( face ); _PTR(Study) aStudy = SMESH::getStudy(); GEOM::GEOM_IShapesOperations_wrap shapeOp; if ( !geomGen->_is_nil() && aStudy ) From af067a707ef5f310a5f0878fa9c535655c332ada Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 16 Jan 2020 16:41:56 +0300 Subject: [PATCH 17/60] Update mesh according to geometry modif. Care of actors of removed objects --- src/SMESHGUI/SMESHGUI.cxx | 7 +- src/SMESHGUI/SMESHGUI_VTKUtils.cxx | 57 +++++++++++++++ src/SMESHGUI/SMESHGUI_VTKUtils.h | 3 + src/SMESH_I/SMESH_Mesh_i.cxx | 109 +++++++++++++++++++---------- 4 files changed, 137 insertions(+), 39 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 661a7b09e..6d875cfd2 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -4984,8 +4984,13 @@ bool SMESHGUI::activateModule( SUIT_Study* study ) if ( aDesk ) { QList wndList = aDesk->windows(); SUIT_ViewWindow* wnd; - foreach(wnd, wndList) { + foreach(wnd, wndList) + { connectView(wnd); + + // remove actors whose objects are removed in GetSMESHGen()->UpdateStudy() + SMESH::UpdateActorsAfterUpdateStudy(wnd); + wnd->update(); } } diff --git a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx index d6251e7d6..bc6714980 100644 --- a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx +++ b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx @@ -232,6 +232,63 @@ namespace SMESH } } + //================================================================================ + /*! + * \brief Remove/update actors while module activation + * \param [in] wnd - window + * + * At module activation, groups and sub-meshes can be removed on engine side due + * to modification of meshed geometry, while their actors can remain. + * Here we remove/update SMESH_Actor's of changed objects. State (emptiness) of objects + * is defined by their icons in the Object Browser + */ + //================================================================================ + + void UpdateActorsAfterUpdateStudy( SUIT_ViewWindow* theWindow ) + { + const char* emptyIcon = "ICON_SMESH_TREE_MESH_WARN"; + _PTR(Study) aStudy = SMESH::getStudy(); + + if ( SVTK_ViewWindow* aViewWindow = GetVtkViewWindow( theWindow )) + { + vtkRenderer *aRenderer = aViewWindow->getRenderer(); + VTK::ActorCollectionCopy aCopy(aRenderer->GetActors()); + vtkActorCollection *aCollection = aCopy.GetActors(); + aCollection->InitTraversal(); + while ( vtkActor *actor = aCollection->GetNextActor() ) { + if ( SMESH_Actor *smeshActor = dynamic_cast( actor )) + { + if ( !smeshActor->hasIO() ) + continue; + Handle(SALOME_InteractiveObject) io = smeshActor->getIO(); + if ( !io->hasEntry() ) + continue; + _PTR(SObject) so = aStudy->FindObjectID( io->getEntry() ); + if ( !so ) + continue; // seems impossible + + CORBA::Object_var obj = SMESH::SObjectToObject( so ); + if ( CORBA::is_nil( obj )) // removed object + { + RemoveActor( theWindow, smeshActor ); + continue; + } + + bool toShow = smeshActor->GetVisibility(); + _PTR(GenericAttribute) attr; + if ( toShow && so->FindAttribute( attr, "AttributePixMap" )) // check emptiness + { + _PTR(AttributePixMap) pixMap = attr; + toShow = ( pixMap->GetPixMap() != emptyIcon ); + } + smeshActor->Update(); + UpdateView( theWindow, toShow ? eDisplay : eErase, io->getEntry() ); + } + } + } + return; + } + //================================================================================ /*! * \brief Notify the user on problems during visualization diff --git a/src/SMESHGUI/SMESHGUI_VTKUtils.h b/src/SMESHGUI/SMESHGUI_VTKUtils.h index 52919afad..ec6057493 100644 --- a/src/SMESHGUI/SMESHGUI_VTKUtils.h +++ b/src/SMESHGUI/SMESHGUI_VTKUtils.h @@ -216,6 +216,9 @@ SMESHGUI_EXPORT double& theDist ); SMESHGUI_EXPORT void RemoveVisualObjectWithActors( const char* theEntry, bool fromAllViews = false ); + + SMESHGUI_EXPORT + void UpdateActorsAfterUpdateStudy( SUIT_ViewWindow* wnd ); }; #endif // SMESHGUI_VTKUTILS_H diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 965696f48..57daad4a6 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -270,7 +271,7 @@ void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) } // re-assign global hypotheses to the new shape - _mainShapeTick = theNewGeom->GetTick() + 1; + _mainShapeTick = -1; CheckGeomModif(); } @@ -2059,10 +2060,9 @@ TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData) // get indices of group items set curIndices; - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); - GEOM::GEOM_IGroupOperations_ptr groupOp = - geomGen->GetIGroupOperations(); - GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); + GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); + GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); for ( CORBA::ULong i = 0; i < ids->length(); ++i ) curIndices.insert( ids[i] ); @@ -2182,6 +2182,8 @@ void SMESH_Mesh_i::CheckGeomModif() SMESH::SMESH_Mesh_var me = _this(); GEOM::GEOM_Object_var mainGO = GetShapeToMesh(); + TPythonDump dumpNothing; // prevent any dump + //bool removedFromClient = false; if ( mainGO->_is_nil() ) // GEOM_Client cleared or geometry removed? (IPAL52735, PAL23636) @@ -2243,7 +2245,7 @@ void SMESH_Mesh_i::CheckGeomModif() return; } - // Update after shape transformation like Translate + // Update after shape modification GEOM_Client* geomClient = _gen_i->GetShapeReader(); if ( !geomClient ) return; @@ -2253,21 +2255,17 @@ void SMESH_Mesh_i::CheckGeomModif() CORBA::String_var ior = geomGen->GetStringFromIOR( mainGO ); geomClient->RemoveShapeFromBuffer( ior.in() ); - // Update data taking into account that + // Update data taking into account that if topology doesn't change // all sub-shapes change but IDs of sub-shapes remain (except for geom groups) + if ( _preMeshInfo ) + _preMeshInfo->ForgetAllData(); + _impl->Clear(); TopoDS_Shape newShape = _gen_i->GeomObjectToShape( mainGO ); if ( newShape.IsNull() ) return; - // for the SHAPER-STUDY: the geometry may be updated, so, add a warning icon - if (_mainShapeTick != mainGO->GetTick()) { - SALOMEDS::SObject_wrap meshSO = _gen_i->ObjectToSObject( me ); - if ( !meshSO->_is_nil()) - _gen_i->SetPixMap(meshSO, "ICON_SMESH_TREE_MESH_WARN"); - } - _mainShapeTick = mainGO->GetTick(); SMESHDS_Mesh * meshDS = _impl->GetMeshDS(); @@ -2276,6 +2274,7 @@ void SMESH_Mesh_i::CheckGeomModif() std::vector< TGroupOnGeomData > groupsData; const std::set& groups = meshDS->GetGroups(); groupsData.reserve( groups.size() ); + TopTools_DataMapOfShapeShape old2newShapeMap; std::set::const_iterator g = groups.begin(); for ( ; g != groups.end(); ++g ) { @@ -2297,6 +2296,11 @@ void SMESH_Mesh_i::CheckGeomModif() CORBA::String_var ior = geomGen->GetStringFromIOR( geom ); geomClient->RemoveShapeFromBuffer( ior.in() ); groupsData.back()._shape = _gen_i->GeomObjectToShape( geom ); + old2newShapeMap.Bind( group->GetShape(), groupsData.back()._shape ); + } + else if ( old2newShapeMap.IsBound( group->GetShape() )) + { + groupsData.back()._shape = old2newShapeMap( group->GetShape() ); } } } @@ -2310,12 +2314,33 @@ void SMESH_Mesh_i::CheckGeomModif() ids2Hyps.push_back( make_pair( meshDS->ShapeToIndex( s ), hyps )); } - // change shape to mesh + // count shapes excluding compounds corresponding to geom groups int oldNbSubShapes = meshDS->MaxShapeIndex(); + for ( ; oldNbSubShapes > 0; --oldNbSubShapes ) + { + const TopoDS_Shape& s = meshDS->IndexToShape( oldNbSubShapes ); + if ( s.IsNull() || s.ShapeType() != TopAbs_COMPOUND ) + break; + } + + // check if shape topology changes - save shape type per shape ID + std::vector< TopAbs_ShapeEnum > shapeTypes( Max( oldNbSubShapes + 1, 1 )); + for ( int shapeID = oldNbSubShapes; shapeID > 0; --shapeID ) + shapeTypes[ shapeID ] = meshDS->IndexToShape( shapeID ).ShapeType(); + + // change shape to mesh _impl->ShapeToMesh( TopoDS_Shape() ); _impl->ShapeToMesh( newShape ); - // re-add shapes of geom groups + // check if shape topology changes - check new shape types + bool sameTopology = ( oldNbSubShapes == meshDS->MaxShapeIndex() ); + for ( int shapeID = oldNbSubShapes; shapeID > 0 && sameTopology; --shapeID ) + { + const TopoDS_Shape& s = meshDS->IndexToShape( shapeID ); + sameTopology = ( !s.IsNull() && s.ShapeType() == shapeTypes[ shapeID ]); + } + + // re-add shapes (compounds) of geom groups std::list::iterator data = _geomGroupData.begin(); for ( ; data != _geomGroupData.end(); ++data ) { @@ -2332,13 +2357,12 @@ void SMESH_Mesh_i::CheckGeomModif() _impl->GetSubMesh( newShape ); } } - if ( oldNbSubShapes != meshDS->MaxShapeIndex() ) - THROW_SALOME_CORBA_EXCEPTION( "SMESH_Mesh_i::CheckGeomModif() bug", - SALOME::INTERNAL_ERROR ); // re-assign hypotheses for ( size_t i = 0; i < ids2Hyps.size(); ++i ) { + if ( !sameTopology && ids2Hyps[i].first != 1 ) + continue; // assign only global hypos const TopoDS_Shape& s = meshDS->IndexToShape( ids2Hyps[i].first ); const THypList& hyps = ids2Hyps[i].second; THypList::const_iterator h = hyps.begin(); @@ -2346,31 +2370,40 @@ void SMESH_Mesh_i::CheckGeomModif() _impl->AddHypothesis( s, (*h)->GetID() ); } - // restore groups on geometry - for ( size_t i = 0; i < groupsData.size(); ++i ) + if ( !sameTopology ) { - const TGroupOnGeomData& data = groupsData[i]; - if ( data._shape.IsNull() ) - continue; + // remove invalid study sub-objects + CheckGeomGroupModif(); + } + else + { + // restore groups on geometry + for ( size_t i = 0; i < groupsData.size(); ++i ) + { + const TGroupOnGeomData& data = groupsData[i]; + if ( data._shape.IsNull() ) + continue; - std::map::iterator i2g = _mapGroups.find( data._oldID ); - if ( i2g == _mapGroups.end() ) continue; + std::map::iterator i2g = _mapGroups.find( data._oldID ); + if ( i2g == _mapGroups.end() ) continue; - SMESH_GroupBase_i* gr_i = SMESH::DownCast( i2g->second ); - if ( !gr_i ) continue; + SMESH_GroupBase_i* gr_i = SMESH::DownCast( i2g->second ); + if ( !gr_i ) continue; - SMESH_Group* g = _impl->AddGroup( data._type, data._name.c_str(), data._oldID, data._shape ); - if ( !g ) - _mapGroups.erase( i2g ); - else - g->GetGroupDS()->SetColor( data._color ); + SMESH_Group* g = _impl->AddGroup( data._type, data._name.c_str(), data._oldID, data._shape ); + if ( !g ) + _mapGroups.erase( i2g ); + else + g->GetGroupDS()->SetColor( data._color ); + } + + // update _mapSubMesh + std::map::iterator i_sm = _mapSubMesh.begin(); + for ( ; i_sm != _mapSubMesh.end(); ++i_sm ) + i_sm->second = _impl->GetSubMesh( meshDS->IndexToShape( i_sm->first )); } - // update _mapSubMesh - std::map::iterator i_sm = _mapSubMesh.begin(); - for ( ; i_sm != _mapSubMesh.end(); ++i_sm ) - i_sm->second = _impl->GetSubMesh( meshDS->IndexToShape( i_sm->first )); - + _gen_i->UpdateIcons( SMESH::SMESH_Mesh_var( _this() )); } //============================================================================= From 81dd6293459d01b01db558596cd0232f17d958df Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 17 Jan 2020 20:43:01 +0300 Subject: [PATCH 18/60] Fix sub-mesh creation by selection of mesh element + remove dead actors after BreakLink --- src/SMESHGUI/SMESHGUI.cxx | 9 ++++++++- src/SMESH_I/SMESH_Gen_i.cxx | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index 6d875cfd2..fb1e7ad93 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -1419,9 +1419,16 @@ namespace geomGen = GEOM::GEOM_Gen::_narrow(comp); } } - if (!CORBA::is_nil(geomGen)) { + if (!CORBA::is_nil(geomGen)) + { geomGen->BreakLink(aREntry.toStdString().c_str()); SMESHGUI::GetSMESHGUI()->updateObjBrowser(); + + // remove actors whose objects are removed by BreakLink() + QList wndList = SMESHGUI::desktop()->windows(); + SUIT_ViewWindow* wnd; + foreach(wnd, wndList) + SMESH::UpdateActorsAfterUpdateStudy(wnd); } } } diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index 2255f5823..334fc10ee 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -2328,7 +2328,7 @@ SMESH_Gen_i::GetGeometryByMeshElement( SMESH::SMESH_Mesh_ptr theMesh, GEOM::GEOM_Object_wrap geom = FindGeometryByMeshElement(theMesh, theElementID); if ( !geom->_is_nil() ) { GEOM::GEOM_Object_var mainShape = theMesh->GetShapeToMesh(); - GEOM::GEOM_Gen_ptr geomGen = GetGeomEngine( geom ); + GEOM::GEOM_Gen_var geomGen = GetGeomEngine( geom ); // try to find the corresponding SObject SALOMEDS::SObject_wrap SObj = ObjectToSObject( geom.in() ); @@ -2359,7 +2359,7 @@ SMESH_Gen_i::GetGeometryByMeshElement( SMESH::SMESH_Mesh_ptr theMesh, } } } - if ( SObj->_is_nil() ) // publish a new subshape + if ( SObj->_is_nil() && !geomGen->_is_nil() ) // publish a new subshape SObj = geomGen->AddInStudy( geom, theGeomName, mainShape ); // return only published geometry @@ -2392,7 +2392,7 @@ SMESH_Gen_i::FindGeometryByMeshElement( SMESH::SMESH_Mesh_ptr theMesh, THROW_SALOME_CORBA_EXCEPTION( "bad Mesh reference", SALOME::BAD_PARAM ); GEOM::GEOM_Object_var mainShape = theMesh->GetShapeToMesh(); - GEOM::GEOM_Gen_ptr geomGen = GetGeomEngine( mainShape ); + GEOM::GEOM_Gen_var geomGen = GetGeomEngine( mainShape ); // get a core mesh DS SMESH_Mesh_i* meshServant = SMESH::DownCast( theMesh ); @@ -2415,7 +2415,7 @@ SMESH_Gen_i::FindGeometryByMeshElement( SMESH::SMESH_Mesh_ptr theMesh, } if ( !it->_is_nil() ) { for ( it->InitEx(true); it->More(); it->Next() ) { - SALOMEDS::SObject_wrap so = it->Value(); + SALOMEDS::SObject_wrap so = it->Value(); CORBA::Object_var obj = SObjectToObject( so ); GEOM::GEOM_Object_var subGeom = GEOM::GEOM_Object::_narrow( obj ); if ( !subGeom->_is_nil() ) { From 3801576eaf335ea5886b0f73ac30e276a99807ba Mon Sep 17 00:00:00 2001 From: eap Date: Mon, 20 Jan 2020 18:51:55 +0300 Subject: [PATCH 19/60] in UpdateActorsAfterUpdateStudy() do not update actors else an actor can switch to "node display mode" --- src/SMESHGUI/SMESHGUI_VTKUtils.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx index bc6714980..a8564b42e 100644 --- a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx +++ b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx @@ -281,7 +281,7 @@ namespace SMESH _PTR(AttributePixMap) pixMap = attr; toShow = ( pixMap->GetPixMap() != emptyIcon ); } - smeshActor->Update(); + //smeshActor->Update(); UpdateView( theWindow, toShow ? eDisplay : eErase, io->getEntry() ); } } From 93ac2e89649b6c9fdeaa40f2e46aa3408a678d51 Mon Sep 17 00:00:00 2001 From: eap Date: Mon, 20 Jan 2020 18:57:12 +0300 Subject: [PATCH 20/60] fix update of sub-meshes on group in checkGeomModif() + make SMESH_Mesh_i::_mainShapeTick persistent, which is needed for the case: - open a saved study with shaper data - edit an object in the shaper - switch to smesh ==> shape modification not detected --- src/SMESH_I/SMESH_Gen_i.cxx | 20 +++++++++++++++++++- src/SMESH_I/SMESH_Mesh_i.cxx | 8 ++++---- src/SMESH_I/SMESH_Mesh_i.hxx | 8 +++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index 334fc10ee..41588902e 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -4369,6 +4369,14 @@ SALOMEDS::TMPFile* SMESH_Gen_i::Save( SALOMEDS::SComponent_ptr theComponent, aDataset->WriteOnDisk( &meshPersistentId ); aDataset->CloseOnDisk(); + // Store SMESH_Mesh_i::_mainShapeTick + int shapeTick = myImpl->MainShapeTick(); + aSize[ 0 ] = 1; + aDataset = new HDFdataset( "shapeTick", aTopGroup, HDF_INT32, aSize, 1 ); + aDataset->CreateOnDisk(); + aDataset->WriteOnDisk( &shapeTick ); + aDataset->CloseOnDisk(); + // write reference on a shape if exists SALOMEDS::SObject_wrap myRef; bool shapeRefFound = false; @@ -5393,7 +5401,7 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, } // issue 20918. Restore Persistent Id of SMESHDS_Mesh - if( aTopGroup->ExistInternalObject( "meshPersistentId" ) ) + if ( aTopGroup->ExistInternalObject( "meshPersistentId" ) ) { aDataset = new HDFdataset( "meshPersistentId", aTopGroup ); aDataset->OpenOnDisk(); @@ -5405,6 +5413,16 @@ bool SMESH_Gen_i::Load( SALOMEDS::SComponent_ptr theComponent, delete [] meshPersistentId; } + // Restore SMESH_Mesh_i::_mainShapeTick + if ( aTopGroup->ExistInternalObject( "shapeTick" )) + { + aDataset = new HDFdataset( "shapeTick", aTopGroup ); + aDataset->OpenOnDisk(); + int* shapeTick = & myNewMeshImpl->MainShapeTick(); + aDataset->ReadFromDisk( shapeTick ); + aDataset->CloseOnDisk(); + } + // Restore file info if ( aTopGroup->ExistInternalObject( "file info" )) { diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 57daad4a6..b0d14f8bb 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -2046,7 +2046,7 @@ void SMESH_Mesh_i::removeGeomGroupData(CORBA::Object_ptr theSmeshObj) */ //================================================================================ -TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData) +TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData, bool onlyIfChanged ) { TopoDS_Shape newShape; @@ -2066,7 +2066,7 @@ TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData) for ( CORBA::ULong i = 0; i < ids->length(); ++i ) curIndices.insert( ids[i] ); - if ( groupData._indices == curIndices ) + if ( onlyIfChanged && groupData._indices == curIndices ) return newShape; // group not changed // update data @@ -2344,7 +2344,7 @@ void SMESH_Mesh_i::CheckGeomModif() std::list::iterator data = _geomGroupData.begin(); for ( ; data != _geomGroupData.end(); ++data ) { - TopoDS_Shape newShape = newGroupShape( *data ); + TopoDS_Shape newShape = newGroupShape( *data, /*onlyIfChanged=*/false ); if ( !newShape.IsNull() ) { if ( meshDS->ShapeToIndex( newShape ) > 0 ) // a group reduced to one sub-shape @@ -2527,7 +2527,7 @@ void SMESH_Mesh_i::CheckGeomGroupModif() bool processedGroup = !it_new.second; TopoDS_Shape& newShape = it_new.first->second; if ( !processedGroup ) - newShape = newGroupShape( *data ); + newShape = newGroupShape( *data, /*onlyIfChanged=*/true ); if ( newShape.IsNull() ) continue; // no changes diff --git a/src/SMESH_I/SMESH_Mesh_i.hxx b/src/SMESH_I/SMESH_Mesh_i.hxx index 535931d93..388a04587 100644 --- a/src/SMESH_I/SMESH_Mesh_i.hxx +++ b/src/SMESH_I/SMESH_Mesh_i.hxx @@ -623,6 +623,12 @@ public: std::string FileInfoToString(); void FileInfoFromString(const std::string& info); + /*! + * Persistence of geometry tick + */ + int& MainShapeTick() { return _mainShapeTick; } + + /*! * Sets list of notebook variables used for Mesh operations separated by ":" symbol */ @@ -769,7 +775,7 @@ private: /*! * Return new group contents if it has been changed and update group data */ - TopoDS_Shape newGroupShape( TGeomGroupData & groupData); + TopoDS_Shape newGroupShape( TGeomGroupData & groupData, bool onlyIfChanged); }; From e3019524b4caa444880639745b22f30d577aba2c Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 21 Jan 2020 17:41:22 +0300 Subject: [PATCH 21/60] Fix sub-mesh update upon shaper geometry change --- src/SMESH_I/SMESH_Mesh_i.cxx | 97 ++++++++++++++++++++++++------------ src/SMESH_I/SMESH_Mesh_i.hxx | 4 +- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index b0d14f8bb..c65fc0e25 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -272,7 +272,7 @@ void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) // re-assign global hypotheses to the new shape _mainShapeTick = -1; - CheckGeomModif(); + CheckGeomModif( true ); } //================================================================================ @@ -2045,40 +2045,56 @@ void SMESH_Mesh_i::removeGeomGroupData(CORBA::Object_ptr theSmeshObj) * \brief Return new group contents if it has been changed and update group data */ //================================================================================ +enum { ONLY_IF_CHANGED, IS_BREAK_LINK, MAIN_TRANSFORMED }; -TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData, bool onlyIfChanged ) +TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData, int how ) { TopoDS_Shape newShape; - // get geom group - SALOMEDS::SObject_wrap groupSO = SMESH_Gen_i::getStudyServant()->FindObjectID( groupData._groupEntry.c_str() ); - if ( !groupSO->_is_nil() ) + if ( how == IS_BREAK_LINK ) { - CORBA::Object_var groupObj = _gen_i->SObjectToObject( groupSO ); - if ( CORBA::is_nil( groupObj )) return newShape; - GEOM::GEOM_Object_var geomGroup = GEOM::GEOM_Object::_narrow( groupObj ); - - // get indices of group items - set curIndices; - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); - GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); - GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); - for ( CORBA::ULong i = 0; i < ids->length(); ++i ) - curIndices.insert( ids[i] ); - - if ( onlyIfChanged && groupData._indices == curIndices ) - return newShape; // group not changed - - // update data - groupData._indices = curIndices; - - GEOM_Client* geomClient = _gen_i->GetShapeReader(); - if ( !geomClient ) return newShape; - CORBA::String_var groupIOR = geomGen->GetStringFromIOR( geomGroup ); - geomClient->RemoveShapeFromBuffer( groupIOR.in() ); - newShape = _gen_i->GeomObjectToShape( geomGroup ); + SALOMEDS::SObject_wrap meshSO = _gen_i->ObjectToSObject( groupData._smeshObject ); + SALOMEDS::SObject_wrap geomRefSO, geomSO; + if ( !meshSO->_is_nil() && + meshSO->FindSubObject( SMESH::Tag_RefOnShape, geomRefSO.inout() ) && + geomRefSO->ReferencedObject( geomSO.inout() )) + { + CORBA::Object_var geomObj = _gen_i->SObjectToObject( geomSO ); + GEOM::GEOM_Object_var geom = GEOM::GEOM_Object::_narrow( geomObj ); + newShape = _gen_i->GeomObjectToShape( geom ); + } } + else + { + // get geom group + SALOMEDS::SObject_wrap groupSO = SMESH_Gen_i::getStudyServant()->FindObjectID( groupData._groupEntry.c_str() ); + if ( !groupSO->_is_nil() ) + { + CORBA::Object_var groupObj = _gen_i->SObjectToObject( groupSO ); + if ( CORBA::is_nil( groupObj )) return newShape; + GEOM::GEOM_Object_var geomGroup = GEOM::GEOM_Object::_narrow( groupObj ); + // get indices of group items + set curIndices; + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); + GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); + GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); + for ( CORBA::ULong i = 0; i < ids->length(); ++i ) + curIndices.insert( ids[i] ); + + if ( how == ONLY_IF_CHANGED && groupData._indices == curIndices ) + return newShape; // group not changed + + // update data + groupData._indices = curIndices; + + GEOM_Client* geomClient = _gen_i->GetShapeReader(); + if ( !geomClient ) return newShape; + CORBA::String_var groupIOR = geomGen->GetStringFromIOR( geomGroup ); + geomClient->RemoveShapeFromBuffer( groupIOR.in() ); + newShape = _gen_i->GeomObjectToShape( geomGroup ); + } + } if ( newShape.IsNull() ) { // geom group becomes empty - return empty compound TopoDS_Compound compound; @@ -2177,7 +2193,7 @@ namespace */ //============================================================================= -void SMESH_Mesh_i::CheckGeomModif() +void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) { SMESH::SMESH_Mesh_var me = _this(); GEOM::GEOM_Object_var mainGO = GetShapeToMesh(); @@ -2290,7 +2306,24 @@ void SMESH_Mesh_i::CheckGeomModif() GEOM::GEOM_Object_var geom; if ( !gog->_is_nil() ) - geom = gog->GetShape(); + { + if ( isBreakLink ) + { + SALOMEDS::SObject_wrap grpSO = _gen_i->ObjectToSObject( gog ); + SALOMEDS::SObject_wrap geomRefSO, geomSO; + if ( !grpSO->_is_nil() && + grpSO->FindSubObject( SMESH::Tag_RefOnShape, geomRefSO.inout() ) && + geomRefSO->ReferencedObject( geomSO.inout() )) + { + CORBA::Object_var geomObj = _gen_i->SObjectToObject( geomSO ); + geom = GEOM::GEOM_Object::_narrow( geomObj ); + } + } + else + { + geom = gog->GetShape(); + } + } if ( !geom->_is_nil() ) { CORBA::String_var ior = geomGen->GetStringFromIOR( geom ); @@ -2344,7 +2377,7 @@ void SMESH_Mesh_i::CheckGeomModif() std::list::iterator data = _geomGroupData.begin(); for ( ; data != _geomGroupData.end(); ++data ) { - TopoDS_Shape newShape = newGroupShape( *data, /*onlyIfChanged=*/false ); + TopoDS_Shape newShape = newGroupShape( *data, isBreakLink ? IS_BREAK_LINK : MAIN_TRANSFORMED ); if ( !newShape.IsNull() ) { if ( meshDS->ShapeToIndex( newShape ) > 0 ) // a group reduced to one sub-shape @@ -2527,7 +2560,7 @@ void SMESH_Mesh_i::CheckGeomGroupModif() bool processedGroup = !it_new.second; TopoDS_Shape& newShape = it_new.first->second; if ( !processedGroup ) - newShape = newGroupShape( *data, /*onlyIfChanged=*/true ); + newShape = newGroupShape( *data, ONLY_IF_CHANGED ); if ( newShape.IsNull() ) continue; // no changes diff --git a/src/SMESH_I/SMESH_Mesh_i.hxx b/src/SMESH_I/SMESH_Mesh_i.hxx index 388a04587..40e97e4c3 100644 --- a/src/SMESH_I/SMESH_Mesh_i.hxx +++ b/src/SMESH_I/SMESH_Mesh_i.hxx @@ -465,7 +465,7 @@ public: * * Issue 0022501 */ - void CheckGeomModif(); + void CheckGeomModif( bool isBreakLink = false ); /*! * \brief Update hypotheses assigned to geom groups if the latter change * @@ -775,7 +775,7 @@ private: /*! * Return new group contents if it has been changed and update group data */ - TopoDS_Shape newGroupShape( TGeomGroupData & groupData, bool onlyIfChanged); + TopoDS_Shape newGroupShape( TGeomGroupData & groupData, int how ); }; From 7c8ea81b2987d272cefc43713949884edea63d32 Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 21 Jan 2020 17:42:01 +0300 Subject: [PATCH 22/60] Update sub-mesh actors after Compute --- src/SMESHGUI/SMESHGUI_ComputeDlg.cxx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx b/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx index 14e05427d..660838ddb 100644 --- a/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx @@ -951,7 +951,7 @@ void SMESHGUI_BaseComputeOp::computeMesh() SMESH::SMESH_IDSource_var aSubMeshObj = SMESH::SObjectToInterface( smSObj ); SMESH_Actor *anActor = SMESH::FindActorByObject( aSubMeshObj ); - if ( anActor && anActor->GetVisibility() ) + if ( anActor /*&& anActor->GetVisibility()*/ ) aListToUpdate.append( TListOf_IDSrc_SObj::value_type( aSubMeshObj, smSObj )); } // put Groups into list @@ -967,7 +967,7 @@ void SMESHGUI_BaseComputeOp::computeMesh() SMESH::SMESH_IDSource_var aGroupObj = SMESH::SObjectToInterface( aGroupSO ); SMESH_Actor *anActor = SMESH::FindActorByObject( aGroupObj ); - if ( anActor && anActor->GetVisibility() ) + if ( anActor /*&& anActor->GetVisibility()*/ ) aListToUpdate.append( TListOf_IDSrc_SObj::value_type( aGroupObj, aGroupSO )); } @@ -994,6 +994,13 @@ void SMESHGUI_BaseComputeOp::computeMesh() //SMESH::DisplayActor( SMESH::GetActiveWindow(), anActor ); -- 23615 } } + else + { + SMESH_Actor *anActor = SMESH::FindActorByEntry( entry.c_str() ); + anActor->Update(); + if ( !anActor->GetVisibility() ) + continue; + } SMESH::UpdateView( SMESH::eDisplay, entry.c_str() ); if ( SVTK_ViewWindow* vtkWnd = SMESH::GetVtkViewWindow(SMESH::GetActiveWindow() )) From fe414fe4e7ff1d0488d8a9b9795e04a1ad34a75a Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 23 Jan 2020 21:56:19 +0300 Subject: [PATCH 23/60] adjust for shaperBuilder --- src/SMESH_SWIG/smeshBuilder.py | 2 +- src/SMESH_SWIG/smesh_algorithm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index 8fd7b6166..696d4f3cc 100644 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -305,7 +305,7 @@ def AssureGeomPublished(mesh, geom, name=''): """ if not mesh.smeshpyD.IsEnablePublish(): return - if not isinstance( geom, geomBuilder.GEOM._objref_GEOM_Object ): + if not hasattr( geom, "GetShapeType" ): return if not geom.GetStudyEntry(): ## get a name diff --git a/src/SMESH_SWIG/smesh_algorithm.py b/src/SMESH_SWIG/smesh_algorithm.py index d962b4548..be5552d8e 100644 --- a/src/SMESH_SWIG/smesh_algorithm.py +++ b/src/SMESH_SWIG/smesh_algorithm.py @@ -396,7 +396,7 @@ class Mesh_Algorithm: geompy = self.mesh.geompyD for i in reverseList: if isinstance( i, int ): - s = geompy.SubShapes(self.mesh.geom, [i])[0] + s = geompy.GetSubShape(self.mesh.geom, [i]) if s.GetShapeType() != geomBuilder.GEOM.EDGE: raise TypeError("Not EDGE index given") resList.append( i ) From c4d4c3be32adf37c4ff99664205b4f15e1560674 Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 24 Jan 2020 21:36:13 +0300 Subject: [PATCH 24/60] Fix exception at Break Link after shaper group modification Scenario: - Create a mesh on a box with sub-mesh on a group - Modify group in shaper - Switch to SMESH and Break Link ==> SIGSEGV --- src/SMESHGUI/SMESHGUI_ComputeDlg.cxx | 2 +- src/SMESHGUI/SMESHGUI_GroupDlg.cxx | 2 ++ src/SMESH_I/SMESH_Mesh_i.cxx | 41 +++++++++++++++++++++++++--- src/SMESH_I/SMESH_subMesh_i.hxx | 1 + 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx b/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx index 660838ddb..b5720d797 100644 --- a/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_ComputeDlg.cxx @@ -1280,7 +1280,7 @@ void SMESHGUI_BaseComputeOp::onPublishShape() // look for myMainShape in the table for ( int r = 0, nr = table()->rowCount(); r < nr; ++r ) { if ( table()->item( r, COL_SHAPEID )->text() == "1" ) { - if ( so->_is_nil() ) { + if ( !so->_is_nil() ) { CORBA::String_var name = so->GetName(); CORBA::String_var entry = so->GetID(); QString shapeText = QString("%1 (%2)").arg( name.in() ).arg( entry.in() ); diff --git a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx index 62666c97f..0da119054 100644 --- a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx @@ -1083,6 +1083,8 @@ bool SMESHGUI_GroupDlg::onApply() GEOM::GEOM_Object_var aMeshShape = myMesh->GetShapeToMesh(); GEOM::GEOM_Object_wrap aGroupVar = op->CreateGroup(aMeshShape, aGroupType); + if ( aGroupVar->_is_nil() ) + return false; op->UnionList(aGroupVar, myGeomObjects); if (op->IsDone()) { diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index c65fc0e25..8b2fe347f 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -2002,8 +2002,7 @@ void SMESH_Mesh_i::addGeomGroupData(GEOM::GEOM_Object_ptr theGeomObj, return; // group indices GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( theGeomObj ); - GEOM::GEOM_IGroupOperations_ptr groupOp = - geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); GEOM::ListOfLong_var ids = groupOp->GetObjects( theGeomObj ); // store data @@ -2347,6 +2346,8 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) ids2Hyps.push_back( make_pair( meshDS->ShapeToIndex( s ), hyps )); } + std::map< std::set, int > ii2iMap; // group sub-ids to group id in SMESHDS + // count shapes excluding compounds corresponding to geom groups int oldNbSubShapes = meshDS->MaxShapeIndex(); for ( ; oldNbSubShapes > 0; --oldNbSubShapes ) @@ -2354,6 +2355,11 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) const TopoDS_Shape& s = meshDS->IndexToShape( oldNbSubShapes ); if ( s.IsNull() || s.ShapeType() != TopAbs_COMPOUND ) break; + // fill ii2iMap + std::set subIds; + for ( TopoDS_Iterator it( s ); it.More(); it.Next() ) + subIds.insert( meshDS->ShapeToIndex( it.Value() )); + ii2iMap.insert( std::make_pair( subIds, oldNbSubShapes )); } // check if shape topology changes - save shape type per shape ID @@ -2374,9 +2380,15 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) } // re-add shapes (compounds) of geom groups + std::map< int, int > old2newIDs; // group IDs std::list::iterator data = _geomGroupData.begin(); for ( ; data != _geomGroupData.end(); ++data ) { + int oldID = 0; + std::map< std::set, int >::iterator ii2i = ii2iMap.find( data->_indices ); + if ( ii2i != ii2iMap.end() ) + oldID = ii2i->second; + TopoDS_Shape newShape = newGroupShape( *data, isBreakLink ? IS_BREAK_LINK : MAIN_TRANSFORMED ); if ( !newShape.IsNull() ) { @@ -2387,7 +2399,9 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) BRep_Builder().Add( compound, newShape ); newShape = compound; } - _impl->GetSubMesh( newShape ); + int newID = _impl->GetSubMesh( newShape )->GetId(); + if ( oldID && oldID != newID ) + old2newIDs.insert( std::make_pair( oldID, newID )); } } @@ -2396,7 +2410,11 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) { if ( !sameTopology && ids2Hyps[i].first != 1 ) continue; // assign only global hypos - const TopoDS_Shape& s = meshDS->IndexToShape( ids2Hyps[i].first ); + int sID = ids2Hyps[i].first; + std::map< int, int >::iterator o2n = old2newIDs.find( sID ); + if ( o2n != old2newIDs.end() ) + sID = o2n->second; + const TopoDS_Shape& s = meshDS->IndexToShape( sID ); const THypList& hyps = ids2Hyps[i].second; THypList::const_iterator h = hyps.begin(); for ( ; h != hyps.end(); ++h ) @@ -2430,6 +2448,21 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) g->GetGroupDS()->SetColor( data._color ); } + std::map< int, int >::iterator o2n = old2newIDs.begin(); + for ( ; o2n != old2newIDs.end(); ++o2n ) + { + int newID = o2n->second, oldID = o2n->first; + if ( !_mapSubMesh.count( oldID )) + continue; + _mapSubMesh [ newID ] = _impl->GetSubMeshContaining( newID ); + _mapSubMesh_i [ newID ] = _mapSubMesh_i [ oldID ]; + _mapSubMeshIor[ newID ] = _mapSubMeshIor[ oldID ]; + _mapSubMesh. erase(oldID); + _mapSubMesh_i. erase(oldID); + _mapSubMeshIor.erase(oldID); + _mapSubMesh_i [ newID ]->changeLocalId( newID ); + } + // update _mapSubMesh std::map::iterator i_sm = _mapSubMesh.begin(); for ( ; i_sm != _mapSubMesh.end(); ++i_sm ) diff --git a/src/SMESH_I/SMESH_subMesh_i.hxx b/src/SMESH_I/SMESH_subMesh_i.hxx index f782031e6..0af1debff 100644 --- a/src/SMESH_I/SMESH_subMesh_i.hxx +++ b/src/SMESH_I/SMESH_subMesh_i.hxx @@ -123,6 +123,7 @@ protected: void changeLocalId(int localId) { _localId = localId; } friend void SMESH_Mesh_i::CheckGeomGroupModif(); + friend void SMESH_Mesh_i::CheckGeomModif(bool); SMESH_PreMeshInfo* _preMeshInfo; // mesh info before full loading from study file From ab494e654ae6e3b7cc7c272dc09f8aedc6ffddae Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 28 Jan 2020 17:14:39 +0300 Subject: [PATCH 25/60] New mesh icon if geometry changes --- resources/CMakeLists.txt | 1 + resources/mesh_tree_mesh_geom_modif.png | Bin 0 -> 307 bytes src/SMESHGUI/SMESHGUI.cxx | 130 +++++++++++++++++++++++- src/SMESHGUI/SMESH_images.ts | 4 + src/SMESHGUI/SMESH_msg_en.ts | 5 + src/SMESH_I/SMESH_Gen_i.cxx | 47 +++++++++ src/SMESH_I/SMESH_Gen_i.hxx | 6 ++ src/SMESH_I/SMESH_Mesh_i.cxx | 7 ++ 8 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 resources/mesh_tree_mesh_geom_modif.png diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 7b880a703..9a122695a 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -114,6 +114,7 @@ SET(SMESH_RESOURCES_FILES mesh_tree_mesh.png mesh_tree_importedmesh.png mesh_tree_mesh_warn.png + mesh_tree_mesh_geom_modif.png mesh_triangle_n.png mesh_triquad_hexahedron.png mesh_triangle.png diff --git a/resources/mesh_tree_mesh_geom_modif.png b/resources/mesh_tree_mesh_geom_modif.png new file mode 100644 index 0000000000000000000000000000000000000000..8f05208ede2c4ac6401cb3462d531c5348b1a02d GIT binary patch literal 307 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xawj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pIwQHGD3_had<`#_-so-U3d8t3Ou+Q{2%AmBRRSIy%f z*TOQ-UJWC*UQxD9PKQ~18dh9xwOTWQDV5P>RselectedObjects(selected,"",/*convertReferences=*/false); + + SALOME_ListIteratorOfListIO It( selected ); + for ( ; It.More(); It.Next() ) + { + Handle(SALOME_InteractiveObject) io = It.Value(); + if ( !io->hasEntry() ) continue; + _PTR(SObject) so = SMESH::getStudy()->FindObjectID( io->getEntry() ); + SMESH::SMESH_Mesh_var mesh; + while ( mesh->_is_nil() && so ) + { + CORBA::Object_var obj = SMESH::SObjectToObject( so ); + SMESH::SMESH_IDSource_var isrc = SMESH::SMESH_IDSource::_narrow( obj ); + if ( isrc->_is_nil() ) + so = so->GetFather(); + else + mesh = isrc->GetMesh(); + } + if ( mesh->_is_nil() ) continue; + so = SMESH::FindSObject( mesh ); + if ( !so ) continue; + _PTR(GenericAttribute) attr; + so->FindAttribute( attr, "AttributePixMap" ); + _PTR(AttributePixMap) pixmap = attr; + if ( !pixmap ) continue; + + if ( pixmap->GetPixMap() == "ICON_SMESH_TREE_GEOM_MODIF" ) + { + SUIT_MessageBox::warning(SMESHGUI::desktop(), + QObject::tr("SMESH_WRN_WARNING"), + QObject::tr("MSG_WARN_ON_GEOM_MODIF")); + return true; + } + } + return false; + } + void SetDisplayMode(int theCommandID, VTK::MarkerMap& theMarkerMap) { SALOME_ListIO selected; @@ -2808,16 +2857,19 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) break; } - case SMESHOp::OpCreateMesh: - case SMESHOp::OpCreateSubMesh: case SMESHOp::OpEditMeshOrSubMesh: case SMESHOp::OpEditMesh: case SMESHOp::OpEditSubMesh: + case SMESHOp::OpMeshOrder: + case SMESHOp::OpCreateSubMesh: + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified + + case SMESHOp::OpCreateMesh: case SMESHOp::OpCompute: case SMESHOp::OpComputeSubMesh: case SMESHOp::OpPreCompute: case SMESHOp::OpEvaluate: - case SMESHOp::OpMeshOrder: startOperation( theCommandID ); break; case SMESHOp::OpCopyMesh: @@ -2846,6 +2898,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) if ( isStudyLocked() ) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified /*Standard_Boolean aRes; SMESH::SMESH_Mesh_var aMesh = SMESH::IObjectToInterface(IObject); @@ -2876,6 +2930,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) if ( isStudyLocked() ) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified EmitSignalDeactivateDialog(); SMESHGUI_MultiEditDlg* aDlg = NULL; @@ -2894,6 +2950,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpSmoothing: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_SmoothingDlg( this ) )->show(); @@ -2906,6 +2964,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpExtrusion: { if (isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if (vtkwnd) { EmitSignalDeactivateDialog(); ( new SMESHGUI_ExtrusionDlg ( this ) )->show(); @@ -2917,6 +2977,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpExtrusionAlongAPath: { if (isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if (vtkwnd) { EmitSignalDeactivateDialog(); ( new SMESHGUI_ExtrusionAlongPathDlg( this ) )->show(); @@ -2928,6 +2990,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpRevolution: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_RevolutionDlg( this ) )->show(); @@ -2941,6 +3005,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) { if ( isStudyLocked() ) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); @@ -2957,6 +3023,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpReorientFaces: case SMESHOp::OpCreateGeometryGroup: { + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified startOperation( theCommandID ); break; } @@ -2969,6 +3037,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) } if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified EmitSignalDeactivateDialog(); SMESH::SMESH_Mesh_var aMesh = SMESH::SMESH_Mesh::_nil(); @@ -2996,6 +3066,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) } if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified EmitSignalDeactivateDialog(); LightApp_SelectionMgr *aSel = SMESHGUI::selectionMgr(); @@ -3073,6 +3145,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) } if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified EmitSignalDeactivateDialog(); LightApp_SelectionMgr *aSel = SMESHGUI::selectionMgr(); @@ -3170,6 +3244,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) if ( isStudyLocked() ) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified EmitSignalDeactivateDialog(); @@ -3190,6 +3266,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) { if ( isStudyLocked() ) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified EmitSignalDeactivateDialog(); SMESHGUI_GroupOpDlg* aDlg = new SMESHGUI_DimGroupDlg( this ); @@ -3202,6 +3280,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) { if ( isStudyLocked() ) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified EmitSignalDeactivateDialog(); SMESHGUI_FaceGroupsSeparatedByEdgesDlg* aDlg = new SMESHGUI_FaceGroupsSeparatedByEdgesDlg( this ); @@ -3261,6 +3341,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpEditHypothesis: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified LightApp_SelectionMgr *aSel = SMESHGUI::selectionMgr(); SALOME_ListIO selected; @@ -3306,6 +3388,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpUnassign: // REMOVE HYPOTHESIS / ALGORITHMS { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified SUIT_OverrideCursor wc; LightApp_SelectionMgr *aSel = SMESHGUI::selectionMgr(); @@ -3337,6 +3421,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpHexagonalPrism: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); SMDSAbs_EntityType type = SMDSEntity_Edge; @@ -3363,6 +3449,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpPolyhedron: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_CreatePolyhedralVolumeDlg( this ) )->show(); @@ -3386,6 +3474,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpTriQuadraticHexahedron: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); SMDSAbs_EntityType type = SMDSEntity_Last; @@ -3417,6 +3507,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpRemoveNodes: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_RemoveNodesDlg( this ) )->show(); @@ -3430,6 +3522,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpRemoveElements: // REMOVES ELEMENTS { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_RemoveElementsDlg( this ) )->show(); @@ -3444,6 +3538,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpClearMesh: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified SALOME_ListIO selected; if( LightApp_SelectionMgr *aSel = SMESHGUI::selectionMgr() ) @@ -3483,6 +3579,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpRemoveOrphanNodes: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified SALOME_ListIO selected; if( LightApp_SelectionMgr *aSel = SMESHGUI::selectionMgr() ) aSel->selectedObjects( selected ); @@ -3522,6 +3620,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpRenumberingNodes: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_RenumberingDlg( this, 0 ) )->show(); @@ -3536,6 +3636,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpRenumberingElements: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_RenumberingDlg( this, 1 ) )->show(); @@ -3550,6 +3652,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpTranslation: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_TranslationDlg( this ) )->show(); @@ -3563,6 +3667,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpRotation: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_RotationDlg( this ) )->show(); @@ -3576,6 +3682,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpSymmetry: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if(vtkwnd) { EmitSignalDeactivateDialog(); ( new SMESHGUI_SymmetryDlg( this ) )->show(); @@ -3589,6 +3697,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpScale: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_ScaleDlg( this ) )->show(); @@ -3603,6 +3713,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpOffset: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_OffsetDlg( this ) )->show(); @@ -3617,6 +3729,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpSewing: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if(vtkwnd) { EmitSignalDeactivateDialog(); ( new SMESHGUI_SewingDlg( this ) )->show(); @@ -3630,6 +3744,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpMergeNodes: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if(vtkwnd) { EmitSignalDeactivateDialog(); ( new SMESHGUI_MergeDlg( this, 0 ) )->show(); @@ -3643,6 +3759,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) case SMESHOp::OpMergeElements: { if (isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if (vtkwnd) { EmitSignalDeactivateDialog(); ( new SMESHGUI_MergeDlg( this, 1 ) )->show(); @@ -3654,12 +3772,16 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) } case SMESHOp::OpMoveNode: // MAKE MESH PASS THROUGH POINT + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified startOperation( SMESHOp::OpMoveNode ); break; case SMESHOp::OpDuplicateNodes: { if(isStudyLocked()) break; + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified if ( vtkwnd ) { EmitSignalDeactivateDialog(); ( new SMESHGUI_DuplicateNodesDlg( this ) )->show(); @@ -3672,6 +3794,8 @@ bool SMESHGUI::OnGUIEvent( int theCommandID ) } case SMESHOp::OpElem0DOnElemNodes: // 0D_ON_ALL_NODES + if ( warnOnGeomModif() ) + break; // action forbiden as geometry modified startOperation( SMESHOp::OpElem0DOnElemNodes ); break; diff --git a/src/SMESHGUI/SMESH_images.ts b/src/SMESHGUI/SMESH_images.ts index 0a09a43ec..9f0e4a777 100644 --- a/src/SMESHGUI/SMESH_images.ts +++ b/src/SMESHGUI/SMESH_images.ts @@ -531,6 +531,10 @@ ICON_SMESH_TREE_MESH_WARN mesh_tree_mesh_warn.png + + ICON_SMESH_TREE_GEOM_MODIF + mesh_tree_mesh_geom_modif.png + ICON_STD_INFO standard_mesh_info.png diff --git a/src/SMESHGUI/SMESH_msg_en.ts b/src/SMESHGUI/SMESH_msg_en.ts index 635580697..62e0db990 100644 --- a/src/SMESHGUI/SMESH_msg_en.ts +++ b/src/SMESHGUI/SMESH_msg_en.ts @@ -4515,6 +4515,11 @@ It can't be deleted MSG_BREAK_SHAPER_LINK A link with Shaper model for object %1 will be broken. Continue? + + + MSG_WARN_ON_GEOM_MODIF + This action is prohibited since the geometry +was changed and the mesh needs to be recomputed. SMESH_ADVANCED diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index 41588902e..5e4bba536 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -731,6 +731,53 @@ void SMESH_Gen_i::UpdateStudy() } } +//================================================================================= +// function : hasObjectInfo() +// purpose : shows if module provides information for its objects +//================================================================================= + +bool SMESH_Gen_i::hasObjectInfo() +{ + return true; +} + +//================================================================================= +// function : getObjectInfo() +// purpose : returns an information for a given object by its entry +//================================================================================= + +char* SMESH_Gen_i::getObjectInfo( const char* entry ) +{ + // for a mesh with icon == ICON_SMESH_TREE_GEOM_MODIF show a warning; + // for the rest, "module 'SMESH', ID=0:1:2:*" + + SMESH_Comment txt; + + SALOMEDS::SObject_wrap so = getStudyServant()->FindObjectID( entry ); + CORBA::Object_var obj = SObjectToObject( so ); + SMESH::SMESH_Mesh_var mesh = SMESH::SMESH_Mesh::_narrow( obj ); + if ( !mesh->_is_nil() ) + { + SALOMEDS::GenericAttribute_wrap attr; + if ( so->FindAttribute( attr.inout(), "AttributePixMap" )) + { + SALOMEDS::AttributePixMap_wrap pm = attr; + CORBA::String_var ico = pm->GetPixMap(); + if ( strcmp( ico.in(), "ICON_SMESH_TREE_GEOM_MODIF" ) == 0 ) + { + txt << "The geometry was changed and the mesh needs to be recomputed"; + } + } + } + + if ( txt.empty() ) + { + CORBA::String_var compType = ComponentDataType(); + txt << "module '" << compType << "', ID=" << entry; + } + return CORBA::string_dup( txt ); +} + //============================================================================= /*! * SMESH_Gen_i::GetStudyContext diff --git a/src/SMESH_I/SMESH_Gen_i.hxx b/src/SMESH_I/SMESH_Gen_i.hxx index 93359e4bf..3f03b67b2 100644 --- a/src/SMESH_I/SMESH_Gen_i.hxx +++ b/src/SMESH_I/SMESH_Gen_i.hxx @@ -159,6 +159,12 @@ public: // Update study void UpdateStudy(); + // Do provide info on objects + bool hasObjectInfo(); + + // Return an information for a given object + char* getObjectInfo(const char* entry); + // Create hypothesis/algorithm of given type SMESH::SMESH_Hypothesis_ptr CreateHypothesis (const char* theHypType, const char* theLibName) diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 8b2fe347f..3f65f9f9b 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -2470,6 +2470,13 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) } _gen_i->UpdateIcons( SMESH::SMESH_Mesh_var( _this() )); + + if ( !isBreakLink ) + { + SALOMEDS::SObject_wrap meshSO = _gen_i->ObjectToSObject( me ); + if ( !meshSO->_is_nil() ) + _gen_i->SetPixMap(meshSO, "ICON_SMESH_TREE_GEOM_MODIF"); + } } //============================================================================= From 2cdec6ec506baa5a7777e3c1d6826ff176f9f3e9 Mon Sep 17 00:00:00 2001 From: mpv Date: Thu, 30 Jan 2020 19:18:17 +0300 Subject: [PATCH 26/60] Support of python dump in SHAPER STUDY --- src/SMESH_I/SMESH_2smeshpy.cxx | 65 +++++++++++++++++++--------------- src/SMESH_I/SMESH_2smeshpy.hxx | 3 ++ 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index b22af73eb..d126f6b23 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -567,7 +567,8 @@ _pyGen::_pyGen(Resource_DataMapOfAsciiStringAsciiString& theEntry2AccessorMethod myRemovedObjIDs( theRemovedObjIDs ), myNbFilters( 0 ), myToKeepAllCommands( theToKeepAllCommands ), - myGeomIDNb(0), myGeomIDIndex(-1) + myGeomIDNb(0), myGeomIDIndex(-1), + myShaperIDNb(0), myShaperIDIndex(-1) { // make that GetID() to return TPythonDump::SMESHGenName() GetCreationCmd()->Clear(); @@ -575,30 +576,38 @@ _pyGen::_pyGen(Resource_DataMapOfAsciiStringAsciiString& theEntry2AccessorMethod GetCreationCmd()->GetString() += "="; // Find 1st digit of study entry by which a GEOM object differs from a SMESH object - if ( !theObjectNames.IsEmpty() ) + if (!theObjectNames.IsEmpty()) { - // find a GEOM entry - _pyID geomID; - SALOMEDS::SComponent_wrap geomComp = SMESH_Gen_i::getStudyServant()->FindComponent("GEOM"); - if ( geomComp->_is_nil() ) return; - CORBA::String_var entry = geomComp->GetID(); - geomID = entry.in(); + // find a GEOM (aPass == 0) and SHAPERSTUDY (aPass == 1) entries + for(int aPass = 0; aPass < 2; aPass++) { + _pyID geomID; + SALOMEDS::SComponent_wrap geomComp = SMESH_Gen_i::getStudyServant()-> + FindComponent(aPass == 0 ? "GEOM" : "SHAPERSTUDY"); + if (geomComp->_is_nil()) continue; + CORBA::String_var entry = geomComp->GetID(); + geomID = entry.in(); - // find a SMESH entry - _pyID smeshID; - Resource_DataMapIteratorOfDataMapOfAsciiStringAsciiString e2n( theObjectNames ); - for ( ; e2n.More() && smeshID.IsEmpty(); e2n.Next() ) - if ( _pyCommand::IsStudyEntry( e2n.Key() )) - smeshID = e2n.Key(); + // find a SMESH entry + _pyID smeshID; + Resource_DataMapIteratorOfDataMapOfAsciiStringAsciiString e2n(theObjectNames); + for (; e2n.More() && smeshID.IsEmpty(); e2n.Next()) + if (_pyCommand::IsStudyEntry(e2n.Key())) + smeshID = e2n.Key(); - // find 1st difference between smeshID and geomID - if ( !geomID.IsEmpty() && !smeshID.IsEmpty() ) - for ( int i = 1; i <= geomID.Length() && i <= smeshID.Length(); ++i ) - if ( geomID.Value( i ) != smeshID.Value( i )) - { - myGeomIDNb = geomID.Value( i ); - myGeomIDIndex = i; - } + // find 1st difference between smeshID and geomID + if (!geomID.IsEmpty() && !smeshID.IsEmpty()) + for (int i = 1; i <= geomID.Length() && i <= smeshID.Length(); ++i) + if (geomID.Value(i) != smeshID.Value(i)) + { + if (aPass == 0) { + myGeomIDNb = geomID.Value(i); + myGeomIDIndex = i; + } else { + myShaperIDNb = geomID.Value(i); + myShaperIDIndex = i; + } + } + } } } @@ -1680,13 +1689,11 @@ Handle(_pyObject) _pyGen::FindObject( const _pyID& theObjID ) const bool _pyGen::IsGeomObject(const _pyID& theObjID) const { - if ( myGeomIDNb ) - { - return ( myGeomIDIndex <= theObjID.Length() && - int( theObjID.Value( myGeomIDIndex )) == myGeomIDNb && - _pyCommand::IsStudyEntry( theObjID )); - } - return false; + bool isGeom = myGeomIDNb && myGeomIDIndex <= theObjID.Length() && + int( theObjID.Value( myGeomIDIndex )) == myGeomIDNb; + bool isShaper = myShaperIDNb && myShaperIDIndex <= theObjID.Length() && + int( theObjID.Value( myShaperIDIndex )) == myShaperIDNb; + return ((isGeom || isShaper) && _pyCommand::IsStudyEntry( theObjID )); } //================================================================================ diff --git a/src/SMESH_I/SMESH_2smeshpy.hxx b/src/SMESH_I/SMESH_2smeshpy.hxx index 71ecd1445..b7db1557c 100644 --- a/src/SMESH_I/SMESH_2smeshpy.hxx +++ b/src/SMESH_I/SMESH_2smeshpy.hxx @@ -323,7 +323,10 @@ private: Handle(_pyCommand) myLastCommand; int myNbFilters; bool myToKeepAllCommands; + // difference of entry and index of this difference int myGeomIDNb, myGeomIDIndex; + // difference of entry and index of this difference, specific for the SHAPER study + int myShaperIDNb, myShaperIDIndex; std::map< _AString, ExportedMeshData > myFile2ExportedMesh; Handle( _pyHypothesisReader ) myHypReader; From 93781217abf598f9815e290739ef6b579007308a Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 1 Nov 2019 17:54:32 +0300 Subject: [PATCH 27/60] #16479 [CEA 12950] ViscousLayers hypothesis and periodic condition --- src/SMESH/SMESH_MeshEditor.cxx | 8 +- src/StdMeshers/StdMeshers_FaceSide.cxx | 2 +- src/StdMeshers/StdMeshers_ViscousLayers.cxx | 766 ++++++++++++++++---- 3 files changed, 642 insertions(+), 134 deletions(-) diff --git a/src/SMESH/SMESH_MeshEditor.cxx b/src/SMESH/SMESH_MeshEditor.cxx index b7c6ef24a..db5b6507f 100644 --- a/src/SMESH/SMESH_MeshEditor.cxx +++ b/src/SMESH/SMESH_MeshEditor.cxx @@ -9880,10 +9880,10 @@ SMESH_MeshEditor::FindMatchingNodes(set& theSide1, set * faceSetPtr[] = { &theSide1, &theSide2 }; nReplaceMap.clear(); - if ( theFirstNode1 != theFirstNode2 ) - nReplaceMap.insert( make_pair( theFirstNode1, theFirstNode2 )); - if ( theSecondNode1 != theSecondNode2 ) - nReplaceMap.insert( make_pair( theSecondNode1, theSecondNode2 )); + //if ( theFirstNode1 != theFirstNode2 ) + nReplaceMap.insert( make_pair( theFirstNode1, theFirstNode2 )); + //if ( theSecondNode1 != theSecondNode2 ) + nReplaceMap.insert( make_pair( theSecondNode1, theSecondNode2 )); set< SMESH_TLink > linkSet; // set of nodes where order of nodes is ignored linkSet.insert( SMESH_TLink( theFirstNode1, theSecondNode1 )); diff --git a/src/StdMeshers/StdMeshers_FaceSide.cxx b/src/StdMeshers/StdMeshers_FaceSide.cxx index 455af191c..6c6d4c2be 100644 --- a/src/StdMeshers/StdMeshers_FaceSide.cxx +++ b/src/StdMeshers/StdMeshers_FaceSide.cxx @@ -665,7 +665,7 @@ std::vector StdMeshers_FaceSide::GetOrderedNodes(int theEd iE = theEdgeInd % NbEdges(); iEnd = iE + 1; } - for ( iE = 0; iE < iEnd; ++iE ) + for ( ; iE < iEnd; ++iE ) { double prevNormPar = ( iE == 0 ? 0 : myNormPar[ iE-1 ]); // normalized param diff --git a/src/StdMeshers/StdMeshers_ViscousLayers.cxx b/src/StdMeshers/StdMeshers_ViscousLayers.cxx index 31669f15c..075d1b46d 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers.cxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers.cxx @@ -23,6 +23,7 @@ #include "StdMeshers_ViscousLayers.hxx" +#include "ObjectPool.hxx" #include "SMDS_EdgePosition.hxx" #include "SMDS_FaceOfNodes.hxx" #include "SMDS_FacePosition.hxx" @@ -33,6 +34,7 @@ #include "SMESHDS_Hypothesis.hxx" #include "SMESHDS_Mesh.hxx" #include "SMESH_Algo.hxx" +#include "SMESH_Block.hxx" #include "SMESH_ComputeError.hxx" #include "SMESH_ControlsDef.hxx" #include "SMESH_Gen.hxx" @@ -40,12 +42,13 @@ #include "SMESH_HypoFilter.hxx" #include "SMESH_Mesh.hxx" #include "SMESH_MeshAlgos.hxx" +#include "SMESH_MeshEditor.hxx" #include "SMESH_MesherHelper.hxx" #include "SMESH_ProxyMesh.hxx" #include "SMESH_subMesh.hxx" -#include "SMESH_MeshEditor.hxx" #include "SMESH_subMeshEventListener.hxx" #include "StdMeshers_FaceSide.hxx" +#include "StdMeshers_ProjectionUtils.hxx" #include "StdMeshers_ViscousLayers2D.hxx" #include @@ -372,22 +375,7 @@ namespace VISCOUS_3D double _h2lenRatio; // avgNormProj / (2*avgDist) gp_Pnt2d _uv; // UV used in putOnOffsetSurface() public: - static _Curvature* New( double avgNormProj, double avgDist ) - { - _Curvature* c = 0; - if ( fabs( avgNormProj / avgDist ) > 1./200 ) - { - c = new _Curvature; - c->_r = avgDist * avgDist / avgNormProj; - c->_k = avgDist * avgDist / c->_r / c->_r; - //c->_k = avgNormProj / c->_r; - c->_k *= ( c->_r < 0 ? 1/1.1 : 1.1 ); // not to be too restrictive - c->_h2lenRatio = avgNormProj / ( avgDist + avgDist ); - - c->_uv.SetCoord( 0., 0. ); - } - return c; - } + static _Curvature* New( double avgNormProj, double avgDist ); double lenDelta(double len) const { return _k * ( _r + len ); } double lenDeltaByDist(double dist) const { return dist * _h2lenRatio; } }; @@ -583,6 +571,7 @@ namespace VISCOUS_3D gp_XYZ* _plnNorm; _2NearEdges() { _edges[0]=_edges[1]=0; _plnNorm = 0; } + ~_2NearEdges(){ delete _plnNorm; } const SMDS_MeshNode* tgtNode(bool is2nd) { return _edges[is2nd] ? _edges[is2nd]->_nodes.back() : 0; } @@ -637,6 +626,15 @@ namespace VISCOUS_3D bool IsOffsetMethod() const { return _method == StdMeshers_ViscousLayers::FACE_OFFSET; } + bool operator==( const AverageHyp& other ) const + { + return ( _nbLayers == other._nbLayers && + _method == other._method && + Equals( GetTotalThickness(), other.GetTotalThickness() ) && + Equals( GetStretchFactor(), other.GetStretchFactor() )); + } + static bool Equals( double v1, double v2 ) { return Abs( v1 - v2 ) < 0.01 * ( v1 + v2 ); } + private: int _nbLayers, _nbHyps, _method; double _thickness, _stretchFactor; @@ -684,6 +682,7 @@ namespace VISCOUS_3D _SolidData& GetData() const { return *_data; } _EdgesOnShape(): _shapeID(-1), _subMesh(0), _toSmooth(false), _edgeSmoother(0) {} + ~_EdgesOnShape(); }; //-------------------------------------------------------------------------------- @@ -743,6 +742,7 @@ namespace VISCOUS_3D TopTools_MapOfShape _before; // SOLIDs to be computed before _solid TGeomID _index; // SOLID id _MeshOfSolid* _proxyMesh; + bool _done; list< THyp > _hyps; list< TopoDS_Shape > _hypShapes; map< TGeomID, THyp > _face2hyp; // filled if _hyps.size() > 1 @@ -759,7 +759,7 @@ namespace VISCOUS_3D // _LayerEdge's with underlying shapes vector< _EdgesOnShape > _edgesOnShape; - // key: an id of shape (EDGE or VERTEX) shared by a FACE with + // key: an ID of shape (EDGE or VERTEX) shared by a FACE with // layers and a FACE w/o layers // value: the shape (FACE or EDGE) to shrink mesh on. // _LayerEdge's basing on nodes on key shape are inflated along the value shape @@ -786,8 +786,8 @@ namespace VISCOUS_3D _SolidData(const TopoDS_Shape& s=TopoDS_Shape(), _MeshOfSolid* m=0) - :_solid(s), _proxyMesh(m), _helper(0) {} - ~_SolidData(); + :_solid(s), _proxyMesh(m), _done(false),_helper(0) {} + ~_SolidData() { delete _helper; _helper = 0; } void SortOnEdge( const TopoDS_Edge& E, vector< _LayerEdge* >& edges); void Sort2NeiborsOnEdge( vector< _LayerEdge* >& edges ); @@ -888,6 +888,7 @@ namespace VISCOUS_3D const double refSign ); }; struct PyDump; + struct Periodicity; //-------------------------------------------------------------------------------- /*! * \brief Builder of viscous layers @@ -914,10 +915,12 @@ namespace VISCOUS_3D bool findSolidsWithLayers(); bool setBefore( _SolidData& solidBefore, _SolidData& solidAfter ); bool findFacesWithLayers(const bool onlyWith=false); + void findPeriodicFaces(); void getIgnoreFaces(const TopoDS_Shape& solid, const StdMeshers_ViscousLayers* hyp, const TopoDS_Shape& hypShape, set& ignoreFaces); + void makeEdgesOnShape(); bool makeLayer(_SolidData& data); void setShapeData( _EdgesOnShape& eos, SMESH_subMesh* sm, _SolidData& data ); bool setEdgeData( _LayerEdge& edge, _EdgesOnShape& eos, @@ -1000,15 +1003,16 @@ namespace VISCOUS_3D // debug void makeGroupOfLE(); - SMESH_Mesh* _mesh; - SMESH_ComputeErrorPtr _error; + SMESH_Mesh* _mesh; + SMESH_ComputeErrorPtr _error; - vector< _SolidData > _sdVec; - TopTools_IndexedMapOfShape _solids; // to find _SolidData by a solid - TopTools_MapOfShape _shrinkedFaces; + vector< _SolidData > _sdVec; + TopTools_IndexedMapOfShape _solids; // to find _SolidData by a solid + TopTools_MapOfShape _shrunkFaces; + std::unique_ptr _periodicity; - int _tmpFaceID; - PyDump* _pyDump; + int _tmpFaceID; + PyDump* _pyDump; }; //-------------------------------------------------------------------------------- /*! @@ -1212,6 +1216,27 @@ namespace VISCOUS_3D ( dot * dot ) / l1 / l2 >= ( cos * cos )); } + class _Factory + { + ObjectPool< _LayerEdge > _edgePool; + ObjectPool< _Curvature > _curvaturePool; + ObjectPool< _2NearEdges > _nearEdgesPool; + + static _Factory* & me() + { + static _Factory* theFactory = 0; + return theFactory; + } + public: + + _Factory() { me() = this; } + ~_Factory() { me() = 0; } + + static _LayerEdge* NewLayerEdge() { return me()->_edgePool.getNew(); } + static _Curvature * NewCurvature() { return me()->_curvaturePool.getNew(); } + static _2NearEdges* NewNearEdges() { return me()->_nearEdgesPool.getNew(); } + }; + } // namespace VISCOUS_3D @@ -1873,6 +1898,8 @@ SMESH_ComputeErrorPtr _ViscousBuilder::Compute(SMESH_Mesh& theMesh, { _mesh = & theMesh; + _Factory factory; + // check if proxy mesh already computed TopExp_Explorer exp( theShape, TopAbs_SOLID ); if ( !exp.More() ) @@ -1884,20 +1911,30 @@ SMESH_ComputeErrorPtr _ViscousBuilder::Compute(SMESH_Mesh& theMesh, PyDump debugDump( theMesh ); _pyDump = &debugDump; - // TODO: ignore already computed SOLIDs + // TODO: ignore already computed SOLIDs if ( !findSolidsWithLayers()) return _error; if ( !findFacesWithLayers() ) return _error; + // for ( size_t i = 0; i < _sdVec.size(); ++i ) + // { + // if ( ! makeLayer( _sdVec[ i ])) // create _LayerEdge's + // return _error; + // } + + makeEdgesOnShape(); + + findPeriodicFaces(); + for ( size_t i = 0; i < _sdVec.size(); ++i ) { size_t iSD = 0; for ( iSD = 0; iSD < _sdVec.size(); ++iSD ) // find next SOLID to compute if ( _sdVec[iSD]._before.IsEmpty() && !_sdVec[iSD]._solid.IsNull() && - _sdVec[iSD]._n2eMap.empty() ) + !_sdVec[iSD]._done ) break; if ( ! makeLayer(_sdVec[iSD]) ) // create _LayerEdge's @@ -1920,6 +1957,8 @@ SMESH_ComputeErrorPtr _ViscousBuilder::Compute(SMESH_Mesh& theMesh, addBoundaryElements(_sdVec[iSD]); // create quadrangles on prism bare sides + _sdVec[iSD]._done = true; + const TopoDS_Shape& solid = _sdVec[iSD]._solid; for ( iSD = 0; iSD < _sdVec.size(); ++iSD ) _sdVec[iSD]._before.Remove( solid ); @@ -2146,7 +2185,7 @@ bool _ViscousBuilder::findFacesWithLayers(const bool onlyWith) for ( ; exp.More(); exp.Next() ) { const TopoDS_Face& face = TopoDS::Face( exp.Current() ); - const TGeomID faceID = getMeshDS()->ShapeToIndex( face ); + const TGeomID faceID = getMeshDS()->ShapeToIndex( face ); if ( //!sdVec[i]._ignoreFaceIds.count( faceID ) && helper.NbAncestors( face, *_mesh, TopAbs_SOLID ) > 1 && helper.IsReversedSubMesh( face )) @@ -2182,7 +2221,7 @@ bool _ViscousBuilder::findFacesWithLayers(const bool onlyWith) ignore[j] = _sdVec[i]._ignoreFaceIds.count( getMeshDS()->ShapeToIndex( FF[j] )); if ( ignore[0] == ignore[1] ) continue; // nothing interesting - TopoDS_Shape fWOL = FF[ ignore[0] ? 0 : 1 ]; + TopoDS_Shape fWOL = FF[ ignore[0] ? 0 : 1 ]; // FACE w/o layers // add EDGE to maps if ( !fWOL.IsNull()) @@ -2262,7 +2301,7 @@ bool _ViscousBuilder::findFacesWithLayers(const bool onlyWith) } } - // Add to _noShrinkShapes sub-shapes of FACE's that can't be shrinked since + // Add to _noShrinkShapes sub-shapes of FACE's that can't be shrunk since // the algo of the SOLID sharing the FACE does not support it or for other reasons set< string > notSupportAlgos; notSupportAlgos.insert( structAlgoName ); for ( size_t i = 0; i < _sdVec.size(); ++i ) @@ -2487,17 +2526,6 @@ void _ViscousBuilder::getIgnoreFaces(const TopoDS_Shape& solid, bool _ViscousBuilder::makeLayer(_SolidData& data) { - // get all sub-shapes to make layers on - set subIds, faceIds; - subIds = data._noShrinkShapes; - TopExp_Explorer exp( data._solid, TopAbs_FACE ); - for ( ; exp.More(); exp.Next() ) - { - SMESH_subMesh* fSubM = _mesh->GetSubMesh( exp.Current() ); - if ( ! data._ignoreFaceIds.count( fSubM->GetId() )) - faceIds.insert( fSubM->GetId() ); - } - // make a map to find new nodes on sub-shapes shared with other SOLID map< TGeomID, TNode2Edge* >::iterator s2ne; map< TGeomID, TopoDS_Shape >::iterator s2s = data._shrinkShape2Shape.begin(); @@ -2521,6 +2549,8 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) dumpFunction(SMESH_Comment("makeLayers_")<& edgesByGeom = data._edgesOnShape; + data._stepSize = Precision::Infinite(); data._stepSizeNodes[0] = 0; @@ -2531,34 +2561,19 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) vector< const SMDS_MeshNode*> newNodes; // of a mesh face TNode2Edge::iterator n2e2; - // collect _LayerEdge's of shapes they are based on - vector< _EdgesOnShape >& edgesByGeom = data._edgesOnShape; - const int nbShapes = getMeshDS()->MaxShapeIndex(); - edgesByGeom.resize( nbShapes+1 ); - - // set data of _EdgesOnShape's - if ( SMESH_subMesh* sm = _mesh->GetSubMesh( data._solid )) - { - SMESH_subMeshIteratorPtr smIt = sm->getDependsOnIterator(/*includeSelf=*/false); - while ( smIt->more() ) - { - sm = smIt->next(); - if ( sm->GetSubShape().ShapeType() == TopAbs_FACE && - !faceIds.count( sm->GetId() )) - continue; - setShapeData( edgesByGeom[ sm->GetId() ], sm, data ); - } - } // make _LayerEdge's - for ( set::iterator id = faceIds.begin(); id != faceIds.end(); ++id ) + for ( TopExp_Explorer exp( data._solid, TopAbs_FACE ); exp.More(); exp.Next() ) { - const TopoDS_Face& F = TopoDS::Face( getMeshDS()->IndexToShape( *id )); - SMESH_subMesh* sm = _mesh->GetSubMesh( F ); + const TopoDS_Face& F = TopoDS::Face( exp.Current() ); + SMESH_subMesh* sm = _mesh->GetSubMesh( F ); + const TGeomID id = sm->GetId(); + if ( edgesByGeom[ id ]._shape.IsNull() ) + continue; // no layers SMESH_ProxyMesh::SubMesh* proxySub = data._proxyMesh->getFaceSubM( F, /*create=*/true); SMESHDS_SubMesh* smDS = sm->GetSubMeshDS(); - if ( !smDS ) return error(SMESH_Comment("Not meshed face ") << *id, data._index ); + if ( !smDS ) return error(SMESH_Comment("Not meshed face ") << id, data._index ); SMDS_ElemIteratorPtr eIt = smDS->GetElements(); while ( eIt->more() ) @@ -2596,7 +2611,7 @@ bool _ViscousBuilder::makeLayer(_SolidData& data) if ( !(*n2e).second ) { // add a _LayerEdge - _LayerEdge* edge = new _LayerEdge(); + _LayerEdge* edge = _Factory::NewLayerEdge(); edge->_nodes.push_back( n ); n2e->second = edge; edgesByGeom[ shapeID ]._edges.push_back( edge ); @@ -3249,6 +3264,39 @@ bool _ViscousBuilder::findShapesToSmooth( _SolidData& data ) return ok; } +//================================================================================ +/*! + * \brief Set up _SolidData::_edgesOnShape + */ +//================================================================================ + +void _ViscousBuilder::makeEdgesOnShape() +{ + const int nbShapes = getMeshDS()->MaxShapeIndex(); + + for ( size_t i = 0; i < _sdVec.size(); ++i ) + { + _SolidData& data = _sdVec[ i ]; + vector< _EdgesOnShape >& edgesByGeom = data._edgesOnShape; + edgesByGeom.resize( nbShapes+1 ); + + // set data of _EdgesOnShape's + if ( SMESH_subMesh* sm = _mesh->GetSubMesh( data._solid )) + { + SMESH_subMeshIteratorPtr smIt = sm->getDependsOnIterator(/*includeSelf=*/false); + while ( smIt->more() ) + { + sm = smIt->next(); + if ( sm->GetSubShape().ShapeType() == TopAbs_FACE && + data._ignoreFaceIds.count( sm->GetId() )) + continue; + + setShapeData( edgesByGeom[ sm->GetId() ], sm, data ); + } + } + } +} + //================================================================================ /*! * \brief initialize data of _EdgesOnShape @@ -3383,6 +3431,16 @@ bool _EdgesOnShape::GetNormal( const SMDS_MeshElement* face, gp_Vec& norm ) return ok; } +//================================================================================ +/*! + * \brief EdgesOnShape destructor + */ +//================================================================================ + +_EdgesOnShape::~_EdgesOnShape() +{ + delete _edgeSmoother; +} //================================================================================ /*! @@ -3397,12 +3455,13 @@ bool _ViscousBuilder::setEdgeData(_LayerEdge& edge, { const SMDS_MeshNode* node = edge._nodes[0]; // source node - edge._len = 0; - edge._maxLen = Precision::Infinite(); - edge._minAngle = 0; - edge._2neibors = 0; - edge._curvature = 0; - edge._flags = 0; + edge._len = 0; + edge._maxLen = Precision::Infinite(); + edge._minAngle = 0; + edge._2neibors = 0; + edge._curvature = 0; + edge._flags = 0; + edge._smooFunction = 0; // -------------------------- // Compute _normal and _cosin @@ -3675,7 +3734,7 @@ bool _ViscousBuilder::setEdgeData(_LayerEdge& edge, if ( eos.ShapeType() == TopAbs_EDGE /*|| ( onShrinkShape && posType == SMDS_TOP_VERTEX && fabs( edge._cosin ) < 1e-10 )*/) { - edge._2neibors = new _2NearEdges; + edge._2neibors = _Factory::NewNearEdges(); // target nodes instead of source ones will be set later } @@ -4134,6 +4193,34 @@ bool _ViscousBuilder::findNeiborsOnEdge(const _LayerEdge* edge, return true; } +//================================================================================ +/*! + * \brief Create _Curvature + */ +//================================================================================ + +_Curvature* _Curvature::New( double avgNormProj, double avgDist ) +{ + // double _r; // radius + // double _k; // factor to correct node smoothed position + // double _h2lenRatio; // avgNormProj / (2*avgDist) + // gp_Pnt2d _uv; // UV used in putOnOffsetSurface() + + _Curvature* c = 0; + if ( fabs( avgNormProj / avgDist ) > 1./200 ) + { + c = _Factory::NewCurvature(); + c->_r = avgDist * avgDist / avgNormProj; + c->_k = avgDist * avgDist / c->_r / c->_r; + //c->_k = avgNormProj / c->_r; + c->_k *= ( c->_r < 0 ? 1/1.1 : 1.1 ); // not to be too restrictive + c->_h2lenRatio = avgNormProj / ( avgDist + avgDist ); + + c->_uv.SetCoord( 0., 0. ); + } + return c; +} + //================================================================================ /*! * \brief Set _curvature and _2neibors->_plnNorm by 2 neighbor nodes residing the same EDGE @@ -4161,7 +4248,6 @@ void _LayerEdge::SetDataByNeighbors( const SMDS_MeshNode* n1, _2neibors->_wgt[1] = 1 - vec2.Modulus() / sumLen; double avgNormProj = 0.5 * ( _normal * vec1 + _normal * vec2 ); double avgLen = 0.5 * ( vec1.Modulus() + vec2.Modulus() ); - if ( _curvature ) delete _curvature; _curvature = _Curvature::New( avgNormProj, avgLen ); // if ( _curvature ) // debugMsg( _nodes[0]->GetID() @@ -4205,8 +4291,11 @@ gp_XYZ _LayerEdge::Copy( _LayerEdge& other, _lenFactor = other._lenFactor; _cosin = other._cosin; _2neibors = other._2neibors; - _curvature = 0; std::swap( _curvature, other._curvature ); - _2neibors = 0; std::swap( _2neibors, other._2neibors ); + _curvature = other._curvature; + _2neibors = other._2neibors; + _maxLen = Precision::Infinite();//other._maxLen; + _flags = 0; + _smooFunction = 0; gp_XYZ lastPos( 0,0,0 ); if ( eos.SWOLType() == TopAbs_EDGE ) @@ -6927,7 +7016,7 @@ void _ViscousBuilder::findEdgesToUpdateNormalNearConvexFace( _ConvexFace & if ( dist < 0.95 * ledge->_maxLen ) { ledge->Set( _LayerEdge::UPD_NORMAL_CONV ); - if ( !ledge->_curvature ) ledge->_curvature = new _Curvature; + if ( !ledge->_curvature ) ledge->_curvature = _Factory::NewCurvature(); ledge->_curvature->_uv.SetCoord( uv.X(), uv.Y() ); edgesToUpdateFound = true; } @@ -8269,7 +8358,7 @@ void _LayerEdge::MoveNearConcaVer( const _EdgesOnShape* eov, if ( !edgeF->_curvature ) if (( fPos = edgeF->_nodes[0]->GetPosition() )) { - edgeF->_curvature = new _Curvature; + edgeF->_curvature = _Factory::NewCurvature(); edgeF->_curvature->_r = 0; edgeF->_curvature->_k = 0; edgeF->_curvature->_h2lenRatio = 0; @@ -10279,6 +10368,440 @@ bool _ViscousBuilder::refine(_SolidData& data) return true; } +namespace VISCOUS_3D +{ + struct ShrinkFace; + //-------------------------------------------------------------------------------- + /*! + * \brief Pair of periodic FACEs + */ + struct PeriodicFaces + { + typedef StdMeshers_ProjectionUtils::TrsfFinder3D Trsf; + + ShrinkFace* _shriFace[2]; + TNodeNodeMap _nnMap; + Trsf _trsf; + + PeriodicFaces( ShrinkFace* sf1, ShrinkFace* sf2 ): _shriFace{ sf1, sf2 } {} + bool IncludeShrunk( const TopoDS_Face& face, const TopTools_MapOfShape& shrunkFaces ) const; + bool MoveNodes( const TopoDS_Face& tgtFace ); + }; + + //-------------------------------------------------------------------------------- + /*! + * \brief Shrink FACE data used to find periodic FACEs + */ + struct ShrinkFace + { + // ................................................................................ + struct BndPart //!< part of FACE boundary, either shrink or no-shrink + { + bool _isShrink, _isReverse; + int _nbSegments; + AverageHyp* _hyp; + std::vector< SMESH_NodeXYZ > _nodes; + TopAbs_ShapeEnum _vertSWOLType[2]; // shrink part includes VERTEXes + AverageHyp* _vertHyp[2]; + + BndPart(): + _isShrink(0), _isReverse(0), _nbSegments(0), _hyp(0), + _vertSWOLType{ TopAbs_WIRE, TopAbs_WIRE }, _vertHyp{ 0, 0 } + {} + + bool operator==( const BndPart& other ) const + { + return ( _isShrink == other._isShrink && + _nbSegments == other._nbSegments && + _nodes.size() == other._nodes.size() && + vertSWOLType1() == other.vertSWOLType1() && + vertSWOLType2() == other.vertSWOLType2() && + (( !_isShrink ) || + ( *_hyp == *other._hyp && + vertHyp1() == other.vertHyp1() && + vertHyp2() == other.vertHyp2() )) + ); + } + bool CanAppend( const BndPart& other ) + { + return ( _isShrink == other._isShrink && + (( !_isShrink ) || + ( *_hyp == *other._hyp && + *_hyp == vertHyp2() && + vertHyp2() == other.vertHyp1() )) + ); + } + void Append( const BndPart& other ) + { + _nbSegments += other._nbSegments; + bool hasCommonNode = ( _nodes.back()->GetID() == other._nodes.front()->GetID() ); + _nodes.insert( _nodes.end(), other._nodes.begin() + hasCommonNode, other._nodes.end() ); + _vertSWOLType[1] = other._vertSWOLType[1]; + if ( _isShrink ) + _vertHyp[1] = other._vertHyp[1]; + } + const SMDS_MeshNode* Node(size_t i) const + { + return _nodes[ _isReverse ? ( _nodes.size() - 1 - i ) : i ]._node; + } + void Reverse() { _isReverse = !_isReverse; } + const TopAbs_ShapeEnum& vertSWOLType1() const { return _vertSWOLType[ _isReverse ]; } + const TopAbs_ShapeEnum& vertSWOLType2() const { return _vertSWOLType[ !_isReverse ]; } + const AverageHyp& vertHyp1() const { return *(_vertHyp[ _isReverse ]); } + const AverageHyp& vertHyp2() const { return *(_vertHyp[ !_isReverse ]); } + }; + // ................................................................................ + + SMESH_subMesh* _subMesh; + _SolidData* _data1; + _SolidData* _data2; + //bool _isPeriodic; + + std::list< BndPart > _boundary; + int _boundarySize, _nbBoundaryParts; + + void Init( SMESH_subMesh* sm, _SolidData* sd1, _SolidData* sd2 ) + { + _subMesh = sm; _data1 = sd1; _data2 = sd2; //_isPeriodic = false; + } + bool IsSame( const TopoDS_Face& face ) const + { + return _subMesh->GetSubShape().IsSame( face ); + } + bool IsShrunk( const TopTools_MapOfShape& shrunkFaces ) const + { + return shrunkFaces.Contains( _subMesh->GetSubShape() ); + } + + //================================================================================ + /*! + * Check if meshes on two FACEs are equal + */ + bool IsPeriodic( ShrinkFace& other, PeriodicFaces& periodic ) + { + if ( !IsSameNbElements( other )) + return false; + + this->SetBoundary(); + other.SetBoundary(); + if ( this->_boundarySize != other._boundarySize || + this->_nbBoundaryParts != other._nbBoundaryParts ) + return false; + + for ( int isReverse = 0; isReverse < 2; ++isReverse ) + { + if ( isReverse ) + Reverse( _boundary ); + + // check boundaries + bool equalBoundary = false; + for ( int iP = 0; iP < _nbBoundaryParts && !equalBoundary; ++iP ) + { + if ( ! ( equalBoundary = ( this->_boundary == other._boundary ))) + // set first part at end + _boundary.splice( _boundary.end(), _boundary, _boundary.begin() ); + } + if ( !equalBoundary ) + continue; + + // check connectivity + std::set elemsThis, elemsOther; + this->GetElements( elemsThis ); + other.GetElements( elemsOther ); + SMESH_MeshEditor::Sew_Error err = + SMESH_MeshEditor::FindMatchingNodes( elemsThis, elemsOther, + this->_boundary.front().Node(0), + other._boundary.front().Node(0), + this->_boundary.front().Node(1), + other._boundary.front().Node(1), + periodic._nnMap ); + if ( err != SMESH_MeshEditor::SEW_OK ) + continue; + + // check node positions + std::vector< gp_XYZ > srcPnts, tgtPnts; + this->GetBoundaryPoints( srcPnts ); + other.GetBoundaryPoints( tgtPnts ); + if ( !periodic._trsf.Solve( srcPnts, tgtPnts )) { + continue; + } + double tol = std::numeric_limits::max(); + for ( size_t i = 1; i < srcPnts.size(); ++i ) { + tol = Min( tol, ( srcPnts[i-1] - srcPnts[i] ).SquareModulus() ); + } + tol = 0.01 * Sqrt( tol ); + bool nodeCoincide = true; + TNodeNodeMap::iterator n2n = periodic._nnMap.begin(); + for ( ; n2n != periodic._nnMap.end() && nodeCoincide; ++n2n ) + { + SMESH_NodeXYZ nSrc = n2n->first; + SMESH_NodeXYZ nTgt = n2n->second; + gp_XYZ pTgt = periodic._trsf.Transform( nSrc ); + nodeCoincide = (( pTgt - nTgt ).SquareModulus() < tol ); + } + if ( nodeCoincide ) + return true; + } + return false; + } + + bool IsSameNbElements( ShrinkFace& other ) // check number of mesh faces + { + SMESHDS_SubMesh* sm1 = this->_subMesh->GetSubMeshDS(); + SMESHDS_SubMesh* sm2 = other._subMesh->GetSubMeshDS(); + return ( sm1->NbElements() == sm2->NbElements() && + sm1->NbNodes() == sm2->NbNodes() ); + } + + void Reverse( std::list< BndPart >& boundary ) + { + boundary.reverse(); + for ( std::list< BndPart >::iterator part = boundary.begin(); part != boundary.end(); ++part ) + part->Reverse(); + } + + void SetBoundary() + { + if ( !_boundary.empty() ) + return; + + TopoDS_Face F = TopoDS::Face( _subMesh->GetSubShape() ); + if ( F.Orientation() >= TopAbs_INTERNAL ) F.Orientation( TopAbs_FORWARD ); + std::list< TopoDS_Edge > edges; + std::list< int > nbEdgesInWire; + /*int nbWires =*/ SMESH_Block::GetOrderedEdges (F, edges, nbEdgesInWire); + + // std::list< TopoDS_Edge >::iterator edgesEnd = edges.end(); + // if ( nbWires > 1 ) { + // edgesEnd = edges.begin(); + // std::advance( edgesEnd, nbEdgesInWire.front() ); + // } + StdMeshers_FaceSide fSide( F, edges, _subMesh->GetFather(), + /*fwd=*/true, /*skipMedium=*/true ); + _boundarySize = fSide.NbSegments(); + + //TopoDS_Vertex vv[2]; + //std::list< TopoDS_Edge >::iterator edgeIt = edges.begin(); + for ( int iE = 0; iE < nbEdgesInWire.front(); ++iE ) + { + BndPart bndPart; + _EdgesOnShape* eos = _data1->GetShapeEdges( fSide.EdgeID( iE )); + + bndPart._isShrink = ( eos->SWOLType() == TopAbs_FACE ); + if ( bndPart._isShrink ) + if (( _data1->_noShrinkShapes.count( eos->_shapeID )) || + ( _data2 && _data2->_noShrinkShapes.count( eos->_shapeID ))) + bndPart._isShrink = false; + + if ( bndPart._isShrink ) + { + bndPart._hyp = & eos->_hyp; + _EdgesOnShape* eov[2] = { _data1->GetShapeEdges( fSide.FirstVertex( iE )), + _data1->GetShapeEdges( fSide.LastVertex ( iE )) }; + for ( int iV = 0; iV < 2; ++iV ) + { + bndPart._vertHyp [iV] = & eov[iV]->_hyp; + bndPart._vertSWOLType[iV] = eov[iV]->SWOLType(); + if ( _data1->_noShrinkShapes.count( eov[iV]->_shapeID )) + bndPart._vertSWOLType[iV] = TopAbs_SHAPE; + if ( _data2 && bndPart._vertSWOLType[iV] != TopAbs_SHAPE ) + { + eov[iV] = _data2->GetShapeEdges( iV ? fSide.LastVertex(iE) : fSide.FirstVertex(iE )); + if ( _data2->_noShrinkShapes.count( eov[iV]->_shapeID )) + bndPart._vertSWOLType[iV] = TopAbs_SHAPE; + else if ( eov[iV]->SWOLType() > bndPart._vertSWOLType[iV] ) + bndPart._vertSWOLType[iV] = eov[iV]->SWOLType(); + } + } + } + std::vector nodes = fSide.GetOrderedNodes( iE ); + bndPart._nodes.assign( nodes.begin(), nodes.end() ); + bndPart._nbSegments = bndPart._nodes.size() - 1; + + if ( _boundary.empty() || ! _boundary.back().CanAppend( bndPart )) + _boundary.push_back( bndPart ); + else + _boundary.back().Append( bndPart ); + } + + _nbBoundaryParts = _boundary.size(); + if ( _nbBoundaryParts > 1 && _boundary.front()._isShrink == _boundary.back()._isShrink ) + { + _boundary.back().Append( _boundary.front() ); + _boundary.pop_front(); + --_nbBoundaryParts; + } + } + + void GetElements( std::set& theElems) + { + if ( SMESHDS_SubMesh* sm = _subMesh->GetSubMeshDS() ) + for ( SMDS_ElemIteratorPtr fIt = sm->GetElements(); fIt->more(); ) + theElems.insert( theElems.end(), fIt->next() ); + + return ; + } + + void GetBoundaryPoints( std::vector< gp_XYZ >& points ) + { + points.reserve( _boundarySize ); + size_t nb = _boundary.rbegin()->_nodes.size(); + int lastID = _boundary.rbegin()->Node( nb - 1 )->GetID(); + std::list< BndPart >::const_iterator part = _boundary.begin(); + for ( ; part != _boundary.end(); ++part ) + { + size_t nb = part->_nodes.size(); + size_t iF = 0; + size_t iR = nb - 1; + size_t* i = part->_isReverse ? &iR : &iF; + if ( part->_nodes[ *i ]->GetID() == lastID ) + ++iF, --iR; + for ( ; iF < nb; ++iF, --iR ) + points.push_back( part->_nodes[ *i ]); + --iF, ++iR; + lastID = part->_nodes[ *i ]->GetID(); + } + } + }; // struct ShrinkFace + + //-------------------------------------------------------------------------------- + /*! + * \brief Periodic FACEs + */ + struct Periodicity + { + std::vector< ShrinkFace > _shrinkFaces; + std::vector< PeriodicFaces > _periodicFaces; + + PeriodicFaces* GetPeriodic( const TopoDS_Face& face, const TopTools_MapOfShape& shrunkFaces ) + { + for ( size_t i = 0; i < _periodicFaces.size(); ++i ) + if ( _periodicFaces[ i ].IncludeShrunk( face, shrunkFaces )) + return & _periodicFaces[ i ]; + return 0; + } + }; + + //================================================================================ + /*! + * Check if a pair includes the given FACE and the other FACE is already shrunk + */ + bool PeriodicFaces::IncludeShrunk( const TopoDS_Face& face, + const TopTools_MapOfShape& shrunkFaces ) const + { + return (( _shriFace[0]->IsSame( face ) && _shriFace[1]->IsShrunk( shrunkFaces )) || + ( _shriFace[1]->IsSame( face ) && _shriFace[0]->IsShrunk( shrunkFaces ))); + } + + //================================================================================ + /*! + * Make equal meshes on periodic faces by moving corresponding nodes + */ + bool PeriodicFaces::MoveNodes( const TopoDS_Face& tgtFace ) + { + int iTgt = _shriFace[1]->IsSame( tgtFace ); + int iSrc = 1 - iTgt; + + _SolidData* dataSrc = _shriFace[iSrc]->_data1; + _SolidData* dataTgt = _shriFace[iTgt]->_data1; + + Trsf * trsf = & _trsf, trsfInverse; + if ( iSrc != 0 ) + { + trsfInverse = _trsf; + trsfInverse.Invert(); + trsf = &trsfInverse; + } + SMESHDS_Mesh* meshDS = dataSrc->GetHelper().GetMeshDS(); + + TNode2Edge::iterator n2e; + TNodeNodeMap::iterator n2n = _nnMap.begin(); + for ( ; n2n != _nnMap.end(); ++n2n ) + { + const SMDS_MeshNode* const* nn = & n2n->first; + const SMDS_MeshNode* nSrc = nn[ iSrc ]; + const SMDS_MeshNode* nTgt = nn[ iTgt ]; + + if (( nSrc->GetPosition()->GetDim() == 2 ) || + (( n2e = dataSrc->_n2eMap.find( nSrc )) == dataSrc->_n2eMap.end() )) + { + SMESH_NodeXYZ pSrc = nSrc; + gp_XYZ pTgt = trsf->Transform( pSrc ); + meshDS->MoveNode( nTgt, pTgt.X(), pTgt.Y(), pTgt.Z() ); + } + else + { + _LayerEdge* leSrc = n2e->second; + n2e = dataTgt->_n2eMap.find( nTgt ); + if ( n2e == dataTgt->_n2eMap.end() ) + break; + _LayerEdge* leTgt = n2e->second; + if ( leSrc->_nodes.size() != leTgt->_nodes.size() ) + break; + for ( size_t iN = 1; iN < leSrc->_nodes.size(); ++iN ) + { + SMESH_NodeXYZ pSrc = leSrc->_nodes[ iN ]; + gp_XYZ pTgt = trsf->Transform( pSrc ); + meshDS->MoveNode( leTgt->_nodes[ iN ], pTgt.X(), pTgt.Y(), pTgt.Z() ); + } + } + } + bool done = ( n2n == _nnMap.end() ); + // cout << "MMMMMMMOOOOOOOOOOVVVVVVVVVVVEEEEEEEE " + // << _shriFace[iSrc]->_subMesh->GetId() << " -> " + // << _shriFace[iTgt]->_subMesh->GetId() << " -- " + // << ( done ? "DONE" : "FAIL") << endl; + + return done; + } +} // namespace VISCOUS_3D; Periodicity part + + +//================================================================================ +/*! + * \brief Find FACEs to shrink, that are equally meshed before shrink (i.e. periodic) + * and should remain equal after shrink + */ +//================================================================================ + +void _ViscousBuilder::findPeriodicFaces() +{ + // make map of (ids of FACEs to shrink mesh on) to (list of _SolidData containing + // _LayerEdge's inflated along FACE or EDGE) + std::map< TGeomID, std::list< _SolidData* > > id2sdMap; + for ( size_t i = 0 ; i < _sdVec.size(); ++i ) + { + _SolidData& data = _sdVec[i]; + std::map< TGeomID, TopoDS_Shape >::iterator s2s = data._shrinkShape2Shape.begin(); + for (; s2s != data._shrinkShape2Shape.end(); ++s2s ) + if ( s2s->second.ShapeType() == TopAbs_FACE ) + id2sdMap[ getMeshDS()->ShapeToIndex( s2s->second )].push_back( &data ); + } + + _periodicity.reset( new Periodicity ); + _periodicity->_shrinkFaces.resize( id2sdMap.size() ); + + std::map< TGeomID, std::list< _SolidData* > >::iterator id2sdIt = id2sdMap.begin(); + for ( size_t i = 0; i < id2sdMap.size(); ++i, ++id2sdIt ) + { + _SolidData* sd1 = id2sdIt->second.front(); + _SolidData* sd2 = id2sdIt->second.back(); + _periodicity->_shrinkFaces[ i ].Init( _mesh->GetSubMeshContaining( id2sdIt->first ), sd1, sd2 ); + } + + for ( size_t i1 = 0; i1 < _periodicity->_shrinkFaces.size(); ++i1 ) + for ( size_t i2 = i1 + 1; i2 < _periodicity->_shrinkFaces.size(); ++i2 ) + { + PeriodicFaces pf( & _periodicity->_shrinkFaces[ i1 ], + & _periodicity->_shrinkFaces[ i2 ]); + if ( pf._shriFace[0]->IsPeriodic( *pf._shriFace[1], pf )) + { + _periodicity->_periodicFaces.push_back( pf ); + } + } + return; +} + //================================================================================ /*! * \brief Shrink 2D mesh on faces to let space for inflated layers @@ -10295,11 +10818,11 @@ bool _ViscousBuilder::shrink(_SolidData& theData) _SolidData& data = _sdVec[i]; map< TGeomID, TopoDS_Shape >::iterator s2s = data._shrinkShape2Shape.begin(); for (; s2s != data._shrinkShape2Shape.end(); ++s2s ) - if ( s2s->second.ShapeType() == TopAbs_FACE && !_shrinkedFaces.Contains( s2s->second )) + if ( s2s->second.ShapeType() == TopAbs_FACE && !_shrunkFaces.Contains( s2s->second )) { f2sdMap[ getMeshDS()->ShapeToIndex( s2s->second )].push_back( &data ); - // Put mesh faces on the shrinked FACE to the proxy sub-mesh to avoid + // Put mesh faces on the shrunk FACE to the proxy sub-mesh to avoid // usage of mesh faces made in addBoundaryElements() by the 3D algo or // by StdMeshers_QuadToTriaAdaptor if ( SMESHDS_SubMesh* smDS = getMeshDS()->MeshElements( s2s->second )) @@ -10358,15 +10881,24 @@ bool _ViscousBuilder::shrink(_SolidData& theData) Handle(Geom_Surface) surface = BRep_Tool::Surface( F ); - _shrinkedFaces.Add( F ); + _shrunkFaces.Add( F ); helper.SetSubShape( F ); + // ============================== + // Use periodicity to move nodes + // ============================== + + PeriodicFaces* periodic = _periodicity->GetPeriodic( F, _shrunkFaces ); + bool movedByPeriod = ( periodic && periodic->MoveNodes( F )); + // =========================== // Prepare data for shrinking // =========================== // Collect nodes to smooth (they are marked at the beginning of this method) vector < const SMDS_MeshNode* > smoothNodes; + + if ( !movedByPeriod ) { SMDS_NodeIteratorPtr nIt = smDS->GetNodes(); while ( nIt->more() ) @@ -10412,11 +10944,12 @@ bool _ViscousBuilder::shrink(_SolidData& theData) } subEOS.push_back( eos ); - for ( size_t i = 0; i < eos->_edges.size(); ++i ) - { - lEdges.push_back( eos->_edges[ i ] ); - prepareEdgeToShrink( *eos->_edges[ i ], *eos, helper, smDS ); - } + if ( !movedByPeriod ) + for ( size_t i = 0; i < eos->_edges.size(); ++i ) + { + lEdges.push_back( eos->_edges[ i ] ); + prepareEdgeToShrink( *eos->_edges[ i ], *eos, helper, smDS ); + } } } @@ -10488,13 +11021,16 @@ bool _ViscousBuilder::shrink(_SolidData& theData) if ( eos.SWOLType() == TopAbs_EDGE ) { SMESH_subMesh* edgeSM = _mesh->GetSubMesh( eos._sWOL ); - _Shrinker1D& shrinker = e2shrMap[ edgeSM->GetId() ]; - eShri1D.insert( & shrinker ); - shrinker.AddEdge( eos._edges[0], eos, helper ); VISCOUS_3D::ToClearSubWithMain( edgeSM, data._solid ); - // restore params of nodes on EDGE if the EDGE has been already - // shrinked while shrinking other FACE - shrinker.RestoreParams(); + if ( !movedByPeriod ) + { + _Shrinker1D& shrinker = e2shrMap[ edgeSM->GetId() ]; + eShri1D.insert( & shrinker ); + shrinker.AddEdge( eos._edges[0], eos, helper ); + // restore params of nodes on EDGE if the EDGE has been already + // shrunk while shrinking other FACE + shrinker.RestoreParams(); + } } for ( size_t i = 0; i < eos._edges.size(); ++i ) { @@ -10509,7 +11045,7 @@ bool _ViscousBuilder::shrink(_SolidData& theData) } bool toFixTria = false; // to improve quality of trias by diagonal swap - if ( isConcaveFace ) + if ( isConcaveFace && !movedByPeriod ) { const bool hasTria = _mesh->NbTriangles(), hasQuad = _mesh->NbQuadrangles(); if ( hasTria != hasQuad ) { @@ -10528,24 +11064,24 @@ bool _ViscousBuilder::shrink(_SolidData& theData) // Perform shrinking // ================== - bool shrinked = true; + bool shrunk = !movedByPeriod; int nbBad, shriStep=0, smooStep=0; _SmoothNode::SmoothType smoothType = isConcaveFace ? _SmoothNode::ANGULAR : _SmoothNode::LAPLACIAN; SMESH_Comment errMsg; - while ( shrinked ) + while ( shrunk ) { shriStep++; // Move boundary nodes (actually just set new UV) // ----------------------------------------------- dumpFunction(SMESH_Comment("moveBoundaryOnF")<first<<"_st"<SetNewLength2d( surface, F, eos, helper ); + shrunk |= eos._edges[i]->SetNewLength2d( surface, F, eos, helper ); } } dumpFunctionEnd(); @@ -10636,7 +11172,7 @@ bool _ViscousBuilder::shrink(_SolidData& theData) // } // } - } // while ( shrinked ) + } // while ( shrunk ) if ( !errMsg.empty() ) // Try to re-compute the shrink FACE { @@ -10800,7 +11336,7 @@ bool _ViscousBuilder::shrink(_SolidData& theData) return error( errMsg ); } // end of re-meshing in case of failed smoothing - else + else if ( !movedByPeriod ) { // No wrongly shaped faces remain; final smooth. Set node XYZ. bool isStructuredFixed = false; @@ -10844,7 +11380,7 @@ bool _ViscousBuilder::shrink(_SolidData& theData) } // loop on FACES to shrink mesh on - // Replace source nodes by target nodes in shrinked mesh edges + // Replace source nodes by target nodes in shrunk mesh edges map< int, _Shrinker1D >::iterator e2shr = e2shrMap.begin(); for ( ; e2shr != e2shrMap.end(); ++e2shr ) @@ -11408,34 +11944,6 @@ gp_XY _SmoothNode::computeAngularPos(vector& uv, return newPos; } -//================================================================================ -/*! - * \brief Delete _SolidData - */ -//================================================================================ - -_SolidData::~_SolidData() -{ - TNode2Edge::iterator n2e = _n2eMap.begin(); - for ( ; n2e != _n2eMap.end(); ++n2e ) - { - _LayerEdge* & e = n2e->second; - if ( e ) - { - delete e->_curvature; - if ( e->_2neibors ) - delete e->_2neibors->_plnNorm; - delete e->_2neibors; - } - delete e; - e = 0; - } - _n2eMap.clear(); - - delete _helper; - _helper = 0; -} - //================================================================================ /*! * \brief Keep a _LayerEdge inflated along the EDGE @@ -11601,7 +12109,7 @@ void _Shrinker1D::RestoreParams() //================================================================================ /*! - * \brief Replace source nodes by target nodes in shrinked mesh edges + * \brief Replace source nodes by target nodes in shrunk mesh edges */ //================================================================================ From eb32ac37f34e97d1da90cd63579efa6590ecf300 Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 24 Oct 2019 16:34:16 +0300 Subject: [PATCH 28/60] #17351 [CEA] Mesh with Polyhedron --- .../gui/SMESH/input/basic_meshing_algos.rst | 1 + idl/SMESH_BasicHypothesis.idl | 7 + resources/StdMeshers.xml.in | 12 + src/SMESH_SWIG/StdMeshersBuilder.py | 43 ++ src/StdMeshers/CMakeLists.txt | 2 + .../StdMeshers_PolygonPerFace_2D.cxx | 4 +- .../StdMeshers_PolyhedronPerSolid_3D.cxx | 618 ++++++++++++++++++ .../StdMeshers_PolyhedronPerSolid_3D.hxx | 58 ++ src/StdMeshers_I/CMakeLists.txt | 2 + .../StdMeshers_PolyhedronPerSolid_3D_i.cxx | 57 ++ .../StdMeshers_PolyhedronPerSolid_3D_i.hxx | 53 ++ src/StdMeshers_I/StdMeshers_i.cxx | 3 + 12 files changed, 858 insertions(+), 2 deletions(-) create mode 100644 src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.cxx create mode 100644 src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.hxx create mode 100644 src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.cxx create mode 100644 src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.hxx diff --git a/doc/salome/gui/SMESH/input/basic_meshing_algos.rst b/doc/salome/gui/SMESH/input/basic_meshing_algos.rst index 692232fd1..d636b15f9 100644 --- a/doc/salome/gui/SMESH/input/basic_meshing_algos.rst +++ b/doc/salome/gui/SMESH/input/basic_meshing_algos.rst @@ -53,6 +53,7 @@ An algorithm represents either an implementation of a certain meshing technique * :ref:`Extrusion 3D ` - for meshing prismatic 3D shapes with hexahedra and prisms. * :ref:`Quadrangle: Medial Axis Projection ` - for quadrangle meshing of faces with sinuous borders and rings. * **Polygon per Face** meshing algorithm - generates one mesh face (either a triangle, a quadrangle or a polygon) per a geometrical face using all nodes from the face boundary. + * **Polyhedron per Solid** meshing algorithm - generates one mesh volume (of a classical type or a polyhedron) per a geometrical solid using all faces of the solid boundary. It does not require that 2D mesh is generated on geometrical faces. It creates one mesh edge per geometrical edges and applies **Polygon per Face** to faces if they are not meshed by optional algorithms of lower dimensions. * :ref:`Projection algorithms ` - for meshing by projection of another mesh. * :ref:`Import algorithms ` - for meshing by importing elements from another mesh. * :ref:`Radial Prism ` - for meshing 3D geometrical objects with cavities with hexahedra and prisms. diff --git a/idl/SMESH_BasicHypothesis.idl b/idl/SMESH_BasicHypothesis.idl index a4bb746a6..1f9439f01 100644 --- a/idl/SMESH_BasicHypothesis.idl +++ b/idl/SMESH_BasicHypothesis.idl @@ -1114,6 +1114,13 @@ module StdMeshers { }; + /*! + * StdMeshers_PolyhedronPerSolid_3D: interface of "Polyhedron Per Solid" 3D algorithm + */ + interface StdMeshers_PolyhedronPerSolid_3D : SMESH::SMESH_3D_Algo + { + }; + /*! * StdMeshers_Hexa_3D: interface of "Hexahedron (i,j,k)" algorithm */ diff --git a/resources/StdMeshers.xml.in b/resources/StdMeshers.xml.in index 3126c2318..5b7f60e4f 100644 --- a/resources/StdMeshers.xml.in +++ b/resources/StdMeshers.xml.in @@ -371,6 +371,18 @@ + + + PolyhedronPerSolid_3D=Polyhedron() + ViscousLayers=ViscousLayers(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetIgnoreEdges()) + + + #include +#include #include -#include using namespace std; //======================================================================= //function : StdMeshers_PolygonPerFace_2D -//purpose : +//purpose : //======================================================================= StdMeshers_PolygonPerFace_2D::StdMeshers_PolygonPerFace_2D(int hypId, diff --git a/src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.cxx b/src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.cxx new file mode 100644 index 000000000..6695eea7b --- /dev/null +++ b/src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.cxx @@ -0,0 +1,618 @@ +// 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 +// +// 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, 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 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : StdMeshers_PolyhedronPerSolid_3D.cxx +// Module : SMESH +// Created : Fri Oct 20 11:37:07 2006 +// Author : Edward AGAPOV (eap) +// +#include "StdMeshers_PolyhedronPerSolid_3D.hxx" + +#include "SMESHDS_Mesh.hxx" +#include "SMESH_ControlsDef.hxx" +#include "SMESH_Gen.hxx" +#include "SMESH_Mesh.hxx" +#include "SMESH_MeshAlgos.hxx" +#include "SMESH_MeshEditor.hxx" +#include "SMESH_MesherHelper.hxx" +#include "SMESH_ProxyMesh.hxx" +#include "SMESH_subMesh.hxx" +#include "StdMeshers_PolygonPerFace_2D.hxx" +#include "StdMeshers_Regular_1D.hxx" +#include "StdMeshers_ViscousLayers.hxx" + +#include + +#include +#include + +namespace +{ + struct _EdgeMesher : public StdMeshers_Regular_1D + { + _EdgeMesher( int hypId, SMESH_Gen* gen ) + : StdMeshers_Regular_1D( hypId, gen ) + { + _hypType = NB_SEGMENTS; + _ivalue[ NB_SEGMENTS_IND ] = 1; + } + }; + + //======================================================================= + //function : addHexa + //purpose : + //======================================================================= + + const SMDS_MeshElement* addHexa( std::vector< const SMDS_MeshElement* >& faces, + const std::vector< int > & quantities, + SMESH_MesherHelper & helper ) + { + const SMDS_MeshElement* newHexa = 0; + + // check nb of nodes in faces + for ( size_t i = 0; i < quantities.size(); ++i ) + if ( quantities[ i ] != 4 ) + return newHexa; + + // look for a top face + const SMDS_MeshElement* topFace = 0; + const SMDS_MeshElement* botFace = faces[0]; + std::vector< const SMDS_MeshNode* > nodes( 16 ); // last 8 is a working buffer + nodes.assign( botFace->begin_nodes(), botFace->end_nodes() ); + for ( size_t iF = 1; iF < faces.size() && !topFace; ++iF ) + { + bool hasCommonNode = false; + for ( int iN = 0; iN < quantities[ 0 ] && !hasCommonNode; ++iN ) + hasCommonNode = ( faces[ iF ]->GetNodeIndex( nodes[ iN ]) >= 0 ); + + if ( !hasCommonNode ) + topFace = faces[ iF ]; + } + + nodes.resize( 8 ); // set top nodes after hexa nodes - [8-11] + nodes.insert( nodes.end(), topFace->begin_nodes(), topFace->end_nodes() ); + nodes.resize( 12 ); + nodes.insert( nodes.end(), nodes.begin() + 8, nodes.begin() + 12 ); + + // find corresponding nodes of top and bottom by finding a side face including 2 node of each + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + const SMDS_MeshElement* sideFace = 0; + size_t i; + for ( i = 8; i < nodes.size()-1 && !sideFace; ++i ) + { + sideFace = mesh->FindFace( nodes[0], nodes[1], nodes[ i ], nodes[ i + 1 ]); + } + if ( !sideFace ) + return newHexa; + + --i; // restore after ++i in the loop + bool botOriRight = SMESH_MeshAlgos::IsRightOrder( sideFace, nodes[ 0 ], nodes[ 1 ] ); + bool topOriRight = SMESH_MeshAlgos::IsRightOrder( sideFace, nodes[ i ], nodes[ i + 1 ] ); + if ( botOriRight == topOriRight ) + { + nodes[ 4 ] = nodes[ i + 1 ]; + nodes[ 5 ] = nodes[ i + 0 ]; + nodes[ 6 ] = nodes[ i + 3 ]; + nodes[ 7 ] = nodes[ i + 2 ]; + } + else + { + nodes[ 4 ] = nodes[ i + 0 ]; + nodes[ 5 ] = nodes[ i + 1 ]; + nodes[ 6 ] = nodes[ i + 2 ]; + nodes[ 7 ] = nodes[ i + 3 ]; + } + + newHexa = helper.AddVolume( nodes[ 0 ], nodes[ 1 ], nodes[ 2 ], nodes[ 3 ], + nodes[ 4 ], nodes[ 5 ], nodes[ 6 ], nodes[ 7 ]); + + return newHexa; + } + + //======================================================================= + //function : addTetra + //purpose : + //======================================================================= + + const SMDS_MeshElement* addTetra( std::vector< const SMDS_MeshElement* >& faces, + const std::vector< int > & quantities, + SMESH_MesherHelper & helper ) + { + const SMDS_MeshElement* newTetra = 0; + + // check nb of nodes in faces + for ( size_t i = 0; i < quantities.size(); ++i ) + if ( quantities[ i ] != 3 ) + return newTetra; + + const SMDS_MeshElement* botFace = faces[0]; + + std::vector< const SMDS_MeshNode* > nodes( 6 ); + nodes.assign( botFace->begin_nodes(), botFace->end_nodes() ); + nodes.resize( 3 ); + + const SMDS_MeshNode* topNode = 0; + for ( size_t i = 0; i < 3 && !topNode; ++i ) + { + topNode = faces[ 1 ]->GetNode( i ); + if ( botFace->GetNodeIndex( topNode ) >= 0 ) + topNode = 0; + } + if ( !topNode ) + return newTetra; + + nodes.push_back( topNode ); + + newTetra = helper.AddVolume( nodes[ 0 ], nodes[ 1 ], nodes[ 2 ], nodes[ 3 ]); + + return newTetra; + } + + //======================================================================= + //function : addPenta + //purpose : + //======================================================================= + + const SMDS_MeshElement* addPenta( std::vector< const SMDS_MeshElement* >& faces, + const std::vector< int > & quantities, + SMESH_MesherHelper & helper ) + { + const SMDS_MeshElement* newPenta = 0; + + // check nb of nodes in faces and find triangle faces + int trias[2] = { -1, -1 }; + for ( size_t i = 0; i < quantities.size(); ++i ) + if ( quantities[ i ] != 4 ) + { + if ( quantities[ i ] != 3 ) + return newPenta; + int iTria = ( trias[0] != -1 ); + if ( trias[ iTria ] != -1 ) + return newPenta; + trias[ iTria ] = i; + } + if ( trias[1] == -1 ) + return newPenta; + + int iSide = trias[0] + 1; + if ( iSide == trias[1] ) + ++iSide; + + const SMDS_MeshElement* botFace = faces[ trias[0]]; + const SMDS_MeshElement* topFace = faces[ trias[1]]; + const SMDS_MeshElement* sideFace = faces[ iSide ]; + const SMDS_MeshNode* nodes[ 6 ] = { 0,0,0,0,0,0 }; + for ( int i = 0 ; i < 3; ++i ) + { + const SMDS_MeshNode* botNode = botFace->GetNode( i ); + if ( sideFace->GetNodeIndex( botNode ) < 0 ) + nodes[2] = botNode; + else + nodes[ bool( nodes[0] )] = botNode; + + const SMDS_MeshNode* topNode = topFace->GetNode( i ); + if ( sideFace->GetNodeIndex( topNode ) < 0 ) + nodes[5] = topNode; + else + nodes[ 3 + bool( nodes[3]) ] = topNode; + } + bool botOriRight = SMESH_MeshAlgos::IsRightOrder( sideFace, nodes[ 0 ], nodes[ 1 ]); + bool topOriRight = SMESH_MeshAlgos::IsRightOrder( sideFace, nodes[ 3 ], nodes[ 4 ]); + if ( botOriRight == topOriRight ) + std::swap( nodes[ 3 ], nodes[ 4 ]); + + newPenta = helper.AddVolume( nodes[ 0 ], nodes[ 1 ], nodes[ 2 ], + nodes[ 3 ], nodes[ 4 ], nodes[ 5 ]); + + return newPenta; + } + + //======================================================================= + //function : addPyra + //purpose : + //======================================================================= + + const SMDS_MeshElement* addPyra( std::vector< const SMDS_MeshElement* >& faces, + const std::vector< int > & quantities, + SMESH_MesherHelper & helper ) + { + const SMDS_MeshElement* newPyra = 0; + + // check nb of nodes in faces + int iBot = -1; + for ( size_t i = 0; i < quantities.size(); ++i ) + if ( quantities[ i ] != 3 ) + { + if ( quantities[ i ] != 4 || iBot != -1 ) + return newPyra; + iBot = i; + } + + const SMDS_MeshElement* botFace = faces[ iBot ]; + + std::vector< const SMDS_MeshNode* > nodes( 8 ); + nodes.assign( botFace->begin_nodes(), botFace->end_nodes() ); + nodes.resize( 4 ); + + const SMDS_MeshNode* topNode = 0; + for ( size_t i = 0; i < 4 && !topNode; ++i ) + { + topNode = faces[ 1 ]->GetNode( i ); + if ( botFace->GetNodeIndex( topNode ) >= 0 ) + topNode = 0; + } + if ( !topNode ) + return newPyra; + + nodes.push_back( topNode ); + + newPyra = helper.AddVolume( nodes[ 0 ], nodes[ 1 ], nodes[ 2 ], nodes[ 3 ], nodes[4] ); + + return newPyra; + } + + //======================================================================= + //function : addHPrism + //purpose : add hexagonal prism + //======================================================================= + + const SMDS_MeshElement* addHPrism( std::vector< const SMDS_MeshElement* >& faces, + const std::vector< int > & quantities, + SMESH_MesherHelper & helper ) + { + const SMDS_MeshElement* newHexPrism = 0; + + // check nb of nodes in faces and find hexagons + int hexa[2] = { -1, -1 }; + for ( size_t i = 0; i < quantities.size(); ++i ) + if ( quantities[ i ] != 4 ) + { + if ( quantities[ i ] != 6 ) + return newHexPrism; + int iHex = ( hexa[0] != -1 ); + if ( hexa[ iHex ] != -1 ) + return newHexPrism; + hexa[ iHex ] = i; + } + if ( hexa[1] == -1 ) + return newHexPrism; + + int iSide = hexa[0] + 1; + if ( iSide == hexa[1] ) + ++iSide; + + const SMDS_MeshElement* botFace = faces[ hexa[ 0 ]]; + const SMDS_MeshElement* topFace = faces[ hexa[ 1 ]]; + std::vector< const SMDS_MeshNode* > nodes( 24 ); // last 12 is a working buffer + + nodes.assign( botFace->begin_nodes(), botFace->end_nodes() ); + nodes.resize( 12 ); // set top nodes after hexa nodes - [12-17] + nodes.insert( nodes.end(), topFace->begin_nodes(), topFace->end_nodes() ); + nodes.resize( 18 ); + nodes.insert( nodes.end(), nodes.begin() + 12, nodes.begin() + 18 ); + + // find corresponding nodes of top and bottom by finding a side face including 2 node of each + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + const SMDS_MeshElement* sideFace = 0; + size_t i; + for ( i = 12; i < nodes.size()-1 && !sideFace; ++i ) + { + sideFace = mesh->FindFace( nodes[0], nodes[1], nodes[ i ], nodes[ i + 1 ]); + } + if ( !sideFace ) + return newHexPrism; + + --i; // restore after ++i in the loop + bool botOriRight = SMESH_MeshAlgos::IsRightOrder( sideFace, nodes[ 0 ], nodes[ 1 ] ); + bool topOriRight = SMESH_MeshAlgos::IsRightOrder( sideFace, nodes[ i ], nodes[ i + 1 ] ); + if ( botOriRight == topOriRight ) + { + nodes[ 6 ] = nodes[ i + 1 ]; + nodes[ 7 ] = nodes[ i + 0 ]; + nodes[ 8 ] = nodes[ i + 5 ]; + nodes[ 9 ] = nodes[ i + 4 ]; + nodes[ 10 ] = nodes[ i + 3 ]; + nodes[ 11 ] = nodes[ i + 2 ]; + } + else + { + nodes[ 6 ] = nodes[ i + 0 ]; + nodes[ 7 ] = nodes[ i + 1 ]; + nodes[ 8 ] = nodes[ i + 2 ]; + nodes[ 9 ] = nodes[ i + 3 ]; + nodes[ 10 ] = nodes[ i + 4 ]; + nodes[ 11 ] = nodes[ i + 5 ]; + } + + newHexPrism = helper.AddVolume( nodes[ 0 ], nodes[ 1 ], nodes[ 2 ], + nodes[ 3 ], nodes[ 4 ], nodes[ 5 ], + nodes[ 6 ], nodes[ 7 ], nodes[ 8 ], + nodes[ 9 ], nodes[10 ], nodes[11 ]); + + return newHexPrism; + } + + //======================================================================= + //function : addPoly + //purpose : + //======================================================================= + + const SMDS_MeshElement* addPoly( std::vector< const SMDS_MeshElement* >& faces, + const std::vector< int > & quantities, + SMESH_MesherHelper & helper ) + { + const SMDS_MeshElement* newPoly = 0; + + std::vector< const SMDS_MeshNode* > nodes; + for ( size_t iF = 0; iF < faces.size(); ++iF ) + nodes.insert( nodes.end(), faces[iF]->begin_nodes(), faces[iF]->end_nodes() ); + + newPoly = helper.AddPolyhedralVolume( nodes, quantities ); + + return newPoly; + } + +} // namespace + +//======================================================================= +//function : StdMeshers_PolyhedronPerSolid_3D +//purpose : +//======================================================================= + +StdMeshers_PolyhedronPerSolid_3D::StdMeshers_PolyhedronPerSolid_3D(int hypId, + SMESH_Gen* gen) + :SMESH_3D_Algo(hypId, gen), + myEdgeMesher( new _EdgeMesher( gen->GetANewId(), gen )), + myFaceMesher( new StdMeshers_PolygonPerFace_2D( gen->GetANewId(), gen )) +{ + _name = "PolyhedronPerSolid_3D"; + _requireDiscreteBoundary = false; + _supportSubmeshes = true; + _compatibleHypothesis.push_back("ViscousLayers"); + _neededLowerHyps[0] = _neededLowerHyps[1] = _neededLowerHyps[2] = true; +} + +//======================================================================= +//function : ~StdMeshers_PolyhedronPerSolid_3D +//purpose : +//======================================================================= + +StdMeshers_PolyhedronPerSolid_3D::~StdMeshers_PolyhedronPerSolid_3D() +{ + delete myEdgeMesher; + delete myFaceMesher; +} + +//======================================================================= +//function : CheckHypothesis +//purpose : +//======================================================================= + +bool StdMeshers_PolyhedronPerSolid_3D::CheckHypothesis(SMESH_Mesh& theMesh, + const TopoDS_Shape& theShape, + Hypothesis_Status& theStatus) +{ + myViscousLayersHyp = NULL; + + const std::list& hyps = + GetUsedHypothesis( theMesh, theShape, /*ignoreAuxiliary=*/false); + std::list ::const_iterator h = hyps.begin(); + if ( h == hyps.end()) + { + theStatus = SMESH_Hypothesis::HYP_OK; + return true; + } + + // only StdMeshers_ViscousLayers can be used + theStatus = HYP_OK; + for ( ; h != hyps.end(); ++h ) + { + if ( !(myViscousLayersHyp = dynamic_cast< const StdMeshers_ViscousLayers*> ( *h ))) + break; + } + if ( !myViscousLayersHyp ) + theStatus = HYP_INCOMPATIBLE; + else + error( myViscousLayersHyp->CheckHypothesis( theMesh, theShape, theStatus )); + + return theStatus == HYP_OK; +} + +//======================================================================= +//function : Compute +//purpose : +//======================================================================= + +bool StdMeshers_PolyhedronPerSolid_3D::Compute(SMESH_Mesh& theMesh, + const TopoDS_Shape& theShape) +{ + const SMDS_MeshElement* newVolume = 0; + + SMESH_subMesh* sm = theMesh.GetSubMesh( theShape ); + SMESH_subMeshIteratorPtr smIt = sm->getDependsOnIterator( /*includeSelf=*/true, + /*complexFirst=*/false); + while ( smIt->more() ) + { + sm = smIt->next(); + if ( !sm->IsEmpty() ) + continue; + + const TopoDS_Shape & shape = sm->GetSubShape(); + switch ( shape.ShapeType() ) + { + case TopAbs_VERTEX: + sm->ComputeStateEngine( SMESH_subMesh::COMPUTE ); + break; + + case TopAbs_EDGE: + sm->ComputeStateEngine( SMESH_subMesh::COMPUTE ); + if ( sm->IsEmpty() ) + myEdgeMesher->Compute( theMesh, shape ); + break; + + case TopAbs_FACE: + sm->ComputeStateEngine( SMESH_subMesh::COMPUTE ); + if ( sm->IsEmpty() && !myFaceMesher->Compute( theMesh, shape )) + { + sm->GetComputeError() = myFaceMesher->GetComputeError(); + sm->GetComputeError()->myAlgo = myFaceMesher; + return false; + } + break; + + case TopAbs_SOLID: + { + SMESH_MesherHelper helper( theMesh ); + helper.SetElementsOnShape( true ); + _quadraticMesh = helper.IsQuadraticSubMesh( shape ); + + SMESH_ProxyMesh::Ptr proxymesh( new SMESH_ProxyMesh( theMesh )); + if ( myViscousLayersHyp ) + { + proxymesh = myViscousLayersHyp->Compute( theMesh, theShape ); + if ( !proxymesh ) + return false; + } + + std::vector< const SMDS_MeshElement* > faces; + faces.reserve( 20 ); + + for ( TopExp_Explorer faceEx( shape, TopAbs_FACE ); faceEx.More(); faceEx.Next() ) + { + const SMESHDS_SubMesh* smDS = proxymesh->GetSubMesh( faceEx.Current() ); + for ( SMDS_ElemIteratorPtr faceIt = smDS->GetElements(); faceIt->more(); ) + faces.push_back( faceIt->next() ); + } + + bool useMediumNodes = false; + if ( !_quadraticMesh && theMesh.GetMeshDS()->GetMeshInfo().NbFaces( ORDER_QUADRATIC )) + for ( size_t i = 0; i < faces.size() && !useMediumNodes ; ++i ) + useMediumNodes = faces[ i ]->IsQuadratic(); + + std::vector< int > quantities( faces.size() ); + std::set< const SMDS_MeshNode* > nodes; + for ( size_t i = 0; i < faces.size(); ++i ) + { + quantities[ i ] = useMediumNodes ? faces[ i ]->NbNodes() : faces[ i ]->NbCornerNodes(); + for ( int iN = 0; iN < quantities[ i ]; ++iN ) + nodes.insert( faces[ i ]->GetNode( iN )); + } + + const size_t nbNodes = nodes.size(), nbFaces = faces.size(); + if ( nbNodes == 8 && nbFaces == 6 ) newVolume = addHexa ( faces, quantities, helper ); + else if ( nbNodes == 4 && nbFaces == 4 ) newVolume = addTetra ( faces, quantities, helper ); + else if ( nbNodes == 6 && nbFaces == 5 ) newVolume = addPenta ( faces, quantities, helper ); + else if ( nbNodes == 5 && nbFaces == 5 ) newVolume = addPyra ( faces, quantities, helper ); + else if ( nbNodes == 12 && nbFaces == 8 ) newVolume = addHPrism( faces, quantities, helper ); + if ( !newVolume ) + newVolume = addPoly ( faces, quantities, helper ); + + if ( newVolume ) + { + SMESH::Controls::BadOrientedVolume checker; + checker.SetMesh( theMesh.GetMeshDS() ); + if ( checker.IsSatisfy( newVolume->GetID() )) + { + SMESH_MeshEditor editor( &theMesh ); + editor.Reorient( newVolume ); + } + } + } + default:; + + } // switch ( shape.ShapeType() ) + } // loop on sub-meshes + + return newVolume; +} + +//======================================================================= +//function : Evaluate +//purpose : +//======================================================================= + +bool StdMeshers_PolyhedronPerSolid_3D::Evaluate(SMESH_Mesh& theMesh, + const TopoDS_Shape& theShape, + MapShapeNbElems& theResMap) +{ + _quadraticMesh = false; + + SMESH_subMesh* sm = theMesh.GetSubMesh( theShape ); + SMESH_subMeshIteratorPtr smIt = sm->getDependsOnIterator( /*includeSelf=*/true, + /*complexFirst=*/false); + while ( smIt->more() ) + { + sm = smIt->next(); + + MapShapeNbElems::iterator sm2vec = theResMap.find( sm ); + if ( sm2vec != theResMap.end() && !sm2vec->second.empty() ) + continue; + + const TopoDS_Shape & shape = sm->GetSubShape(); + switch ( shape.ShapeType() ) + { + case TopAbs_EDGE: + myEdgeMesher->Evaluate( theMesh, shape, theResMap ); + break; + + case TopAbs_FACE: + { + myFaceMesher->Evaluate( theMesh, shape, theResMap ); + std::vector & quantities = theResMap[ sm ]; + _quadraticMesh = ( !quantities.empty() && + ( quantities[ SMDSEntity_Quad_Triangle ] + + quantities[ SMDSEntity_Quad_Quadrangle ] + + quantities[ SMDSEntity_Quad_Polygon ])); + break; + } + + case TopAbs_SOLID: + { + std::vector & quantities = theResMap[ sm ]; + quantities.resize( SMDSEntity_Last, 0 ); + + SMESH_MesherHelper helper( theMesh ); + const int nbNodes = helper.Count( shape, TopAbs_VERTEX, /*ignoreSame=*/true ); + const int nbFaces = helper.Count( shape, TopAbs_FACE, /*ignoreSame=*/false ); + + if ( nbNodes == 8 && nbFaces == 6 ) + quantities[ _quadraticMesh ? SMDSEntity_Quad_Hexa : SMDSEntity_Hexa ] = 1; + else if ( nbNodes == 4 && nbFaces == 4 ) + quantities[ _quadraticMesh ? SMDSEntity_Quad_Tetra : SMDSEntity_Tetra ] = 1; + else if ( nbNodes == 6 && nbFaces == 5 ) + quantities[ _quadraticMesh ? SMDSEntity_Quad_Penta : SMDSEntity_Penta ] = 1; + else if ( nbNodes == 5 && nbFaces == 5 ) + quantities[ _quadraticMesh ? SMDSEntity_Quad_Pyramid : SMDSEntity_Pyramid ] = 1; + else if ( nbNodes == 12 && nbFaces == 8 ) + quantities[ /*_quadraticMesh ? SMDSEntity_Quad_Pyramid :*/ SMDSEntity_Hexagonal_Prism ] = 1; + else + quantities[ /*_quadraticMesh ? SMDSEntity_Quad_Polyhedra : */SMDSEntity_Polyhedra ] = 1; + + return true; + } + default:; + + } // switch ( shape.ShapeType() ) + } // loop on sub-meshes + + return false; +} diff --git a/src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.hxx b/src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.hxx new file mode 100644 index 000000000..f6296aa18 --- /dev/null +++ b/src/StdMeshers/StdMeshers_PolyhedronPerSolid_3D.hxx @@ -0,0 +1,58 @@ +// 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 +// +// 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, 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 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : StdMeshers_PolyhedronPerSolid_3D.hxx +// Module : SMESH +// +#ifndef _SMESH_PolyhedronPerSolid_3D_HXX_ +#define _SMESH_PolyhedronPerSolid_3D_HXX_ + +#include "SMESH_StdMeshers.hxx" +#include "SMESH_Algo.hxx" + +class StdMeshers_Regular_1D; +class StdMeshers_PolygonPerFace_2D; +class StdMeshers_ViscousLayers; + +class STDMESHERS_EXPORT StdMeshers_PolyhedronPerSolid_3D: public SMESH_3D_Algo +{ + public: + StdMeshers_PolyhedronPerSolid_3D(int hypId, SMESH_Gen* gen); + ~StdMeshers_PolyhedronPerSolid_3D(); + + virtual bool CheckHypothesis(SMESH_Mesh& aMesh, + const TopoDS_Shape& aShape, + SMESH_Hypothesis::Hypothesis_Status& aStatus); + + virtual bool Compute(SMESH_Mesh& aMesh, const TopoDS_Shape& aShape); + + virtual bool Evaluate(SMESH_Mesh & aMesh, const TopoDS_Shape & aShape, + MapShapeNbElems& aResMap); + + private: + + StdMeshers_Regular_1D* myEdgeMesher; + StdMeshers_PolygonPerFace_2D* myFaceMesher; + const StdMeshers_ViscousLayers* myViscousLayersHyp; +}; + +#endif diff --git a/src/StdMeshers_I/CMakeLists.txt b/src/StdMeshers_I/CMakeLists.txt index ceed790b4..4b1c390ba 100644 --- a/src/StdMeshers_I/CMakeLists.txt +++ b/src/StdMeshers_I/CMakeLists.txt @@ -119,6 +119,7 @@ SET(StdMeshersEngine_HEADERS StdMeshers_CartesianParameters3D_i.hxx StdMeshers_Cartesian_3D_i.hxx StdMeshers_PolygonPerFace_2D_i.hxx + StdMeshers_PolyhedronPerSolid_3D_i.hxx ) IF(SALOME_SMESH_ENABLE_MEFISTO) SET(StdMeshersEngine_HEADERS ${StdMeshersEngine_HEADERS} StdMeshers_MEFISTO_2D_i.hxx) @@ -174,6 +175,7 @@ SET(StdMeshersEngine_SOURCES StdMeshers_Cartesian_3D_i.cxx StdMeshers_Adaptive1D_i.cxx StdMeshers_PolygonPerFace_2D_i.cxx + StdMeshers_PolyhedronPerSolid_3D_i.cxx ) IF(SALOME_SMESH_ENABLE_MEFISTO) diff --git a/src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.cxx b/src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.cxx new file mode 100644 index 000000000..94d5648ce --- /dev/null +++ b/src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.cxx @@ -0,0 +1,57 @@ +// 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 +// +// 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, 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 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : StdMeshers_PolyhedronPerSolid_3D_i.cxx +// Module : SMESH +// + +#include "StdMeshers_PolyhedronPerSolid_3D_i.hxx" + +#include "SMESH_Gen.hxx" +#include "StdMeshers_PolyhedronPerSolid_3D.hxx" + +//============================================================================= +/*! + * Constructor + */ +//============================================================================= + +StdMeshers_PolyhedronPerSolid_3D_i::StdMeshers_PolyhedronPerSolid_3D_i( PortableServer::POA_ptr thePOA, + ::SMESH_Gen* theGenImpl ) + : SALOME::GenericObj_i( thePOA ), + SMESH_Hypothesis_i( thePOA ), + SMESH_Algo_i( thePOA ), + SMESH_3D_Algo_i( thePOA ) +{ + myBaseImpl = new ::StdMeshers_PolyhedronPerSolid_3D( theGenImpl->GetANewId(), + theGenImpl ); +} + +//============================================================================= +/*! + * Destructor + */ +//============================================================================= + +StdMeshers_PolyhedronPerSolid_3D_i::~StdMeshers_PolyhedronPerSolid_3D_i() +{ +} diff --git a/src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.hxx b/src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.hxx new file mode 100644 index 000000000..26bdffc8d --- /dev/null +++ b/src/StdMeshers_I/StdMeshers_PolyhedronPerSolid_3D_i.hxx @@ -0,0 +1,53 @@ +// 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 +// +// 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, 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 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +// File : StdMeshers_PolyhedronPerSolid_3D_i.hxx +// Module : SMESH +// +#ifndef _SMESH_PolyhedronPerSolid_3D_I_HXX_ +#define _SMESH_PolyhedronPerSolid_3D_I_HXX_ + +#include "SMESH_StdMeshers_I.hxx" + +#include +#include CORBA_SERVER_HEADER(SMESH_BasicHypothesis) + +#include "SMESH_3D_Algo_i.hxx" + +class SMESH_Gen; + +// ====================================================== +// Polyhedron Per Solid 3D algorithm +// ====================================================== +class STDMESHERS_I_EXPORT StdMeshers_PolyhedronPerSolid_3D_i: + public virtual POA_StdMeshers::StdMeshers_PolyhedronPerSolid_3D, + public virtual SMESH_3D_Algo_i +{ + public: + // Constructor + StdMeshers_PolyhedronPerSolid_3D_i( PortableServer::POA_ptr thePOA, + ::SMESH_Gen* theGenImpl ); + // Destructor + virtual ~StdMeshers_PolyhedronPerSolid_3D_i(); +}; + +#endif diff --git a/src/StdMeshers_I/StdMeshers_i.cxx b/src/StdMeshers_I/StdMeshers_i.cxx index 602b00ae2..676de466d 100644 --- a/src/StdMeshers_I/StdMeshers_i.cxx +++ b/src/StdMeshers_I/StdMeshers_i.cxx @@ -57,6 +57,7 @@ #include "StdMeshers_NumberOfLayers_i.hxx" #include "StdMeshers_NumberOfSegments_i.hxx" #include "StdMeshers_PolygonPerFace_2D_i.hxx" +#include "StdMeshers_PolyhedronPerSolid_3D_i.hxx" #include "StdMeshers_Prism_3D_i.hxx" #include "StdMeshers_ProjectionSource1D_i.hxx" #include "StdMeshers_ProjectionSource2D_i.hxx" @@ -250,6 +251,8 @@ STDMESHERS_I_EXPORT aCreator = new StdHypothesisCreator_i; else if (strcmp(aHypName, "PolygonPerFace_2D") == 0) aCreator = new StdHypothesisCreator_i; + else if (strcmp(aHypName, "PolyhedronPerSolid_3D") == 0) + aCreator = new StdHypothesisCreator_i; else ; return aCreator; From 7cda93af4017eadf99cf109fc3cce1208c969b26 Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 5 Feb 2020 18:38:06 +0300 Subject: [PATCH 29/60] Make SetNodeOnEdge() tell a valid range if U is invalid Inspired by https://salome-platform.org/forum/forum_10/103286584 --- src/SMESH_I/SMESH_MeshEditor_i.cxx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/SMESH_I/SMESH_MeshEditor_i.cxx b/src/SMESH_I/SMESH_MeshEditor_i.cxx index 421a0babf..b813fc37b 100644 --- a/src/SMESH_I/SMESH_MeshEditor_i.cxx +++ b/src/SMESH_I/SMESH_MeshEditor_i.cxx @@ -1390,8 +1390,11 @@ void SMESH_MeshEditor_i::SetNodeOnEdge(CORBA::Long NodeID, CORBA::Long EdgeID, Standard_Real f,l; BRep_Tool::Range( TopoDS::Edge( shape ), f,l); if ( paramOnEdge < f || paramOnEdge > l ) - THROW_SALOME_CORBA_EXCEPTION("Invalid paramOnEdge", SALOME::BAD_PARAM); - + { + SMESH_Comment txt("Invalid paramOnEdge. It must vary in range [ "); + txt << f << ", " << l << " ]"; + THROW_SALOME_CORBA_EXCEPTION(txt.c_str(), SALOME::BAD_PARAM); + } mesh->SetNodeOnEdge( node, EdgeID, paramOnEdge ); myMesh->SetIsModified( true ); @@ -1434,14 +1437,11 @@ void SMESH_MeshEditor_i::SetNodeOnFace(CORBA::Long NodeID, CORBA::Long FaceID, v > surf.LastVParameter() ); if ( isOut ) { -#ifdef _DEBUG_ - MESSAGE ( "FACE " << FaceID << " (" << u << "," << v << ") out of " - << " u( " << surf.FirstUParameter() - << "," << surf.LastUParameter() - << ") v( " << surf.FirstVParameter() - << "," << surf.LastVParameter() << ")" ); -#endif - THROW_SALOME_CORBA_EXCEPTION("Invalid UV", SALOME::BAD_PARAM); + SMESH_Comment txt("Invalid UV. U must vary in range [ "); + txt << surf.FirstUParameter() << ", " << surf.LastUParameter() << " ], "; + txt << "V must vary in range [ "; + txt << surf.FirstVParameter() << ", " << surf.LastVParameter() << " ]"; + THROW_SALOME_CORBA_EXCEPTION(txt.c_str(), SALOME::BAD_PARAM); } mesh->SetNodeOnFace( node, FaceID, u, v ); From 2fb74f97f6789b067b03ae3e9574b1b332c52832 Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 6 Feb 2020 13:33:19 +0300 Subject: [PATCH 30/60] #18665 [CEA 17339] Polyhedron volume calculation --- src/OBJECT/SMESH_FaceOrientationFilter.cxx | 29 ++++++++++++---------- src/SMDS/SMDS_VolumeTool.cxx | 6 +++-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/OBJECT/SMESH_FaceOrientationFilter.cxx b/src/OBJECT/SMESH_FaceOrientationFilter.cxx index e77f9a7d8..515629ca9 100644 --- a/src/OBJECT/SMESH_FaceOrientationFilter.cxx +++ b/src/OBJECT/SMESH_FaceOrientationFilter.cxx @@ -25,18 +25,18 @@ #include +#include #include #include -#include -#include -#include -#include - #include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include @@ -216,13 +216,16 @@ void GetFaceParams( vtkCell* theFace, double theNormal[3], double& theSize ) vtkPoints* aPoints = theFace->GetPoints(); // here we get first 3 points from the face and calculate the normal as a cross-product of vectors - double x0 = aPoints->GetPoint(0)[0], y0 = aPoints->GetPoint(0)[1], z0 = aPoints->GetPoint(0)[2]; - double x1 = aPoints->GetPoint(1)[0], y1 = aPoints->GetPoint(1)[1], z1 = aPoints->GetPoint(1)[2]; - double x2 = aPoints->GetPoint(2)[0], y2 = aPoints->GetPoint(2)[1], z2 = aPoints->GetPoint(2)[2]; + // double x0 = aPoints->GetPoint(0)[0], y0 = aPoints->GetPoint(0)[1], z0 = aPoints->GetPoint(0)[2]; + // double x1 = aPoints->GetPoint(1)[0], y1 = aPoints->GetPoint(1)[1], z1 = aPoints->GetPoint(1)[2]; + // double x2 = aPoints->GetPoint(2)[0], y2 = aPoints->GetPoint(2)[1], z2 = aPoints->GetPoint(2)[2]; - theNormal[0] = ( y1 - y0 ) * ( z2 - z0 ) - ( z1 - z0 ) * ( y2 - y0 ); - theNormal[1] = ( z1 - z0 ) * ( x2 - x0 ) - ( x1 - x0 ) * ( z2 - z0 ); - theNormal[2] = ( x1 - x0 ) * ( y2 - y0 ) - ( y1 - y0 ) * ( x2 - x0 ); + // theNormal[0] = ( y1 - y0 ) * ( z2 - z0 ) - ( z1 - z0 ) * ( y2 - y0 ); + // theNormal[1] = ( z1 - z0 ) * ( x2 - x0 ) - ( x1 - x0 ) * ( z2 - z0 ); + // theNormal[2] = ( x1 - x0 ) * ( y2 - y0 ) - ( y1 - y0 ) * ( x2 - x0 ); + + // issue #18665: Polyhedron volume calculation + vtkPolygon::ComputeNormal( aPoints, theNormal ); double* aBounds = theFace->GetBounds(); theSize = pow( pow( aBounds[1] - aBounds[0], 2 ) + diff --git a/src/SMDS/SMDS_VolumeTool.cxx b/src/SMDS/SMDS_VolumeTool.cxx index 2148a15a5..ba0798484 100644 --- a/src/SMDS/SMDS_VolumeTool.cxx +++ b/src/SMDS/SMDS_VolumeTool.cxx @@ -1203,11 +1203,13 @@ bool SMDS_VolumeTool::GetFaceNormal (int faceIndex, double & X, double & Y, doub XYZ aVec13( p3 - p1 ); XYZ cross = aVec12.Crossed( aVec13 ); - if ( myCurFace.myNbNodes >3*iQuad ) { - XYZ p4 ( myCurFace.myNodes[3*iQuad] ); + for ( int i = 3*iQuad; i < myCurFace.myNbNodes; i += iQuad ) + { + XYZ p4 ( myCurFace.myNodes[i] ); XYZ aVec14( p4 - p1 ); XYZ cross2 = aVec13.Crossed( aVec14 ); cross = cross + cross2; + aVec13 = aVec14; } double size = cross.Magnitude(); From 4f9b4e3fb3c7eecfc9290b53b078bf1b99357648 Mon Sep 17 00:00:00 2001 From: mpv Date: Fri, 7 Feb 2020 15:42:25 +0300 Subject: [PATCH 31/60] Make the dead shape correctly referenced by SMESH mesh after loading the python dump script --- src/SMESH_I/SMESH_Mesh_i.cxx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 3f65f9f9b..8c2a1a91b 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -273,6 +273,18 @@ void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) // re-assign global hypotheses to the new shape _mainShapeTick = -1; CheckGeomModif( true ); + + // update the reference to theNewGeom (needed for correct execution of a dumped python script) + SALOMEDS::SObject_var aSO = _gen_i->ObjectToSObject(_this()); + if (!aSO->_is_nil()) { + SALOMEDS::SObject_var aShapeRefSO; + if (aSO->FindSubObject(1, aShapeRefSO)) { + _gen_i->getStudyServant()->NewBuilder()->Addreference( + aShapeRefSO, _gen_i->getStudyServant()->FindObjectID(theNewGeom->GetStudyEntry())); + } + } + + TPythonDump() << SMESH::SMESH_Mesh_var(_this()) << ".ReplaceShape( " << theNewGeom->GetStudyEntry() << " )"; } //================================================================================ From 800fdf5fdc1b26f01fbd194253fc8a4d400b1c25 Mon Sep 17 00:00:00 2001 From: vsv Date: Wed, 12 Feb 2020 16:35:57 +0300 Subject: [PATCH 32/60] Do not remove Mesh on geometry change according to request #3134 --- src/SMESH_I/SMESH_Gen_i.cxx | 2 ++ src/SMESH_I/SMESH_Mesh_i.cxx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index 5e4bba536..f5c36db78 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -2021,6 +2021,8 @@ CORBA::Boolean SMESH_Gen_i::Compute( SMESH::SMESH_Mesh_ptr theMesh, if ( meshServant ) { meshServant->Load(); // NPAL16168: "geometrical group edition from a submesh don't modify mesh computation" + // Clear meshy because it was not cleared in CheckGeomModif of previous call + meshServant->Clear(); meshServant->CheckGeomModif(); // get local TopoDS_Shape TopoDS_Shape myLocShape; diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 8c2a1a91b..dff142a6a 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -2288,7 +2288,7 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) if ( _preMeshInfo ) _preMeshInfo->ForgetAllData(); - _impl->Clear(); + //_impl->Clear(); TopoDS_Shape newShape = _gen_i->GeomObjectToShape( mainGO ); if ( newShape.IsNull() ) return; From bda71f4197e589fab8b784abb636a993ff6aa808 Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 13 Feb 2020 13:37:31 +0300 Subject: [PATCH 33/60] Prevent crash at too small comment ratio of geometric progression hypothesis --- src/StdMeshers/StdMeshers_Regular_1D.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/StdMeshers/StdMeshers_Regular_1D.cxx b/src/StdMeshers/StdMeshers_Regular_1D.cxx index 12619c94f..45e1c25b6 100644 --- a/src/StdMeshers/StdMeshers_Regular_1D.cxx +++ b/src/StdMeshers/StdMeshers_Regular_1D.cxx @@ -974,6 +974,8 @@ bool StdMeshers_Regular_1D::computeInternalParameters(SMESH_Mesh & theMesh, an = eltSize; eltSize *= q; ++nbParams; + if ( q < 1. && eltSize < 1e-100 ) + return error("Too small common ratio causes too many segments"); } if ( nbParams > 1 ) { From 09e4b589830931f8a2cc4a93b5cd0696fa665dcb Mon Sep 17 00:00:00 2001 From: mpv Date: Thu, 13 Feb 2020 19:09:53 +0300 Subject: [PATCH 34/60] Fix for the break-link dump into python: also sub-mesh references must be updated. --- src/SMESH_I/SMESH_Mesh_i.cxx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index dff142a6a..04e0371bc 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -284,7 +284,11 @@ void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) } } - TPythonDump() << SMESH::SMESH_Mesh_var(_this()) << ".ReplaceShape( " << theNewGeom->GetStudyEntry() << " )"; + TPythonDump() << SMESH::SMESH_Mesh_var(_this()) << ".ReplaceShape( " + << theNewGeom->GetStudyEntry() << " )"; + + TPythonDump() << "SHAPERSTUDY.breakLinkForSubElements(salome.ObjectToSObject(" + << SMESH::SMESH_Mesh_var(_this()) <<".GetMesh()), " << theNewGeom->GetStudyEntry() << ")"; } //================================================================================ @@ -2288,7 +2292,9 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) if ( _preMeshInfo ) _preMeshInfo->ForgetAllData(); - //_impl->Clear(); + + if (isBreakLink) + _impl->Clear(); TopoDS_Shape newShape = _gen_i->GeomObjectToShape( mainGO ); if ( newShape.IsNull() ) return; From 77a0ffa4a136576fc5c95da1d477a0e974663c74 Mon Sep 17 00:00:00 2001 From: vsv Date: Fri, 14 Feb 2020 11:25:10 +0300 Subject: [PATCH 35/60] Set warning icon for mesh without geometry --- resources/mesh_tree_mesh_geom_modif.png | Bin 307 -> 3281 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/mesh_tree_mesh_geom_modif.png b/resources/mesh_tree_mesh_geom_modif.png index 8f05208ede2c4ac6401cb3462d531c5348b1a02d..a19574b0bb7a019c7090cb6165e843c6ab3a99b7 100644 GIT binary patch literal 3281 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 Date: Fri, 14 Feb 2020 14:16:51 +0300 Subject: [PATCH 36/60] Fix Break Link called from TUI --- src/SMESH_I/SMESH_Gen_i.cxx | 39 +++++++++++++++++----------- src/SMESH_I/SMESH_Gen_i.hxx | 3 +++ src/SMESH_I/SMESH_Mesh_i.cxx | 49 ++++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index f5c36db78..6117d4547 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -731,6 +731,25 @@ void SMESH_Gen_i::UpdateStudy() } } +//================================================================================ +/*! + * \brief Return true if mesh has ICON_SMESH_TREE_GEOM_MODIF icon + */ +//================================================================================ + +bool SMESH_Gen_i::isGeomModifIcon( SMESH::SMESH_Mesh_ptr mesh ) +{ + SALOMEDS::SObject_wrap so = ObjectToSObject( mesh ); + SALOMEDS::GenericAttribute_wrap attr; + if ( ! so->_is_nil() && so->FindAttribute( attr.inout(), "AttributePixMap" )) + { + SALOMEDS::AttributePixMap_wrap pm = attr; + CORBA::String_var ico = pm->GetPixMap(); + return ( strcmp( ico.in(), "ICON_SMESH_TREE_GEOM_MODIF" ) == 0 ); + } + return false; +} + //================================================================================= // function : hasObjectInfo() // purpose : shows if module provides information for its objects @@ -756,18 +775,9 @@ char* SMESH_Gen_i::getObjectInfo( const char* entry ) SALOMEDS::SObject_wrap so = getStudyServant()->FindObjectID( entry ); CORBA::Object_var obj = SObjectToObject( so ); SMESH::SMESH_Mesh_var mesh = SMESH::SMESH_Mesh::_narrow( obj ); - if ( !mesh->_is_nil() ) + if ( isGeomModifIcon( mesh )) { - SALOMEDS::GenericAttribute_wrap attr; - if ( so->FindAttribute( attr.inout(), "AttributePixMap" )) - { - SALOMEDS::AttributePixMap_wrap pm = attr; - CORBA::String_var ico = pm->GetPixMap(); - if ( strcmp( ico.in(), "ICON_SMESH_TREE_GEOM_MODIF" ) == 0 ) - { - txt << "The geometry was changed and the mesh needs to be recomputed"; - } - } + txt << "The geometry was changed and the mesh needs to be recomputed"; } if ( txt.empty() ) @@ -2019,10 +2029,11 @@ CORBA::Boolean SMESH_Gen_i::Compute( SMESH::SMESH_Mesh_ptr theMesh, SMESH_Mesh_i* meshServant = SMESH::DownCast( theMesh ); ASSERT( meshServant ); if ( meshServant ) { - meshServant->Load(); + if ( isGeomModifIcon( theMesh )) + meshServant->Clear(); + else + meshServant->Load(); // NPAL16168: "geometrical group edition from a submesh don't modify mesh computation" - // Clear meshy because it was not cleared in CheckGeomModif of previous call - meshServant->Clear(); meshServant->CheckGeomModif(); // get local TopoDS_Shape TopoDS_Shape myLocShape; diff --git a/src/SMESH_I/SMESH_Gen_i.hxx b/src/SMESH_I/SMESH_Gen_i.hxx index 3f03b67b2..0cb93fbb9 100644 --- a/src/SMESH_I/SMESH_Gen_i.hxx +++ b/src/SMESH_I/SMESH_Gen_i.hxx @@ -634,6 +634,9 @@ private: SMESH::SMESH_Mesh_ptr createMesh() throw ( SALOME::SALOME_Exception ); + // Check mesh icon + bool isGeomModifIcon( SMESH::SMESH_Mesh_ptr mesh ); + // Create a sub-mesh on a geometry that is not a sub-shape of the main shape // for the case where a valid sub-shape not found by CopyMeshWithGeom() SMESH::SMESH_subMesh_ptr createInvalidSubMesh( SMESH::SMESH_Mesh_ptr mesh, diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 04e0371bc..2b831f8c9 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -270,25 +270,30 @@ void SMESH_Mesh_i::ReplaceShape(GEOM::GEOM_Object_ptr theNewGeom) geomClient->RemoveShapeFromBuffer(aIOR); } + // update the reference to theNewGeom (needed for correct execution of a dumped python script) + SMESH::SMESH_Mesh_var me = _this(); + SALOMEDS::SObject_wrap aSO = _gen_i->ObjectToSObject( me ); + CORBA::String_var entry = theNewGeom->GetStudyEntry(); + if ( !aSO->_is_nil() ) + { + SALOMEDS::SObject_wrap aShapeRefSO; + if ( aSO->FindSubObject( _gen_i->GetRefOnShapeTag(), aShapeRefSO.inout() )) + { + SALOMEDS::SObject_wrap aShapeSO = _gen_i->getStudyServant()->FindObjectID( entry ); + SALOMEDS::StudyBuilder_var builder = _gen_i->getStudyServant()->NewBuilder(); + builder->Addreference( aShapeRefSO, aShapeSO ); + } + } + // re-assign global hypotheses to the new shape _mainShapeTick = -1; CheckGeomModif( true ); - // update the reference to theNewGeom (needed for correct execution of a dumped python script) - SALOMEDS::SObject_var aSO = _gen_i->ObjectToSObject(_this()); - if (!aSO->_is_nil()) { - SALOMEDS::SObject_var aShapeRefSO; - if (aSO->FindSubObject(1, aShapeRefSO)) { - _gen_i->getStudyServant()->NewBuilder()->Addreference( - aShapeRefSO, _gen_i->getStudyServant()->FindObjectID(theNewGeom->GetStudyEntry())); - } - } - - TPythonDump() << SMESH::SMESH_Mesh_var(_this()) << ".ReplaceShape( " - << theNewGeom->GetStudyEntry() << " )"; - TPythonDump() << "SHAPERSTUDY.breakLinkForSubElements(salome.ObjectToSObject(" - << SMESH::SMESH_Mesh_var(_this()) <<".GetMesh()), " << theNewGeom->GetStudyEntry() << ")"; + << me <<".GetMesh()), " << entry.in() << ")"; + + TPythonDump() << me << ".ReplaceShape( " << entry.in() << " )"; + } //================================================================================ @@ -2077,6 +2082,8 @@ TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData, int how ) CORBA::Object_var geomObj = _gen_i->SObjectToObject( geomSO ); GEOM::GEOM_Object_var geom = GEOM::GEOM_Object::_narrow( geomObj ); newShape = _gen_i->GeomObjectToShape( geom ); + CORBA::String_var entry = geom->GetStudyEntry(); + groupData._groupEntry = entry.in(); } } else @@ -2472,13 +2479,17 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) int newID = o2n->second, oldID = o2n->first; if ( !_mapSubMesh.count( oldID )) continue; - _mapSubMesh [ newID ] = _impl->GetSubMeshContaining( newID ); - _mapSubMesh_i [ newID ] = _mapSubMesh_i [ oldID ]; - _mapSubMeshIor[ newID ] = _mapSubMeshIor[ oldID ]; + if ( newID > 0 ) + { + _mapSubMesh [ newID ] = _impl->GetSubMeshContaining( newID ); + _mapSubMesh_i [ newID ] = _mapSubMesh_i [ oldID ]; + _mapSubMeshIor[ newID ] = _mapSubMeshIor[ oldID ]; + } _mapSubMesh. erase(oldID); _mapSubMesh_i. erase(oldID); _mapSubMeshIor.erase(oldID); - _mapSubMesh_i [ newID ]->changeLocalId( newID ); + if ( newID > 0 ) + _mapSubMesh_i [ newID ]->changeLocalId( newID ); } // update _mapSubMesh @@ -2487,7 +2498,7 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) i_sm->second = _impl->GetSubMesh( meshDS->IndexToShape( i_sm->first )); } - _gen_i->UpdateIcons( SMESH::SMESH_Mesh_var( _this() )); + _gen_i->UpdateIcons( me ); if ( !isBreakLink ) { From 59fe51ff7f83a733aee46ecad7adb391bc4323bf Mon Sep 17 00:00:00 2001 From: mpv Date: Fri, 14 Feb 2020 15:51:40 +0300 Subject: [PATCH 37/60] Correct python dump for the SHAPERSTUDY part of the script --- src/SMESH_I/SMESH_2smeshpy.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index d126f6b23..10398d8fc 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -3839,6 +3839,8 @@ bool _pyCommand::IsMethodCall() return false; if ( myString.StartsWith("#") ) return false; + if ( myString.StartsWith("SHAPERSTUDY") ) // skip shaperstudy specific dump string analysis + return false; const char* s = myString.ToCString() + GetBegPos( METHOD_IND ) + myMeth.Length() - 1; return ( s[0] == '(' || s[1] == '(' ); } From 70eb9c09d00f9c4b0e48d5aba70676e45e779f9c Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 25 Oct 2019 16:55:08 +0300 Subject: [PATCH 38/60] #16522 [CEA 7599] Viscous layers hypothesis: extract layers as a group --- .../examples/defining_hypotheses_ex17.py | 23 +++- .../SMESH/images/viscous_layers_2d_hyp.png | Bin 26908 -> 32774 bytes .../gui/SMESH/images/viscous_layers_hyp.png | Bin 32154 -> 39441 bytes .../gui/SMESH/input/additional_hypo.rst | 2 + idl/SMESH_BasicHypothesis.idl | 6 + resources/StdMeshers.xml.in | 10 +- src/SMESHGUI/SMESHGUI_Hypotheses.cxx | 14 +- src/SMESHGUI/SMESHGUI_Hypotheses.h | 2 + src/SMESH_SWIG/smesh_algorithm.py | 27 +++- src/StdMeshers/StdMeshers_ViscousLayers.cxx | 130 ++++++++++++++---- src/StdMeshers/StdMeshers_ViscousLayers.hxx | 10 ++ src/StdMeshers/StdMeshers_ViscousLayers2D.cxx | 26 +++- src/StdMeshersGUI/CMakeLists.txt | 2 + .../StdMeshersGUI_NameCheckableGrpWdg.cxx | 68 +++++++++ .../StdMeshersGUI_NameCheckableGrpWdg.h | 53 +++++++ .../StdMeshersGUI_StdHypothesisCreator.cxx | 43 ++++++ src/StdMeshersGUI/StdMeshers_msg_en.ts | 8 ++ .../StdMeshers_ViscousLayers2D_i.cxx | 27 ++++ .../StdMeshers_ViscousLayers2D_i.hxx | 4 + .../StdMeshers_ViscousLayers_i.cxx | 26 ++++ .../StdMeshers_ViscousLayers_i.hxx | 6 +- 21 files changed, 436 insertions(+), 51 deletions(-) create mode 100644 src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.cxx create mode 100644 src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.h diff --git a/doc/salome/examples/defining_hypotheses_ex17.py b/doc/salome/examples/defining_hypotheses_ex17.py index f2b7d8c20..cadd71e24 100644 --- a/doc/salome/examples/defining_hypotheses_ex17.py +++ b/doc/salome/examples/defining_hypotheses_ex17.py @@ -33,16 +33,22 @@ mesh.Segment().NumberOfSegments( 4 ) mesh.Triangle() mesh.Quadrangle(face1) -mesh.Compute() algo3D = mesh.Tetrahedron() thickness = 20 numberOfLayers = 10 stretchFactor = 1.5 -layersHyp = algo3D.ViscousLayers(thickness,numberOfLayers,stretchFactor,ignoreFaces) +groupName = "Boundary layers" +layersHyp = algo3D.ViscousLayers(thickness,numberOfLayers,stretchFactor, + ignoreFaces, # optional + groupName = groupName) # optional mesh.Compute() +# retrieve boundary prisms created by mesh.Compute() +boundaryGroup = mesh.GetGroupByName( layersHyp.GetGroupName() )[0] +print( "Nb boundary prisms", boundaryGroup.Size() ) + mesh.MakeGroup("Tetras",SMESH.VOLUME,SMESH.FT_ElemGeomType,"=",SMESH.Geom_TETRA) mesh.MakeGroup("Pyras",SMESH.VOLUME,SMESH.FT_ElemGeomType,"=",SMESH.Geom_PYRAMID) mesh.MakeGroup("Prims",SMESH.VOLUME,SMESH.FT_ElemGeomType,"=",SMESH.Geom_PENTA) @@ -55,12 +61,17 @@ edgeIds = geompy.SubShapeAllIDs( face1, geompy.ShapeType["EDGE"])[:-1] mesh = smesh.Mesh(face1,"VicsousLayers2D") mesh.Segment().NumberOfSegments( 5 ) -# viscous layers should be created on 1 edge, as we set 3 edges to ignore -vlHyp = mesh.Triangle().ViscousLayers2D( 2, 3, 1.5, edgeIds, isEdgesToIgnore=True ) - +# viscous layers will be created on 1 edge, as we set 3 edges to ignore +vlHyp = mesh.Triangle().ViscousLayers2D( 2, 3, 1.5, + edgeIds, isEdgesToIgnore=True, # optional + groupName=groupName) # optional mesh.Compute() -# viscous layers should be created on 3 edges, as we pass isEdgesToIgnore=False +# retrieve boundary elements created by mesh.Compute() +quadrangles = mesh.GetGroupByName( vlHyp.GetGroupName() )[0] +print( "Nb boundary quadrangles", quadrangles.Size() ) + +# viscous layers will be created on 3 edges, as we pass isEdgesToIgnore=False vlHyp.SetEdges( edgeIds, False ) mesh.Compute() diff --git a/doc/salome/gui/SMESH/images/viscous_layers_2d_hyp.png b/doc/salome/gui/SMESH/images/viscous_layers_2d_hyp.png index 4ad6ee0ec4720ea1ee8c55ead9d8bcdb0cb7946f..f9b952fd7f9379a54d1640e8d1613c7c30ba4039 100644 GIT binary patch literal 32774 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 literal 26908 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%0ABD}=QEghK0ZDG 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 IIVCg!0M|_~Jpcdz literal 32154 zcmbq*1yodB`|pT~NOwwyNJ}?3ARQtN0wUc?cSwVDIZ}djBi+&+(v3)W!_ar*`+vXt z)m?Ypb%!O$Fo!w&oV}mtSDWBh3X*8Z#K;f`1Wj5>{51rEzzc!gTSGzs&+sRdbbv39 z-%DxQK_KXDcmM9iF`|=zCmHQuYS@b#*%{cHy|*{Bv4*IdSsU6oQHWXU+u89`DA_aH zIlR+<|K8HfK>zONfod<6z+t3!hpCv^TN**6T;AK*n;O}f*-?nuSlij#IvCu&aL@OH z4+KI1krscU;+(WS=kx|=?H02CG$9g`iN>2k`ZJaCOROiq5SZimKg{I*Ab5z2_ri)% z?4dZWiYV@i`iGg2bO%NU6C8YCzYHpS!ay@)b1WJ**bED;-*iiEWkx0Jyb z)fj_wF#-rs)2p>!OmJ>rop&?Za5}si<#d#rs;KXQrwrkB=|O&&=H}SPjx^wVZ_CIW z@P(dJN(BNL3e+dfWx6OS6t35sl0u@4dNmn^e{cO^linvN67$j20mEKJdc~=dugy!n z33Y6|PdTEA*1EwjkX)D|oXd7?t&VKB0NayG_5NYDktHK(}zp5)@Twnzlot%sA-Q&l?r;0NS4 z;bGyI75ifzexqzur9y>2%~E&54h)`G@IKisHE4>I@{^9C+t|dkUhpiG8M*oK)-bwa z4~bHo3d_GGi^a13+XC`_>4tlHZ8G-Lrqc)6E4Oo-dJhmjc+K)tVO>&EG!|UhY^d_z z=cIx*Y?2TQc~EH|Gm<=}9Hogp*v+61ZbJ%gM+%M#`Tkk1MS}4CQIAURf`{4yJvLIC z3=O5M^49{I-)gy6*9y{}JYOR)ztN?YNYk5MJXONNTx?70XUd9S3)x@uYJ?(@ir|`m zE%t4aYdH5pN}F?29QgA5gLh>|p+H2XwaDmXmn0GvlHbdtWaB~GCtnMdbgp;L5{WKv z_D~y<7mz7qqf!DeiMQY)(zo9v!jE+ud)nH*mgLymP2M*2Ed5$x;UHZdgcbA-7v<;Q zE)9IEhG#fAoFGwNK3CI~UEAE+KuY|=TilC{k=*2PnU)bsOO|M>haXW{5veKpW#()| zL$2`Cr$>73i?cKxqo(}g?;h-M4A;57ZW`oV-s>uw#8FP}Bqn;EX%J7YQRdN!vh+K~ zSdOGPU*K)hHvu1S>AjEL6b-(Qn}-+jHv0??kTw}di+;~3dYI>{Um{(Ji>C>p7q;%lphgsGfv(JCD#y?x# z?0DgRMGzy|bhS0;+iCrl@L;Amt7u>Am8y;%yh}+k@G;67UzGIKeisg*#MQ}Yh5u2b zDAM&|Z}E!F!n_GN`Eys{`O)!0Z5F+2G3m!JRCe7)%Zt~0ukw(sngz4D7@L?G-Q1Sf zKgi0K!A73ulG3KZA9;nQ+HaH0!q|Q=u^m7(!R9Ma6lW#4qx*3cYT|Qe~T-uJ} z#>diUR8@&37woyv7b{TNG?Pp>&ndR930Lk=rwjdT?uB7+Snz1`Cy}r8eY$a;(Z?1? z?e$G<4sl!$4)Z5+($Y?M7JAsVAs&P2FHsT+g4?qP-yFVA0?qFGcOFVyj3e>OGev9UY3!%~(c)>4V__H(}j%n$QWd&%hvUhHTZn5%O#8BBS`z%s~6|C^`&+v&)wtUQ^IHkQs4 z5~O~Z(9O*}q_p4ZR1Wewo=XB7s&X3C-W?m%(NP<4Va^w}v}13r$A3zO zAcWE?rN(=oT=d$AJNs5+}Q*^~X4HJZSm zX^Hg%dezVEIt-&U>{qL{w5d)XKNyaIFVixx`^!L)ux`%nq^D!vk@~gKeC%j=vpbY+ zQ}{?^oP^%k-sgFQ;n&w_qx4-#Peysd_*Zu7enIA z_25$u$5#=k>2!JSaGLp$sthABwG*8yYvSz|YbO>|b&Tt0T|~Wxm}n4Xxr`q`cnUcaZ_@^%o7aWek%s_YTtOV2awJnq)=rqdtBBDe?vQL(WZx{bF1 z8Cl;QH1NM@1t3vc1!P`z960hE3hd{|s zMP}=h&6UxCSHjEgwPKo-+SGV~uWxv@L^<(sOs}#Ai61Oy(Y@PZ{-I8O9$jxB$3%xp z`h2eKT}#s*C4%Gb6|-Mt)!BD_vHsgD4n__RWgTVp?_*zI_|nG?qM~UarG5YYosN>6 z`mNClD773M97aZ5)c1A#A~l#;R%~BFg(ZIMZ-_oXNYjKM_%rMb4puM@{!-r!BS_Os;LTIt>L#ID{Y(WDB%?p@O1Ih7|<;cBn0Dw zcs6lZZ=3oJb;n41sjG6JU1G7lduin0CGQF!XQrL~vRt;ytpwovB zr7U*%++6x}bcl!-eILU0qPiK>2ll++yk+U>J%*9O(45N3fZp!WA@Ti_@#teyP)|p?us)T3j5SF?efgi5pQ)`kUPgPdGh#gqP|qxRU2KL_>AE-&47R z+lYzH3srM+3L2gETtZ8+0x#UH;+WwgtWq%oI8U6+{<)GRumH>T8#Ww}{g&O7WJDw1%_4MSy z;eOe9g)Sm@G5VvtA_^lazBiWSW^(QIzadqren)-Z$N;bs!9;Ated;s|u)&vE1nWnAQB2 zE$@)>-0GbH_3hM=h+?v!hN=>km!wvxrb4#W&gJC~s&w?L_1lKHu~!Un#3TtNi;{ZM z$r&RP3=8q@74HK^mJj6R~JYLZAU+cFtp6@6vN%g!UjgkO{rlo z)jF=XtTcsF2dSgz?a}a+ZGsF{U%Kc^AK0ZuvTi{6y^R~F{pRCW8a!eqhW5inDPF~j ze9PwmB@rp@YSyj3w&~$VM1*_a@`f~A#V&Krxh)DS`1_IkuXt#u#=Pv#uUt`ERW5S3 zN|=O|cUVm%m)n@KmCr9(g0oZ9RX?AVq&2F+^77Jh&uZcR$2=M7^c z(_mWsEY*Es2pS5#$M4ytqGR-Mz+S4~!7x9+fImirLKjL3;DGMcyIl%$uk7FCou;q5tlX?55o6JUREUXSD85YZn}@LT?2DnR~( zH+%;7HM@_VDi4*@~%^sSPa|Ss%Kir^v|2;_>4p?uW(3#nn**+79`;Bkr&^kX&0^ zyVx2??YC}Ut8sOsKTa56WMo9?H_A1-)(7*&lu=OV*b_r^a)=>AIM&AMA)QolL>tbcv`Udn8ZK*B}sU_1tuah;da&8S(X>Wk^$2lZ-&&$ zkirX9RW*~>R2BO<)A9A8ua`{yg!oShq#fHbTYG!U zLefE_6lRnJW*x`lbtO9AbDzuoMO;cm@cY;+gti7}X9WeQGb?rBwI36bZ|Tl75udKT zpsjkFkT|w^f6)0I-iw#JEIf?rJn9T_wNI@hGX0HuC5A^vLPJ9{WkW(kIoR1(2UGQ0 zm^Z#RfEzNjR-T-5b9JP6tsgT#et5*U@{o0smX&-#n2fB!^&;ZY@ILYfi?spm#`D$w zM7Q6kPxxH@nhnb>m+SVLE}Q&rV_2l6q~N=kd-S*=hMab119PlNjcn|k#9X$c2L_!7 z(6m?89y6y=mDK(HeM?JA2T%G3O$yZ z!WbA4v_-OHLZ0@w%^suy1M$q-rkvAE37V1-PudwvrYtQiwr;L3pVH8jOl@Zwz zJ$~4-GgB!`EG;WrXTLGL6+Mw0dbbLrV{(dg8}h7-d49Mv83Y+vHI!r(MJ>EaNc?a< z)~i0>+i=#lBP|?LZ=9tG?O{CeoMf#VkAMl5Q8c~nTZS>J6_`$a2?*#I%~g&d^YDrl zaa~?7Hyh#PO8M6CQA+C;^M|eM?K+-fr&iURDSU+IU3 zhqEEiR|p{^3nJtAmMMU7X0M1WWzx{%Xuk_J=d_wVeS z?3QZ!>qBXeurUBA+L|aXKF~Q@-oWw0jA`#19OU8QNhr$w_Dxws!_?e71hZ#w(1dMX z5AWM6`je9r6$U&?zifN}d)HPrR_Z#Oyjna}RmY~MwYpKU6j=wq{-6M>L|a??%C4UY ze+wSGBDJg}m3!Z6@MNQjMjm zj!ElXTnpniSOHSRW4K1o&z~PQM{{knT-{t;+V{dD7D)D1R%q>Zz-*$$MaF_Y*uJLp zT>R*Nj|2f31tmW}-`Dr!0hE9s;l~eAmW19FTVDjUQ~3u54kq&uuxi#jmo_zdSz8y0Qgrq793IC$ z$7-^B4n`q*D6*elP+-Y9)U~kSRWetdS2kr)tt#MdqD)+UJ-CLdr1ZtOH$-YcOJt_y zd<$>NjNl0=X|Bqq=r^BL{MAA(Q-R_5bh%~%ts;k%-`l&Z1B>tFVO*b4R|k&x{8z#l+5(tECAko&)-0b0)e zkPIjvYZvRj?rs<+Ll-L>$M-J2IL;5-T}h*tDS86RxubZ!>f7NnVVGa|9n2N|X5D^S zD}t7HG~MWS5k|sucy!27#_pj8f79gYak$*AFoK_km_p?HJ1CN0XKuFTYB*DN7Telb zjSecc+HiAMoCeGUlosu70mL8|@&r&wwwm_z^b`r};2;KFEU??~H-@ar#4AaplSPy} z1mLPWQ}*Y$l&7$hiPO0eP`|x(9|Qv*!W-%<{?G7eI^|?SK`F{(he+1*#7aIUiz*D;@eRb7 z);w=Jz=_NUI7z+yEUVlqB;=E=gI#+up|8 z|0L1LiP)WkGKf_}?9Y8tF(zlG1o(Ngva-koo!`$+eo#mi5c0ZtQY@PG3jNCchfBzj z#$_p(=^^d!x&ut22j$M+h4dpO?loQ)RaXn1rRUFsFhHll)?u;5KNO$2*7h7ZK;M}Uy54;KD1k-qcl|0o zBV%W0XM(M)&20S#=9OtsNoW#f@xL^PBdMyW8O{!B+_t(dj5XrfFz#k9937pZEMSML z^SUmZ0^Bg1n^Ep&_O6tdn_KO>zPb74#_|a3jr((#!(~t}jf_?%Xp6C+#j?6hEG}W{ zM@L864XzH33a0=Yq=>i`G4&ERas9amRrW8^sW3qsSlk@T7b^^4hsn{!@ENX0T1*rz zt{|mF4q=j#l3uM$ahUK}Pv?BQdz6_S(RHsz`x~=jK|!%sS5{8su_24-)ULB;ZcIu} z4pZlK+}41PzrTO~K7TS9?i2fs-?ZHU+SHYsTU%#a6N9}oTjxh>>;hC+(ndBt3=9nR z4i4WO#j&&&*Y+OOfa&M@(ydtOxjo?_BTcfLR>|9YjrDJvKW4;8a~Svi(EF~zBwaB) z{My6A!+q+HOHMVkG2O$x0IliuF1~+4$4}wc_O4WlrlrJ7xwx{DyHParH3>1X zA$N*1+lRIAWF;jfdk0r3sjdv^7%i0hgQ`(cQ9HpzpbjTiXU?9gZ2d3{8P~!MZ3mdk z>*}BrD3dIya<*trD-a-cK6exx__^XD@rrYD@Hn9!WBUq!!%K-2b}1sUQ(Fc}yb zW9G%bQ`o`U-;oM<$|))q8&7ZtYSINDCyJ{{fusO*J?hW1o3C5g{sHNRdxx_GM0{>F@JUfI|)*x+C}u4IvB4dce=_&P39o`H|<=)|Mh|^2I?m zIrtx~|45cR07m1b6e7S4m*i3HbBAyJCvh(#H)EL@(kIAl|zp zcG)*Hze><}$#L*Pu-Doz}qnaI~=%nu(nRCI5 z;F=oGhT6&|=X%c;5Ay39BsedE`t2SEPLz~oB?O$tzLK~2Sf@S`^*eFY)*!+tajL~& zWIb> z-yvD}!O}#r-tEQ7);wBV`@0iYR9^g0e^z=aL5TbBS|jn7-+m5UJ6f+6z~+APu44k( z;|CHUR^ke~#ol~Fri~1k)^V}1QZ(2;qVy~bjb68&SB=hKGR=qMQu=k_+S&wqV?tMK zda;TKb%xe*Ji31SzcsD9rQqFlZWuAC3It za@8!Vzg(2(`Qb{hd?NQ;qlY#D_FZauV25`(V0sjTyDD$S;qqxtFOPmb2>s^PVP;u zeXe~?U|=U;$p%iHhv!#8K|##gmEsZ-zw>+MW9Piy-?p5i%+JrG5HQOPN;SQ|Y3cYC zre37`0*MxJhJoVmEWqCckdv7Ct=NxKs~KM&L?_8*4-6ks3hZD)ENbjv*?6W8wzktH z`mH9Z9uibh=H})K$^0kF-8h2HzixGE?Kg^a>arA)wbaz`@$mz0A2{vK!hp;Y9i0y7 zGGnRLDhF4n6>`P2qI+~j^ z_%@rGpxXYv2RTLtGLk!ga*>Sc?}>@7+|D8W_p&uRW>`&Bs=B(uR-5Gu#kWOSS#(Fl zs#aEot>S@!5oEs;4ULV4H|&K|ZA{9Nl@p0wF5QEacg)W>3Z;Hy=iwL}=#E$RkyFhtBv4URb(mm|EiW%` zygr>-*ZDc@ub3*V!{+7VU-K}g8R#O6pz{@^k8B`Ovcs4f`qqvPDxRQs>Zkjr6s54p zI(;51iN^l?<)<`Qw~rq`{=~?%d#~6%S;C6t3R|(Pfw4I9)mUEOc~fp#A*7f z>9qkE1Ua<&)M0Dh25QBza9psU}b5k zm6eJ`Es?}$msMZy4wOG@>pc^V^^J|iZ?tA|bR4X#7?G?bJmU0UInmy+N-Gc%{Arj&A3{3070g>`|{Bh7>_+fJZN?DA-owg?z_x2WSchvPf;$X06m`K3xIK9~^HSESFCr_=fuW!1&nVFbSe{DCG zr}_jvuywIOs{-`;#+F_%Rw`=htpOYR{Y;PPeBXog1_z4nS1|!nnRwWa>%`*}^MZ~szWDPFd)=17wc#f+p)X+Chccv=<<-vKXz{$-WpCpZfgj50^Kv1oyAQg1p4?rW?KZL{K zy$P&9lUw-t^XJr*re|+IXC2<+pvdi2D~1SAivg2UNa9th&1!18%~vme^5n^Wg0QeK zhsi(^he`OCFVmnoPJ$Oza?!m#q`T03$ z2zR9QYYhu{N!2R^@>Foi09*l6{o5VuJboah@{8Jc0U`{bJiEuzeQgUh1m)p-uQ(%f zen@6n;5^Qth2Snoy2k;|^&=Z1q2e){7nkoU0F-Pv1?cytN@;l|Do1QL>8o#T9Qyg@qG z)7+dUAdGzXVXZ-?oS<*9)FUR)F>Bdg|IdLV~<{#f%e|m9^UP5+zr~ zT=Xp@Od>xgXB{S0%c~@f?Mp=ytcxLv1quTg&vUBB)KNh+g0i4s*-%A+$P}frtAHrP z`y)2@^;0FZAu=|*aXGv^ypYLnXYda+dRz?HhY${E0SAQf;-;&`OS(X+osX+2l(z<*v2K#9Bi)rG^Bm%v%# z@pN@{_3`mB(1^h_2b@N#SVme}bE%Gwo!vI%2$xTu_P;?0YClk%psmp4q@^@H1e6|NIQxy)cRdf-A-nC9V2a8`=b#>=VM3ohd&Zt$`njMby~R2# zV+Kdn*7h*1H4rGkPUHM`a$o@?Dj0_CO#Y|--Rbb{xhX63A5epCUl`C5Ogkeur zz``-cT2Uo4GZPUJ5&k_Y3M-*EPIG5tBpb*`gQ+5&Ceilx3dAW|m{1jlinO$}DJ|>A z6J1?7D{7Z#J9;H=eeC1S3!k9}`mQOIYA|63)7aJFV9ti)d@$pw{6IxNNW+8XOg&O) z5NvRtfbS|!7U$(@X=_`U=|9Wv5BJoF>wdSowzj06m_eH=og(1G5+-qUdK$lg5Zcb~ z`MWwLMYrrQO)QX1N)RBJV!fuu4yXQ@Y(*_b6-*2=K|(I`*kxKEwwsjRhZshGirHs; zXlrO}>}WwVcay95U({7Jv@p=Zq`Cf0Qe7}EvzjS?-`wJjcF*76e{5{5`9*WIEu!xS zy7Ru^rKj%<-mO%X{A&xOg@rG!tN{~TH?n-kAjdODSX!4lAw7gq+9lji1T`JRLa2Y2tz z)ve$nKz?+--{@agqHIsxI$C%IWy{G!>i9+ryQpC0*Ry)K%2LM^;7# z(4$P%^a0=8X_`o_fw?#;R3;(+EyK$Ev`GW)2nc#l7)nPbPaRJ=O#0=drS-E*-QF_! z|0N|N!d%u*lV@-`6stYf93zaL{UtQY6=tTB@Yoz1#zX&@uU(_9Q&Q-F@BZE8fJCsc zpm)(K*nonqs%LCsGeN32^8n(GjJvjeFTw>#0{`aT{@v;RhlU$ppBR~Km(Qc$gY27? zmQ`&Xa#Jdue7LT-GPc!3Q@C*()IV&zQSWpcgMkMNu zqNH#^nf(n9{_=%*jWxNorA0+WWgijfjn%B4dS+%Dz+4=DzJGo-G&Hm@a1(&}WrdxQ zg2EeMYa%&6%+PKqQUy1>x3?E4q$vQMjgE3!&sOh5%>;}827+$Y-t73$kSNmQ2OqJ< zsjcU0bJNm(rsV9-*VMTjt%4H6%v?B@ukLl-KcRDSop5@Sj*CSd9Ylk@(G^AgNAdtI z#y=nc7$dLgV|UjHAp4~;WJ;x@Kb^cbsYw-%QbFV+VbS_OB8;Eb`;C+LG zgJly~tk&p&#{zrkbzT-qr>` zKd1Bqo+F^B!?`p7+Llnhy{z16CRte6N=ZH5 z-=C~^vD@C&09G~@R;f{Md{&Y0|a2H+}`tjqSnr>xK2-nQgX=c&&f$Byee1ePJ_>=@)iKn_jfaUv54E6m6^ zZoMGi-rhE@=`%M`spPH%8pnVhKL@Pu)ZIXUGAubOKUX!jOEOuXZbxZIu0o&^j)b2z_^Mv z#-$qU0u0Q|ahyhmhBRh*@_<&Op`f|lT=5A{JWS;>0aBBdmB&F#JGhUpm6gHLn7qls z{?DE-K)n%ZOwAyu1i%F#UNp3{Xk>!b0{jml$M@u0vgXkj8?2L38ttd z={hIYh)w{pw5qIi0<}=kky)7U9%$&uDI6x~y4C;M@b5UUu>cDTi;s`bn>)`I@gZVO zb#*xhC#RQyoQISx8m|*kr@ht{M{R1JmxqSpsf?%0IN>{=%wAy89^=)tS(dvMmz5Vtp zWndrOwrrqCK9U5p!Tk^c+SNa21)QYWxmkHcSb;|4yUN~e?itOUx@jN_a=BA{83&kT|Mpb>pT2=KL>yYtg8!Uz>u#%*@na5*G(8t$%XIZX=}% zM`VsLQcQCj8xtp!3Q$9;41hOVM8knG*J$U~0)GtZ`EDtbKZTM9MSD{qCD8j7EOOU7 z`bmUfnV5f?KKsdg?&$lVhgX>!xn*+!x!w=B?(bi{w~;&`qseRR8<;N=NU3}bf#cE- z{Jc+gbmw&Zzp!ST3CB$Ik3dX6nb?iMnqQ!AfX$<$LB3Xpgoc2K#{ADhpc&K}ycVFz z6Gj&!lyJVq@G5_F8u8~vLt1qlFPl8C0YP$Qk8X^-gAs{mvvsyBJprlQ?`viicX!oq&%x78Nl2ZTrn?`8 zDAN?Hj03h(RmiD;djvG#jMusMeC}})7lLGi3Dw4hc!lkXj4VoT?1C}~2HjzVww-1i z)jb9lmdes=T=GVRe})ydwzfgyzd=MBR8_u&T-_$mxtJFKu0)n>hy?tM;v-T0<@-PC|VB2fwm2wdgv$RR2p>bZNj7_$8fA^^Gq z9o;JMk^v$C6+O`3KQ||*d=?b6e`ndhlgWTw=d`_hcz6gTRLh82Yiny3QtWwz(C(I* z?*GIMT%4SS-1F=Z&_IcaFJ`6|Frj5{j2}LD5ZS#PUpKO{Fs9(hcYp(07cmLR60=+)KQoZXdkeRBb}*js4%z-V2Dza-TK>wz;~G>= zX4d?2$K2e?%5nYl)>cW36;76hv^l~$z|vrMy!Cro+1#7CNwe+vx^EOf*8+(rz-odE z0CoYTDp(xQ@ZsTMb5qku5wV8S(u4L;f}5KgK9B$&AK{ER$ZJX+RTUu z&CT`+3OvK`#L}*kumtejYIc_wpt$khpk+rJp$9Ip{k%s> zgCF{mO35N7`>*I${EBlt6iF#}G#~)+981{af|H4J8yMd30=(|`X%!uraq;oQKy2>? zlGdBurfzLlCtS%gEWDJQSK8DJ^&gXy=Ynyb0;v)o2WLe204$uBF(tWGV$Q`R+1Yop z<=}!1yyT3pW<_7A)(=?BA|Ku;Dk>^~4LX1FRyN%08}zJ-tP5O?mpG^Sd3nIr)6&xD zF{{^v0BIADW_S>Th)8Pl+j6xsn;&ebMe6tV&7zYo%F4@Q^}r5_GHvRO3q=pl+qp9{ zGqbR;FaQmJRH~-7w$P)y#sZ0^t1v?O#nZ%C>Idbbr=lC zVL%@S!tl4OEN(z_02Q~HsVp=xcmSb)kQOT$LB@RJ`MfwqoplAMNfBg1(M99(QTEq{ z$X|KEymh^B#bx)Gqr9Wyu=I?KyZMV7a<#DmVo7goZNH~@9$7pGJ0cHHPm6L2zz3k$ z&Dr(R+7-5AozG!F8x|!W`aiW(;6ry&{Ad3Jo^@nyg^K;sYFJ9&YotGZHO!Tlr28IF zd$lVLsS^``*!lphm*g9yR|&r2V+m7JGY*|2-1i`<2}%JnLg~$4Va#8@cl&)O9dj<&R%(S^<>9*mWAWjIbc%& zY(8yEWRDQjyj;Ipp7c7cci5llVJ+sToF7dYWwC~u3fGU$$U!Crlz^yiLji$0YRQOS zEB!Yi3H9ItgQ!Fmk$&sD??A->IU52@D7YT~mXB`Z!9w<*sj?zB6Bq`CRalse6*pKM z__@SF=H>o1{>8-r(_0Xcqz-}@3$ZbmCGZEoex(pjjd(OPI_jrb8LumWG}mBvhLX+t z2{lY2?1{tG|#goMTN$cGHv`>9xFw|AxC-?RD1!9nR?rc^}e%6phk<})daXZ`X zo?_*@oM9gYcqM|EucvFswBwj)YAV@Fmp8?r1b?527C%&5&3dW@1Z$GfK|7#O@;PgL zCT%Z<{2YHEv%i@>ZRU1$8m!9liaDXBxH$u@B0o+4KNN7XlwW|bwm0u_U-QT+RKeE$R+ka8`fw+7iAT>nC!SHZM zeQ#=dJ~~Y^L4X&hnyv%bU4M z)TX8`0KeY$G+*eF(d>4-UmL_iZei!yOEjG)TjNeFaPC1k7Se46f?%8Fd=rLzPtxIzliZf zktn<2xkhP8g~05tl(3v5EJ^tJp#Aie;$C+}?M5W$?@N4g-z zPfEWHPbL5y z#fc6NSJ2m|6mIFd%X1SRI{mi@I3H<6CBb@AEBd+E1DH@{ZpF2E;TwgVWD2`%xO?M) z=#udYFjSaPeu_+FetJYKOMv{~BMU0_vC#W4WD4MHj*hBybL9abwZSwt?IPkgbhhAe zr?Y^dF&Hf=sg;qDe}cBA=FVJQLgx}59$uk#Rguth5X4heRR!Oo2bPU{6=t~qdz`xk z0Kv=4%eB>2@P-8iOp1zw@o2Tx)ejI6ZEJvHK!QuS{QEbR2!xfjxasCXmL8YN4~g)F zrz9~kG04{KOcjTpPG*gt=2slDJjR>Q+T`M7v(%i|if34i=aiKNyXQcO)xoA*&wnfa za6TsK+T!R_YTH?8(*l0!4%nOtE-e5F!E>ac36hqf8e2`=+;nQPvKHqdD-%V!7G=5r zmbu;naLc(Sp^Bh5g~#Od30cHiT?YZ{zeTT&S>F7+Gf^B(?hW(<;G?4lf)vSjw=?T= z(!zbs=LQ#_0Dr)!DWEFr|6XE*3gTIC7bs-eyBpU-Gtko< zKH3P1p;SaBCe%vT5y3-O-NU^4v+}1@U2YzZ&Oo*3Qe3&Lb=kM<57$|op~NDd?q%2z zVL|_2xkXb_qyumt$B*IeU{*DL;;JN=IfDEyKa(8d&KDcW))M3eV=bJkk z%Y0`uHDDLTK6TbH`?@ggEcVq6793$#7U7tXP|2YI!#&5*Mkt14P!0g?|LMwYVB-2k zCO7CdS@M}_ z^fxP4w=7JOs#^=z$yMJ1V6kM_6C*=YPB1rtWE=ki)Oyf!V2Lmca_+0?F$=Ie0Td%3 z;-2l!wKA69IzKHd9)AZmv4~v5V>10$z?Lmwb#82*RUhpy#`ZD2q`}?;R-V(qtRwfz zf9uj@fPSQ;q}Ve&6QWMmZfQrs`5)aq!)@ct4`C4m+_bp~fu8&H=~~YiBcRXSJ$)g; z;V*8lbcPQ<2Vou_9B5X!st)M!nGB@3z}M4~!oOG{@h`UyQXTsoVSepAewB`liaJYJ zuS9`~@K+6(8@oF`Nlv+AEy2M^ZGDN7Vs!f6;m`svE0mdze^qrpA9!eQYFt-V!xDGS zJsoStzpEtrGIW<94vqlln_R%L07yl^8^I8{qL@nlk9|{uj>j6#&eb4p6efX!j9v@W z)ZSA-u)sDEV5IvtBRxiwR#33Elbnxjbh$ib4s8z!2{HGQ{CZ6x>q92-&pSQ-vKyaO z2{BhXhV_{E5B{R`eU8fLri_nC=&e<+`41tWyVuy_AmN`S`FM++j!9B}_pZ@VT*}zw zK9?ZCI*d^-sa|VM(fBAeDn~z57&G*Z*qm zE1;tK*R{t4L1B~<0TDz}0cmN6ly0O&qy-d)lwm;WkWf0MK@pU06#)Th>5}g5{$Bjw zbM86!+;i8rzWcdYd(9vWd;j*{Z#?hwJlnVFaGO2Il)w`iE@K5SM;~9jf)p+k?6bxV z_xS$;C>~HPYvZe!806FfQ7)}%;D7+_qY#+(Ed}YH^hnlE5u3kHYosg7$C7TC?@`x^%cE=M&pP)=UA87$G_}) z9v>~!ig|5ue010pac6yFBQPMKMppYeeJZ5Wlc__Mu7(CZQ+=>FNEbetccyb!S;iE) zQ}?V)Zc+B|1F`|b)&Pp-RWdS$Hw`dsVpIUe<&R+x080SB<>leIf^xaK`N+oXi~A`Ur(Pqt z^sJ6>Lqf_!hBj19LxXi^|D~4=YsK?%NhrF^7uiPvg|VA%0fGSlTp(5Mn6wBMFDSa+ z)1UgPULd^+mZ&yh3}6;rRx&?v?S*Ht=x28%F56sY_^4PwZ7ss)l#nsKPWZ^Hl7z=$ zu($NS9fLv?W1e%7HNk~TtMRiG*REKI3<@7Ct|Wa)iXf4cl;|HA*xv=R{a(=3d$hD1 zFDuWuRk^*Rd3SR`iq1w3WSIgl#73KH&Y0l*KHiB@t)k0vwNj};i8Gl|Q|YDPB~Vl_ zK4}NO(*>Fx&aso!MjZgLA(wt*f zqTA-ACeV;rSy=Lmi-Qd)M9e$rCPxm}TSXvVR|)TO)6hLq5Y^SCw0C|r>mMo8+Lgec zQ4HP*TgwC0*yAHuAfVgk=$3Z$v_>Mo35RIxLkGa-PlVa*(iD2JrAu1$6!Xo6${;0K zsdx(%1DIIP}r5;u8LOi<^)&X!#28|M6BN?N=s1Y-`31;UOUx}VW@F#DX;8kwnmYt8= z1MQ+hjXxfOKYmSUMs5Hhmv>UXD2OHf+pW7so8r<9?BN8F@VRqk9`JdHIPbrgrKhKdEwt~RNX2&yow+N{t{@0jjm~&0i^d+v$jJ$!cfcp*eWOF{Z{>U|FY+_- z_mfVea6@HM;R8I8{4(Se6p)`{qoQ{9_C5=4^$ZRkOlUGpS8z!A#H#~?T0x3_(yGk^ zKtV$>pHB;4h<^f4LEmLIt!0+@>b1DNSJQ7(h%1|qSdyLE>n4Jr_GCV33ur@s3^fl8 zHS5t`>FViGorLc%SMDdgNqJKqyDvqJ5nmqp_3Ld|7>D7(St*?Q%*;%<8zQW-a^AF# zvk>FW9hpyBp?=g325_4GETDMI>o&M^(xwI z54+n8)Gqm|B;7m9>{Jw|LXj326a;b&dZ8)CJK(u!Il zH05W4CYvIq#3hMGSdYe<*goo)H+FKDU9al{hw?m5z=MI-Kjlk$J+S-~EWxoB>HjpoTeAVs9T!`{`vk%{MAn<5nqa)O7qyDMlOWf=NCetCRJRA=-W2@)k;o+iWZ>v2)zv$d19m!C$|gJV~9ZR+6*l z=qtvKz^x2HyEcFn$lXAgy~cAkM@5S;OYi!p57C+S;albB&f|ID;nJiIDf@rkFp$lV zk1b++a#`=OmDM3Ix`9QFL;=64etb34_Ij{Xe-yRdmiV3-Fv^~okitZOUoE zUz+w8M>ckiOESmHH|LDDNPp44|PW|M?*kk<`gF(qCV2 z>C0J`KR^H1Z~eb*C-yj>5!|HO7&?b=C%B))@r0xFLuQHeNF|`n$iHIjzYC}}dRXs3 z)8%nsDh^Hq5p} z?2cfS#@4LhSo2uzyMTeidGvIj)aB2NPoQ2}v_2=wwj%gH#OX{*q9)_X_@M@kRcF_1 ziVexgm3nGyYDPvPsNwP{WCla{F#Q_43x)=Ta zMJ<>?nKrY?)3wsXR()Hm;(J58`GjvMlmeg~5KspjtP&>Y&XP~#u#B#to>VggbWcX6 z5e9#hRFo;n$rhHDS!)5MWTq`q=Dgpn4@y=+s;g{{Vk91}d=ZHR*TZwJ_q{4!B5kKz zsNN|_9~J*dF$nK*1$ypP5$lQ}WVw6aDG7(}mB-gk zC)SUEd?>2j=&bAg_cv)hL7Y*Cji{zS^CV_8BI;T^w`Dh!`dZ$EU$}AAR-WEV<2lJ; zfCim!z0{qTeM>f?qI9O&vL{z>ch#S})dkopYA56UR_PtoMjGf=I;f{zpr)qw6G!1Tw6rV%)2t<$5+$v# zKMb}OGnh%VmO4#0>FiboD7N~ zj!PbJ3T0qQM;#&Z)Lz^(rlzL+`5#<0ckXh}EiJjtci)K^J@f3+X%ghOyoin` z$`U!+GlfksC}WV1c><7$iAj;m=G^*pi)uNQkP8|!wg+?ZsMy%t{YO;lPXC%ek_A+0 zazS7bDtZGMP|JL0Z)Zn3O$8<0G8@-c`|e(b9WITE->>?5A50*%^}(~Yr2CL3FX7{t zKOaBelwcwC2^>OR;Sd6Ni9}U3AMo(;MH%CIhIo3O-BY{1?~6+V_$dVRn2RCi-a3yh zetr9vTvir?`H0{<_a|Lol>HBEx=^`#KvSNs<{xSQ8rnJ@OnDc2+OU3k4c7epeAV3{ zc$cNGiqm)T^X1->^3upi5uL+@f$C*|&VZR41(fKfeSA;UsA3P+1qAhQCNw`hd_LJ2 z4s{9Wm{m>d9s;PSOo7?kaNgTsI@xhUkl~GvKA($Kg^ma+q$;;{d+|^*yu7R|eOb2Jd438sg~=(&>TXRC-&nk04b0+Bp)E9zY32<0)%0Qd=Jh+*W49q|%nBnX}qItovMOEhd z_28W4u)#W{XCSONDGREX{A*5Bp8ThLs6u1|e16DZ73nUQQ6MRTm)Z&26zq>gp&z0! zx&KNTbUeUrq6227lRo=CQmueL8E#0HM4l%n`^ja)1xH#JNFLLC7Qt{s=_@CduB`HR zo$r40M2kKHt@sJkv%3dp9va$s1yS)jSho%O5z*s4O^7GL|Ciq7M)qK94;vR9+VjwN zBB%j+Et@`n;5>rxvxk@s<=aD?3qWXu2eYfQ)0YMJEsS@hIk9I5v}G{j>dYXI_D)Lf zy4;*7A|k+|!x~x4FB(y0-jS)x+S<~Xe&&v7WZSsQs1QDWz!Ao!yQeE}p+0}AVS*#x z9={-2e)*ccr2*J|tZDlrlXq>i1^Ax*76nFY47X*0em*!9mZ<0PVsg7JHAt^rnOhj6 zq{^AvxanTR7l>wLHrYSx>-`Ujn0m*{G}E2SdF@H~l_YSoSr{(dcdA=$$rPlgPv^{5 zZ}0O!CUL@Q8QkEzJG#CDMd3VA4@`6B%h?b^vYM5W_G=RlG|76l^gNXI0W{Y#k_>LQ z(N_7*F_&HiqifX>s#7x#1=N`!U{=!USxYNzyBg0N3n$e!&Wf(BjEeD325KZX z0hG>fvJmyU2b!P$v_sL&@o;LR$40mB+?%u5!<`eEUAVQIUXZPeV2d}djeiQHW|t&W7#2sBRrF=dxc^&$Q6 zZD4qyLAhQq+bRB>mYJI&vA%j`$m>rr3%qNx%2H|Ca3m^SF5$DyaTPS8%GCl**lt2i zHwYwN-!?YTI5;?yja|Z|!NVpW8dXbs+gd=#q;1l9a)CgJo12_mNM|BT-l*~QC+6rtYwb7u z00a^BFo06+l?;bG77=kP-=ax!dvT=LzDeWL!>6ms5-1d^>DL9hoRI1%PUCE9L{jX` z_mp9{3gE0ltey=64Rad>bSnKOGV@tHK}-Va+0eKQZ5-}z>*PrNIZrg$X4A&7En+Cc z+T4;{#uII%(>g$lx4u1sg}c<=&MpN@%;tY$yi+QuPb}iNq?;YWl#|iVmA(*_4btG` zSW-XnobHs^pV`+;$hz1UnGo*pI*zicc}c56EAH-sIuZU{BH#1J{aajnQc%ez+%qx( zSmx2A-~PTNYKGEgzA&~klQx4p6LiZkKYtMJQ%Rzsqba~^W(~u8HS}A0c}{hb%j7iv zM9zwvOMUs%FeE7*nf}~!bC_12qI%0f_V|R~MM^4}QK&I+D_t_{Z4!I~3M`BD5RSs+ z(Il{3yD)Ss&A3eG6hEPLIs!4&xz|iw!a?{~8~>k0Av&a1x!RAmHnuiEjLfoxv&UB3 zB27d)i|&c?51@4M>l2zqE`81zub{414sEEZdFAHtSYQ7-&3DxwJ80o>u`^`Dj|&qE z<^%WcO$Lh`Nh>PCno(|A)eAH<8#;v0zxt=ja5h~t>4Mulu*-(8`FXW9p`sYx zwV%=VpD@`f=F?o*82d6>ifkjGx(=T1a%bJ zLmU2}W?`=%LND}7b&mTT>@f7JSNuGYVZgou&1W3vJ-^98pFij0;o}vjSsRhZ_gipBpM|PG^R&S|^a`@@l{oF{ z%F0!~XE$VNr6yg2bEblB-)2murcXjGW|~_FwAn^cJiTt$iF$ig9&KRbir`n?@_ljJ3se-t$Ce ziT86n2=|AW!DrT%1mXa1FRvlE`I!ZukBI{}F&O%Crwj8$ppAXE8h3Ye;~ZSXls`_M z5uhS)0kjT6PAfMy+uUt9t1l4{m_(mKNyBepn1eB>zgKv{F?AiMjB^~^XECO8qJM`i zxEC!bthRMJzupIZx{_A2y8tp1>>p+}Rhv*-2B~(vs>jOBt*wd2bFRRf77iA%{SN+F zG8ViMTz;>fp6|nb3TdFJDe+L#apVXCa;vzzm^$!wnV~pq23KOnyoeB3(|44-QsjXUnWBcRu7#`p`wdUb!qs)kb${Qa%zxKmgNSf2O%tRX3DQ z(RjA?q*+)bV3x|k&@qQB(^sK`4hoL`zCH+~*k75-CMG7s>5-9<{g{4OdSfL>TmmeyM~P>j zc`n%=dMp*r>ZMpOHZ?b^>&sq7aTzGwe@{Tkr=+c2X3~5urw3>w_|_x~@+0pzW6HzZ zLX=F&rf1B!GkEXY>HYrd*CwHS${ZRP=37a3pwcgQTog`tUct#6?B_QdXi-if?qZEf zdRZ;{Y>v+L`pAzTW|%RT6Sz5v20#`)QSF=V4mHihpe8rR1skyce_N-YJ|1>gXOos$ zfs>DWI-KTBgK>-?@*2Xu2|RWX2);`m02BRpTgO9kj#ZOSRpp2_jMZ?37JXzEvCJ)% zO0;A>G$?-|Gk&$c>IfGlRueXz2&UTROl(O>n~ zjIV5*2*iefRKW*(o1>M^k8eoAR`}4viAI@ElvlEi86l{XM;W z)T8zNWqTOC=@l|(B#-&M-ar2+Brmyx^(=FZRBoo)_c}aP71nqWtid}ofB!W>TRNxy zf-_~y*WE|WjU~D~MZ0)l5D;uGF*&|&iX^c6?$FZ z;O{a9E2|lbiPhlD3-dXzrGoYpv}dyp(T$}SVMXCE2qg(G+OL`>HrqYB`cQe5jx&EEh_|Zkc|GD{IP3DBKs*K_W?y56KXPnh;;kM3^kk#~ zy{GGHEm^taa(d(V*x140Vb(wF+-oWpK8FvY+lQyvoyv5TL0ARc9u<3#T!V?u&$xSU zkSfZ`U#HizY+-s^rk8HZda{~1`YERoiyeJ>*yG(+EI7^V3^|U}*43%p0*DmO4|acG z1X2bV$gw}|Wk>yh!t$>bX-(m9g>`ePWzo0aZ9J+LlX!#DF*q^qf&o)dEbmP2J-ey6 zgT2(`2csfQB1}vV=p!2@K3{r3F#|hYxKqoqwe{6MTj1fqpd2pEpO4wN-7e@#_!DifeMwWvehXG;FU{ zY~3rn7IEm*Eg0d9V|~SjG@$nJ9M&0;B+F4Mw`i!!97^zsBH4#vRtX|!j9_r1VXBz& z^UF;Hm3vV_A1%QK<6Bns5*WHz_7|7~Br>|b3wAR{t=J>6a8nZ#7iVW6;DeJ8^o?Lk zjjBF8%g!$EPhWG?u@`7RuZo4y$|O^qUYei7z-MFGr%xBa_yDqm1a12_FZ}A=DGJmN z81sQ8S7l`-P|S<1hxUT82jegw+*`WsKHAotRRk>f>^s%y*)Rg_9@a%1X5rx6gF9pd zo#=v@)V!~gX5YuaR_T`{XlZz?`eFQ=Y10}nAhk?HMO6u7QXw?KmuIu|MB)kJm|>Bn z>33#Xsv2s#0v2{TB9!Q*!BQ}c$|4qqc6xtn5kkGo>=pNSV@J*Sx+wvcYSjm8O<}dN z&|pfw2ENOcJuop-orE0)j4y~npv5q?2JS975*}{uwA56&t0G_9zuskL&Mqp7V6KjO z@q8*U+CglgE|>y4v2r<_9$+W9>@U#3IQ9#f+7A|78t^CrcI*T;KyXjpx!Lje7mwBP zx`9id@S~`d)HW7A>tisL(sbZf@91a;1Kbm*)Y8Vsp=sLk^zX{b`fK~V5`usi2kks? z%kMTL{t5CV>YxYON3g$tf1zchts#J-&;%8OmK=mzuhj?;%*5Ub^PK4cqJ>Wy!g$Fl zofr2CSw!?dO=;Qm=6NuW08bfk%wQN@?fNu*KE?#@zflwh=}`S^>T>k1ijmlO`h>jN ziF4IW+ME71WZ2b7GR<5)b@Ojv=tdigM1d{?TKaH1dLLw1WF&B2xLl|$^we2dH1!|p z{clU@&WlJ?s*1UZ$yCjnPmY4`6v#M`SFgge7ZQW6-bIyGKv`c_#t$-3I=a-M@H3*a zd#`WC!vS|QdH8GVw*>@3^`sEP)d0o8&!0~>=enj_<1-ut08HEazA5H?@h&f~!S+}F z?3I;wHGo*1K$pC{WVHccWF-*};X?BV%XJ|aIwUTb49WyR=b)1H78z#8&wzez)-p?p z@>QeVTwOI>p7Ws)0fRs#nAd=+8AQqVNfP*;)6&p*oRrcPo|_jK6yj!f;xgks(_|wd zsN|ivm#tY%6@UHu^){y{F1S}aHSFDzrxzE4vy53R?1AF@uG_&*ga|h$XTa;%RSt{V z`^-3q!VM}Z{`hWFIkKKiXuSVw+fx8UMLvG|R4%-!(HNX4n5%U(GUfvFC45=cYAoOkisT|{Ovk6TVvd0Xww*wzLB zsmkrjKNt)%u)|LM4wG`-h6fbl1kS1t}bqjfeW-?@cAhz z$=Jr@2fGoO6M7{ILPRN3nyZiB$f zc6WELK0aIq%UZU|jW93~0^Gh|n-jslO6+2>X82Gk9Rdt6Yaw|o10JcR)hF<2xor}E zitXY0Q4H*cnxz0+3^qlOQeXwhg`e=!-kdEHwlu|104{I=md%tAnNa0Q43VpaJ~;Q{hSmUx$7_ z?Co1(LAEH&d4OYSaxARh0lCLB1cW)qamMlOXeusgQ^( z5pH)YIfQ!ogi7b!t7B+X9~4i)uj{PwK$+tG&VjCYdX$d3&+7-`yS4iaT1=v_3P{c?uT1~DCs9^sgNlWa}yJD zM|?xUjOJ$M8sx&(M1kdn2|-}9tjQ0=s5Nn2_#1Ys%FFoq=Sj7G7V(AsMo;1q72mTY zIA-vS(#|C>Jwj}FG4)WlakXyt zV|z>B?JGs2G)rVFa0<<<;-@yE*LF_vo$0)Q`3Ke0;ZFG_d3n!ThlOGmeijCP)SeC2 zV7{P5JT^9Wg^FtEs|&(i6)fCRmRZpsnN-uwwn<$Vak52@c5Vs@#kYJJS{38zp^ers zCfohYILG9YsZ4jvFAtC$!Z-*D1ZK^$e#wD&#mZN&KIi2nh4r3x z|7xbfMZOgY(QUuy#MBTZZEz{-{OhNjik3@{W6`rpS!~G|{(2wSV0R@jh?!*m@rfuy zfT-bkPi7(Ei4QYr(m#wcmp14$Uu$`r5qBZDKE*B#=mrLh!dA^0A9LrWDIfBZ`0uXTclASA|z z--&D**b9);s!_LnQyjw`cT2HU`*P@IxOInBD`9pzJ2{!=kXSUO+S8acVe9Om!E?An zh+w~HRmS4c&(6uK17?fBhbNZz_pbAX%n$VxzkmR+ibal=jV!1sC}QD;2NMgJEPhz{ zU}Zi4W`cFW)NG+LnN)KL-QYr{3`3`D++eEQC6ohh8Q>Z7RE7izRwhc`N= zt*4ndK1@jOHYwR4b8l>wCWX*K6-o>huNNl}7Ja&}^Ay}>FUlYxUR``=cuQ>2`^a{g z3?&WcW^Z@{C?srE2alH%DyP}v6lG)p3(zTc7#cZ$vqM7x?q@DTKF33#hXwHH)iSZN zf*kn>ST~xv`k3`8`Rl>k;CCKr;1-FfkX*oMrh{ary}(aFpk>xO&45z% zRLg0$<5rGC2EHgT@YN+gYcHyTg=pudRFy9P+fK!tNYpdW}fWq=SaS~4Nn5r3Xf z^S|Prbi*G8atwbiH7G1C%&%oWvDH$3uAJd;;+b^oe`d%0JJyWRPdOm3*!K|*trNR2 zV+V%n@DX|n#X@qQ(vm1p^|@0r+Y_v;}h^dzF#5zyn3=^zW_PXeqwuo?rb@69G5QJuq$)TH;IrO#Qf{vS(_A%@#Sl`C~!pRBb1vW4}y$MXip(X<5Ixu0=8)ao= zP89kJ3xeQzd*X}awjabyhI{t-XuBH9V-fUB8&sh$U!KlBfWi$Pivp!-1w6d~pxUf& zz-5E9pb*9WwV{CtoOD5tgl;Y5J~>gh<;5ajE=zTL|0;TL-w zSsG=jkA&oAX_Ua=QxW=}jbbnZ-=ED$oB?+sTRNZ;0Z#zdd_I+cv4Hrb69M*N|7Taw zM8fR13=AKn=^(&BPlZk)W7XY(*b3^+Dez`nDqmKa6mVE5aot@5B?u6S_Se>IvsX^~ zPMI{R*Oa^P6JXai%h+4BofeNJLVUTqxU;VLLi7={`Q{HX+}$JS(08X|VJ5o;M+vG` z?b)!Go9t;ms#pMSz-t&i2^)C1QM_EWzm$>a);PT`kY!J5WuwBBx?A@vQYL)eyi|6sU1UND}(0;{Yr3d zc7jfFah?Hj^`h0`E;K}0Ia+USDxUC?DZzZWx%y}`cV*>S4%+XWPLhQhmxCk}?hz#6 zBrqS{D@y+S4J4=5mP0u6w6wHa>C05Yu1=-}Ey+3K;P&|8Ag2TTjnWPPGl}K3Vr66m zr#TwWs}vNS+|_))B`nHKa}&TdD6I&lq+cYHI`%;H3bEH1B@GN#uwDX}!AdcPN;bq7 zz@*pNm|0bhx(!p$sOQs5UB8|vWwn0pz~3K_iZXN~FWL6_^Lv0a9UqPyD~}`y*>%^} zdRepZ@%4W1eKJacFEYIO3GAXw(e4j)>gUJCY=OF@IbEHta-D>nWJ{wrB1Z?%hV7B# zBh4A6R7(x>mwVm){d&VCW1!fC0$325WRMyyS8ofe>x0`Uur>87UEu74U^*FXt(ivntBxhNBCJGs6_~Q% z@sESKZwat|GI)qSXn@WZ2JnxQjeco#g#HOxuSKwhIA_ztG=c>^LkQmpzdhmbFVv(G zf(LC{I4e^Lh+T$<;g)clkH`VJCtwDz4Jbgsn!97!mT=e3JM@z>0BsGiVGMFnZRA5{ zSvliyhIjhyEjx?SYU{p()gqf#nP)bQn^lEVJ|7Rg13*IHK_&RcPV#G6^4>JXv8&4d zllIsE;Ni^3fLF01u3uo_ZR|tnzxTTbZ0@R`Mhy8B9UbU&r|gEB4(>c(H;5nU?VXvQ zE@ylaO7L3xpO~RRYlNv+vKEm8E!O%55k7314GbfHqB#K>fvQn`;Nj|AaJ;fzQ;k}# zuu1UzHJE9Ipk_<|3hE`#qB=0t5G6emQ0deUQo~xzl*O#5*g8cw1n>~-kF@XIc3inJ zc;~eGQmrmkapt?Cq82VB>p>(A;$oNv0UOml+-d!vi=-a@`DFu6r;?gx6MUG?W>5c{ zH$|c(Z2mo3A|*-XL1c)sxFzSP#|M<)ukU`nJu&66>>u|*$A6t>v@Y+>ch$=UA3_3D zn%7El(d|hZaf3IRU9XU~l`9EVMEFISTO_2D=Triangle(algo=smeshBuilder.MEFISTO) LengthFromEdges=LengthFromEdges() MaxElementArea=MaxElementArea(SetMaxElementArea()) - ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetEdges(1),SetEdges(2)) + ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetEdges(1),SetEdges(2),SetGroupName()) @@ -335,7 +335,7 @@ Quadrangle_2D=Quadrangle(algo=smeshBuilder.QUADRANGLE) QuadrangleParams=QuadrangleParameters(SetQuadType(),SetTriaVertex(),SetEnforcedNodes(1),SetEnforcedNodes(2),SetCorners()) - ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetIgnoreEdges()) + ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetEdges(1),SetEdges(2),SetGroupName()) @@ -351,7 +351,7 @@ dim ="2"> QuadFromMedialAxis_1D2D=Quadrangle(algo=smeshBuilder.QUAD_MA_PROJ) - ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetIgnoreEdges()) + ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetEdges(1),SetEdges(2),SetGroupName()) NumberOfLayers2D=NumberOfLayers(SetNumberOfLayers()) @@ -367,7 +367,7 @@ dim ="2"> PolygonPerFace_2D=Polygon() - ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetIgnoreEdges()) + ViscousLayers2D=ViscousLayers2D(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetEdges(1),SetEdges(2),SetGroupName()) @@ -395,7 +395,7 @@ dim ="3"> Hexa_3D=Hexahedron(algo=smeshBuilder.Hexa) - ViscousLayers=ViscousLayers(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetFaces(1),SetFaces(2),SetMethod()) + ViscousLayers=ViscousLayers(SetTotalThickness(),SetNumberLayers(),SetStretchFactor(),SetFaces(1),SetFaces(2),SetMethod(),SetGroupName()) diff --git a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx index 5078f0df7..521966bf7 100644 --- a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx +++ b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx @@ -185,10 +185,11 @@ QFrame* SMESHGUI_GenericHypothesisCreator::buildStdFrame() GroupC1Layout->setMargin( MARGIN ); ListOfStdParams::const_iterator anIt = params.begin(), aLast = params.end(); - for( int i=0; anIt!=aLast; anIt++, i++ ) + for( int i = 0; anIt != aLast; anIt++, i++ ) { - QLabel* lab = new QLabel( (*anIt).myName, GroupC1 ); - GroupC1Layout->addWidget( lab, i, 0 ); + QLabel* lab = anIt->hasName() ? new QLabel( anIt->myName, GroupC1 ) : NULL; + if ( lab ) + GroupC1Layout->addWidget( lab, i, 0 ); myParamLabels << lab; QWidget* w = getCustomWidget( *anIt, GroupC1, i ); @@ -251,9 +252,12 @@ QFrame* SMESHGUI_GenericHypothesisCreator::buildStdFrame() default:; } // switch( (*anIt).myValue.type() ) - if( w ) + if ( w ) { - GroupC1Layout->addWidget( w, i, 1 ); + if ( lab ) + GroupC1Layout->addWidget( w, i, 1 ); + else + GroupC1Layout->addWidget( w, i, 0, 1, 2 ); changeWidgets().append( w ); } } diff --git a/src/SMESHGUI/SMESHGUI_Hypotheses.h b/src/SMESHGUI/SMESHGUI_Hypotheses.h index 454ac1ae3..b50f5f128 100644 --- a/src/SMESHGUI/SMESHGUI_Hypotheses.h +++ b/src/SMESHGUI/SMESHGUI_Hypotheses.h @@ -92,6 +92,8 @@ protected: const char* text() const { ((QByteArray&) myTextAsBytes) = myText.toUtf8(); return myTextAsBytes.constData(); } + void setNoName() { myName.clear(); } // ==> widget occupies both columns + bool hasName() const { return !myName.isEmpty(); } }; typedef QList ListOfStdParams; diff --git a/src/SMESH_SWIG/smesh_algorithm.py b/src/SMESH_SWIG/smesh_algorithm.py index f14fa318d..7603ab2bc 100644 --- a/src/SMESH_SWIG/smesh_algorithm.py +++ b/src/SMESH_SWIG/smesh_algorithm.py @@ -290,7 +290,8 @@ class Mesh_Algorithm: return shape.GetStudyEntry() def ViscousLayers(self, thickness, numberOfLayers, stretchFactor, - faces=[], isFacesToIgnore=True, extrMethod=StdMeshers.SURF_OFFSET_SMOOTH ): + faces=[], isFacesToIgnore=True, + extrMethod=StdMeshers.SURF_OFFSET_SMOOTH, groupName=""): """ Defines "ViscousLayers" hypothesis to give parameters of layers of prisms to build near mesh boundary. This hypothesis can be used by several 3D algorithms: @@ -319,8 +320,17 @@ class Mesh_Algorithm: - StdMeshers.NODE_OFFSET method extrudes nodes along average normal of surrounding mesh faces by the layers thickness. Thickness of layers can be limited to avoid creation of invalid prisms. + groupName: name of a group to contain elements of layers. If not provided, + no group is created. The group is created upon mesh generation. + It can be retrieved by calling + :: + + group = mesh.GetGroupByName( groupName, SMESH.VOLUME )[0] + + Returns: + StdMeshers.StdMeshers_ViscousLayers hypothesis """ - + if not isinstance(self.algo, SMESH._objref_SMESH_3D_Algo): raise TypeError("ViscousLayers are supported by 3D algorithms only") if not "ViscousLayers" in self.GetCompatibleHypothesis(): @@ -342,11 +352,12 @@ class Mesh_Algorithm: hyp.SetStretchFactor( stretchFactor ) hyp.SetFaces( faces, isFacesToIgnore ) hyp.SetMethod( extrMethod ) + hyp.SetGroupName( groupName ) self.mesh.AddHypothesis( hyp, self.geom ) return hyp def ViscousLayers2D(self, thickness, numberOfLayers, stretchFactor, - edges=[], isEdgesToIgnore=True ): + edges=[], isEdgesToIgnore=True, groupName="" ): """ Defines "ViscousLayers2D" hypothesis to give parameters of layers of quadrilateral elements to build near mesh boundary. This hypothesis can be used by several 2D algorithms: @@ -361,6 +372,15 @@ class Mesh_Algorithm: the value of **isEdgesToIgnore** parameter. isEdgesToIgnore: if *True*, the Viscous layers are not generated on the edges specified by the previous parameter (**edges**). + groupName: name of a group to contain elements of layers. If not provided, + no group is created. The group is created upon mesh generation. + It can be retrieved by calling + :: + + group = mesh.GetGroupByName( groupName, SMESH.FACE )[0] + + Returns: + StdMeshers.StdMeshers_ViscousLayers2D hypothesis """ if not isinstance(self.algo, SMESH._objref_SMESH_2D_Algo): @@ -383,6 +403,7 @@ class Mesh_Algorithm: hyp.SetNumberLayers(numberOfLayers) hyp.SetStretchFactor(stretchFactor) hyp.SetEdges(edges, isEdgesToIgnore) + hyp.SetGroupName( groupName ) self.mesh.AddHypothesis( hyp, self.geom ) return hyp diff --git a/src/StdMeshers/StdMeshers_ViscousLayers.cxx b/src/StdMeshers/StdMeshers_ViscousLayers.cxx index 075d1b46d..270330be4 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers.cxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers.cxx @@ -612,12 +612,16 @@ namespace VISCOUS_3D _thickness = Max( _thickness, hyp->GetTotalThickness() ); _stretchFactor += hyp->GetStretchFactor(); _method = hyp->GetMethod(); + if ( _groupName.empty() ) + _groupName = hyp->GetGroupName(); } } double GetTotalThickness() const { return _thickness; /*_nbHyps ? _thickness / _nbHyps : 0;*/ } double GetStretchFactor() const { return _nbHyps ? _stretchFactor / _nbHyps : 0; } int GetNumberLayers() const { return _nbLayers; } int GetMethod() const { return _method; } + bool ToCreateGroup() const { return !_groupName.empty(); } + const std::string& GetGroupName() const { return _groupName; } bool UseSurfaceNormal() const { return _method == StdMeshers_ViscousLayers::SURF_OFFSET_SMOOTH; } @@ -636,8 +640,9 @@ namespace VISCOUS_3D static bool Equals( double v1, double v2 ) { return Abs( v1 - v2 ) < 0.01 * ( v1 + v2 ); } private: - int _nbLayers, _nbHyps, _method; - double _thickness, _stretchFactor; + int _nbLayers, _nbHyps, _method; + double _thickness, _stretchFactor; + std::string _groupName; }; //-------------------------------------------------------------------------------- @@ -1247,7 +1252,8 @@ namespace VISCOUS_3D StdMeshers_ViscousLayers::StdMeshers_ViscousLayers(int hypId, SMESH_Gen* gen) :SMESH_Hypothesis(hypId, gen), _isToIgnoreShapes(1), _nbLayers(1), _thickness(1), _stretchFactor(1), - _method( SURF_OFFSET_SMOOTH ) + _method( SURF_OFFSET_SMOOTH ), + _groupName("") { _name = StdMeshers_ViscousLayers::GetHypType(); _param_algo_dim = -3; // auxiliary hyp used by 3D algos @@ -1279,6 +1285,15 @@ void StdMeshers_ViscousLayers::SetMethod( ExtrusionMethod method ) if ( _method != method ) _method = method, NotifySubMeshesHypothesisModification(); } // -------------------------------------------------------------------------------- +void StdMeshers_ViscousLayers::SetGroupName(const std::string& name) +{ + if ( _groupName != name ) + { + _groupName = name; + if ( !_groupName.empty() ) + NotifySubMeshesHypothesisModification(); + } +} // -------------------------------------------------------------------------------- SMESH_ProxyMesh::Ptr StdMeshers_ViscousLayers::Compute(SMESH_Mesh& theMesh, const TopoDS_Shape& theShape, @@ -1333,6 +1348,9 @@ std::ostream & StdMeshers_ViscousLayers::SaveTo(std::ostream & save) save << " " << _shapeIds[i]; save << " " << !_isToIgnoreShapes; // negate to keep the behavior in old studies. save << " " << _method; + save << " " << _groupName.size(); + if ( !_groupName.empty() ) + save << " " << _groupName; return save; } // -------------------------------------------------------------------------------- std::istream & StdMeshers_ViscousLayers::LoadFrom(std::istream & load) @@ -1345,6 +1363,13 @@ std::istream & StdMeshers_ViscousLayers::LoadFrom(std::istream & load) _isToIgnoreShapes = !shapeToTreat; if ( load >> method ) _method = (ExtrusionMethod) method; + int nameSize = 0; + if ( load >> nameSize && nameSize > 0 ) + { + _groupName.resize( nameSize ); + load.get( _groupName[0] ); // remove a white-space + load.getline( &_groupName[0], nameSize + 1 ); + } } else { _isToIgnoreShapes = true; // old behavior @@ -1378,6 +1403,36 @@ bool StdMeshers_ViscousLayers::IsShapeWithLayers(int shapeIndex) const ( std::find( _shapeIds.begin(), _shapeIds.end(), shapeIndex ) != _shapeIds.end() ); return IsToIgnoreShapes() ? !isIn : isIn; } + +// -------------------------------------------------------------------------------- +SMDS_MeshGroup* StdMeshers_ViscousLayers::CreateGroup( const std::string& theName, + SMESH_Mesh& theMesh, + SMDSAbs_ElementType theType) +{ + SMESH_Group* group = 0; + SMDS_MeshGroup* groupDS = 0; + + if ( theName.empty() ) + return groupDS; + + if ( SMESH_Mesh::GroupIteratorPtr grIt = theMesh.GetGroups() ) + while( grIt->more() && !group ) + { + group = grIt->next(); + if ( !group || + group->GetGroupDS()->GetType() != theType || + group->GetName() != theName || + !dynamic_cast< SMESHDS_Group* >( group->GetGroupDS() )) + group = 0; + } + if ( !group ) + group = theMesh.AddGroup( theType, theName.c_str() ); + + groupDS = & dynamic_cast< SMESHDS_Group* >( group->GetGroupDS() )->SMDSGroup(); + + return groupDS; +} + // END StdMeshers_ViscousLayers hypothesis //================================================================================ @@ -10230,6 +10285,11 @@ bool _ViscousBuilder::refine(_SolidData& data) const TGeomID faceID = getMeshDS()->ShapeToIndex( exp.Current() ); if ( data._ignoreFaceIds.count( faceID )) continue; + _EdgesOnShape* eos = data.GetShapeEdges( faceID ); + SMDS_MeshGroup* group = StdMeshers_ViscousLayers::CreateGroup( eos->_hyp.GetGroupName(), + *helper.GetMesh(), + SMDSAbs_Volume ); + std::vector< const SMDS_MeshElement* > vols; const bool isReversedFace = data._reversedFaceIds.count( faceID ); SMESHDS_SubMesh* fSubM = getMeshDS()->MeshElements( exp.Current() ); SMDS_ElemIteratorPtr fIt = fSubM->GetElements(); @@ -10260,14 +10320,20 @@ bool _ViscousBuilder::refine(_SolidData& data) if ( 0 < nnSet.size() && nnSet.size() < 3 ) continue; + vols.clear(); + const SMDS_MeshElement* vol; + switch ( nbNodes ) { case 3: // TRIA { // PENTA for ( size_t iZ = 1; iZ < minZ; ++iZ ) - helper.AddVolume( (*nnVec[0])[iZ-1], (*nnVec[1])[iZ-1], (*nnVec[2])[iZ-1], - (*nnVec[0])[iZ], (*nnVec[1])[iZ], (*nnVec[2])[iZ]); + { + vol = helper.AddVolume( (*nnVec[0])[iZ-1], (*nnVec[1])[iZ-1], (*nnVec[2])[iZ-1], + (*nnVec[0])[iZ], (*nnVec[1])[iZ], (*nnVec[2])[iZ]); + vols.push_back( vol ); + } for ( size_t iZ = minZ; iZ < maxZ; ++iZ ) { @@ -10280,16 +10346,18 @@ bool _ViscousBuilder::refine(_SolidData& data) int i2 = *degenEdgeInd.begin(); int i0 = helper.WrapIndex( i2 - 1, nbNodes ); int i1 = helper.WrapIndex( i2 + 1, nbNodes ); - helper.AddVolume( (*nnVec[i0])[iZ-1], (*nnVec[i1])[iZ-1], - (*nnVec[i1])[iZ ], (*nnVec[i0])[iZ ], (*nnVec[i2]).back()); + vol = helper.AddVolume( (*nnVec[i0])[iZ-1], (*nnVec[i1])[iZ-1], + (*nnVec[i1])[iZ ], (*nnVec[i0])[iZ ], (*nnVec[i2]).back()); + vols.push_back( vol ); } else // TETRA { int i3 = !degenEdgeInd.count(0) ? 0 : !degenEdgeInd.count(1) ? 1 : 2; - helper.AddVolume( (*nnVec[ 0 ])[ i3 == 0 ? iZ-1 : nnVec[0]->size()-1 ], - (*nnVec[ 1 ])[ i3 == 1 ? iZ-1 : nnVec[1]->size()-1 ], - (*nnVec[ 2 ])[ i3 == 2 ? iZ-1 : nnVec[2]->size()-1 ], - (*nnVec[ i3 ])[ iZ ]); + vol = helper.AddVolume( (*nnVec[ 0 ])[ i3 == 0 ? iZ-1 : nnVec[0]->size()-1 ], + (*nnVec[ 1 ])[ i3 == 1 ? iZ-1 : nnVec[1]->size()-1 ], + (*nnVec[ 2 ])[ i3 == 2 ? iZ-1 : nnVec[2]->size()-1 ], + (*nnVec[ i3 ])[ iZ ]); + vols.push_back( vol ); } } break; // TRIA @@ -10298,10 +10366,13 @@ bool _ViscousBuilder::refine(_SolidData& data) { // HEX for ( size_t iZ = 1; iZ < minZ; ++iZ ) - helper.AddVolume( (*nnVec[0])[iZ-1], (*nnVec[1])[iZ-1], - (*nnVec[2])[iZ-1], (*nnVec[3])[iZ-1], - (*nnVec[0])[iZ], (*nnVec[1])[iZ], - (*nnVec[2])[iZ], (*nnVec[3])[iZ]); + { + vol = helper.AddVolume( (*nnVec[0])[iZ-1], (*nnVec[1])[iZ-1], + (*nnVec[2])[iZ-1], (*nnVec[3])[iZ-1], + (*nnVec[0])[iZ], (*nnVec[1])[iZ], + (*nnVec[2])[iZ], (*nnVec[3])[iZ]); + vols.push_back( vol ); + } for ( size_t iZ = minZ; iZ < maxZ; ++iZ ) { @@ -10320,9 +10391,9 @@ bool _ViscousBuilder::refine(_SolidData& data) int i0 = helper.WrapIndex( i3 + 1, nbNodes ); int i1 = helper.WrapIndex( i0 + 1, nbNodes ); - const SMDS_MeshElement* vol = - helper.AddVolume( nnVec[i3]->back(), (*nnVec[i0])[iZ], (*nnVec[i0])[iZ-1], - nnVec[i2]->back(), (*nnVec[i1])[iZ], (*nnVec[i1])[iZ-1]); + vol = helper.AddVolume( nnVec[i3]->back(), (*nnVec[i0])[iZ], (*nnVec[i0])[iZ-1], + nnVec[i2]->back(), (*nnVec[i1])[iZ], (*nnVec[i1])[iZ-1]); + vols.push_back( vol ); if ( !ok && vol ) degenVols.push_back( vol ); } @@ -10330,15 +10401,15 @@ bool _ViscousBuilder::refine(_SolidData& data) default: // degen HEX { - const SMDS_MeshElement* vol = - helper.AddVolume( nnVec[0]->size() > iZ-1 ? (*nnVec[0])[iZ-1] : nnVec[0]->back(), - nnVec[1]->size() > iZ-1 ? (*nnVec[1])[iZ-1] : nnVec[1]->back(), - nnVec[2]->size() > iZ-1 ? (*nnVec[2])[iZ-1] : nnVec[2]->back(), - nnVec[3]->size() > iZ-1 ? (*nnVec[3])[iZ-1] : nnVec[3]->back(), - nnVec[0]->size() > iZ ? (*nnVec[0])[iZ] : nnVec[0]->back(), - nnVec[1]->size() > iZ ? (*nnVec[1])[iZ] : nnVec[1]->back(), - nnVec[2]->size() > iZ ? (*nnVec[2])[iZ] : nnVec[2]->back(), - nnVec[3]->size() > iZ ? (*nnVec[3])[iZ] : nnVec[3]->back()); + vol = helper.AddVolume( nnVec[0]->size() > iZ-1 ? (*nnVec[0])[iZ-1] : nnVec[0]->back(), + nnVec[1]->size() > iZ-1 ? (*nnVec[1])[iZ-1] : nnVec[1]->back(), + nnVec[2]->size() > iZ-1 ? (*nnVec[2])[iZ-1] : nnVec[2]->back(), + nnVec[3]->size() > iZ-1 ? (*nnVec[3])[iZ-1] : nnVec[3]->back(), + nnVec[0]->size() > iZ ? (*nnVec[0])[iZ] : nnVec[0]->back(), + nnVec[1]->size() > iZ ? (*nnVec[1])[iZ] : nnVec[1]->back(), + nnVec[2]->size() > iZ ? (*nnVec[2])[iZ] : nnVec[2]->back(), + nnVec[3]->size() > iZ ? (*nnVec[3])[iZ] : nnVec[3]->back()); + vols.push_back( vol ); degenVols.push_back( vol ); } } @@ -10349,6 +10420,11 @@ bool _ViscousBuilder::refine(_SolidData& data) return error("Not supported type of element", data._index); } // switch ( nbNodes ) + + if ( group ) + for ( size_t i = 0; i < vols.size(); ++i ) + group->Add( vols[ i ]); + } // while ( fIt->more() ) } // loop on FACEs diff --git a/src/StdMeshers/StdMeshers_ViscousLayers.hxx b/src/StdMeshers/StdMeshers_ViscousLayers.hxx index 142f35b01..7d7e362fd 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers.hxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers.hxx @@ -32,6 +32,8 @@ #include +class SMDS_MeshGroup; + /*! * \brief Hypothesis defining parameters of viscous layers */ @@ -73,6 +75,13 @@ public: void SetMethod( ExtrusionMethod how ); ExtrusionMethod GetMethod() const { return _method; } + // name of a group to create + void SetGroupName(const std::string& name); + const std::string& GetGroupName() const { return _groupName; } + static SMDS_MeshGroup* CreateGroup( const std::string& theName, + SMESH_Mesh& theMesh, + SMDSAbs_ElementType theType); + // Computes temporary 2D mesh to be used by 3D algorithm. // Return SMESH_ProxyMesh for each SOLID in theShape SMESH_ProxyMesh::Ptr Compute(SMESH_Mesh& theMesh, @@ -116,6 +125,7 @@ public: double _thickness; double _stretchFactor; ExtrusionMethod _method; + std::string _groupName; }; class SMESH_subMesh; diff --git a/src/StdMeshers/StdMeshers_ViscousLayers2D.cxx b/src/StdMeshers/StdMeshers_ViscousLayers2D.cxx index 6248200a7..a0da0d527 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers2D.cxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers2D.cxx @@ -2402,6 +2402,17 @@ bool _ViscousBuilder2D::refine() outerNodes.swap( innerNodes ); } + // Add faces to a group + SMDS_MeshGroup* group = StdMeshers_ViscousLayers::CreateGroup( hyp->GetGroupName(), + *_helper.GetMesh(), + SMDSAbs_Face ); + if ( group ) + { + TIDSortedElemSet::iterator fIt = L._newFaces.begin(); + for ( ; fIt != L._newFaces.end(); ++fIt ) + group->Add( *fIt ); + } + // faces between not shared _LayerEdge's (at concave VERTEX) for ( int isR = 0; isR < 2; ++isR ) { @@ -2413,15 +2424,22 @@ bool _ViscousBuilder2D::refine() if ( lNodes.empty() || rNodes.empty() || lNodes.size() != rNodes.size() ) continue; + const SMDS_MeshElement* face = 0; for ( size_t i = 1; i < lNodes.size(); ++i ) - _helper.AddFace( lNodes[ i+prev ], rNodes[ i+prev ], - rNodes[ i+cur ], lNodes[ i+cur ]); + { + face = _helper.AddFace( lNodes[ i+prev ], rNodes[ i+prev ], + rNodes[ i+cur ], lNodes[ i+cur ]); + if ( group ) + group->Add( face ); + } const UVPtStruct& ptOnVertex = points[ isR ? L._lastPntInd : L._firstPntInd ]; if ( isReverse ) - _helper.AddFace( ptOnVertex.node, lNodes[ 0 ], rNodes[ 0 ]); + face = _helper.AddFace( ptOnVertex.node, lNodes[ 0 ], rNodes[ 0 ]); else - _helper.AddFace( ptOnVertex.node, rNodes[ 0 ], lNodes[ 0 ]); + face = _helper.AddFace( ptOnVertex.node, rNodes[ 0 ], lNodes[ 0 ]); + if ( group ) + group->Add( face ); } // Fill the _ProxyMeshOfFace diff --git a/src/StdMeshersGUI/CMakeLists.txt b/src/StdMeshersGUI/CMakeLists.txt index 0b8834730..4393778db 100644 --- a/src/StdMeshersGUI/CMakeLists.txt +++ b/src/StdMeshersGUI/CMakeLists.txt @@ -82,6 +82,7 @@ SET(_moc_HEADERS StdMeshersGUI_CartesianParamCreator.h StdMeshersGUI_RadioButtonsGrpWdg.h StdMeshersGUI_PropagationHelperWdg.h + StdMeshersGUI_NameCheckableGrpWdg.h ) IF(SALOME_USE_PLOT2DVIEWER) @@ -117,6 +118,7 @@ SET(_other_SOURCES StdMeshersGUI_CartesianParamCreator.cxx StdMeshersGUI_RadioButtonsGrpWdg.cxx StdMeshersGUI_PropagationHelperWdg.cxx + StdMeshersGUI_NameCheckableGrpWdg.cxx ) IF(SALOME_USE_PLOT2DVIEWER) diff --git a/src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.cxx b/src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.cxx new file mode 100644 index 000000000..5ea084b43 --- /dev/null +++ b/src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.cxx @@ -0,0 +1,68 @@ +// Copyright (C) 2007-2019 CEA/DEN, EDF R&D, OPEN CASCADE +// +// 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, 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 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#include "StdMeshersGUI_NameCheckableGrpWdg.h" + +#include +#include +#include + +#define SPACING 6 +#define MARGIN 11 + +//================================================================================ +/*! + * \brief Creates a QGroupBox with a given title + */ +//================================================================================ + +StdMeshersGUI_NameCheckableGrpWdg::StdMeshersGUI_NameCheckableGrpWdg( const QString& groupTitle, + const QString& nameLabel ) + : QGroupBox( groupTitle ), + myNameLineEdit( new QLineEdit( this )) +{ + setCheckable( true ); + + QLabel* label = new QLabel( nameLabel ); + + QGridLayout* layout = new QGridLayout( this ); + layout->setSpacing(SPACING); + layout->setMargin(MARGIN); + + layout->addWidget( label, 0, 0 ); + layout->addWidget( myNameLineEdit, 0, 1 ); + + connect( this, SIGNAL( toggled( bool )), myNameLineEdit, SLOT( setEnabled( bool ))); +} + +QString StdMeshersGUI_NameCheckableGrpWdg::getName() +{ + return isChecked() ? myNameLineEdit->text() : QString(); +} + +void StdMeshersGUI_NameCheckableGrpWdg::setName( CORBA::String_var name ) +{ + myNameLineEdit->setText( name.in() ); + setChecked( ! myNameLineEdit->text().isEmpty() ); +} + +void StdMeshersGUI_NameCheckableGrpWdg::setDefaultName( QString name ) +{ + myNameLineEdit->setText( name ); + setChecked( ! myNameLineEdit->text().isEmpty() ); +} diff --git a/src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.h b/src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.h new file mode 100644 index 000000000..334df51b8 --- /dev/null +++ b/src/StdMeshersGUI/StdMeshersGUI_NameCheckableGrpWdg.h @@ -0,0 +1,53 @@ +// Copyright (C) 2007-2019 CEA/DEN, EDF R&D, OPEN CASCADE +// +// 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, 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 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +#ifndef STDMESHERSGUI_NameCheckableGrpWdg_H +#define STDMESHERSGUI_NameCheckableGrpWdg_H + +// SMESH includes +#include "SMESH_StdMeshersGUI.hxx" + +// Qt includes +#include + +#include + +class QButtonGroup; +class QLineEdit; + +/*! + * \brief A QGroupBox holding several radio buttons + */ +class STDMESHERSGUI_EXPORT StdMeshersGUI_NameCheckableGrpWdg : public QGroupBox +{ + Q_OBJECT + +public: + StdMeshersGUI_NameCheckableGrpWdg(const QString& groupTitle, + const QString& nameLabel); + + QString getName(); + + void setName( CORBA::String_var name ); + void setDefaultName( QString name ); + +private: + QLineEdit* myNameLineEdit; +}; + +#endif // STDMESHERSGUI_NameCheckableGrpWdg_H diff --git a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx index d477b2fc6..f16b06011 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx @@ -39,6 +39,7 @@ #include "StdMeshersGUI_PropagationHelperWdg.h" #include "StdMeshersGUI_QuadrangleParamWdg.h" #include "StdMeshersGUI_RadioButtonsGrpWdg.h" +#include "StdMeshersGUI_NameCheckableGrpWdg.h" #include "StdMeshersGUI_SubShapeSelectorWdg.h" #include @@ -728,6 +729,12 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const { h->SetFaces( idsWg->GetListOfIDs(), params[4].myValue.toInt() ); } + + if ( StdMeshersGUI_NameCheckableGrpWdg* nameWg = + widget< StdMeshersGUI_NameCheckableGrpWdg >( 6 )) + { + h->SetGroupName( nameWg->getName().toUtf8().data() ); + } } else if( hypType()=="ViscousLayers2D" ) { @@ -746,6 +753,12 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const { h->SetEdges( idsWg->GetListOfIDs(), params[3].myValue.toInt() ); } + + if ( StdMeshersGUI_NameCheckableGrpWdg* nameWg = + widget< StdMeshersGUI_NameCheckableGrpWdg >( 5 )) + { + h->SetGroupName( nameWg->getName().toUtf8().data() ); + } } // else if( hypType()=="QuadrangleParams" ) // { @@ -1250,6 +1263,19 @@ bool StdMeshersGUI_StdHypothesisCreator::stdParams( ListOfStdParams& p ) const } customWidgets()->append ( idsWg ); } + + item.setNoName(); + p.append( item ); + StdMeshersGUI_NameCheckableGrpWdg* nameWdg = + new StdMeshersGUI_NameCheckableGrpWdg( tr( "CREATE_GROUPS_FROM_LAYERS" ), + tr( "GROUP_NAME" )); + nameWdg->setName( h->GetGroupName() ); + if ( nameWdg->getName().isEmpty() ) + { + nameWdg->setDefaultName( type() ); + nameWdg->setChecked( false ); + } + customWidgets()->append ( nameWdg ); } else if( hypType()=="ViscousLayers2D" ) { @@ -1308,6 +1334,19 @@ bool StdMeshersGUI_StdHypothesisCreator::stdParams( ListOfStdParams& p ) const } customWidgets()->append ( idsWg ); } + + item.setNoName(); + p.append( item ); + StdMeshersGUI_NameCheckableGrpWdg* nameWdg = + new StdMeshersGUI_NameCheckableGrpWdg( tr( "CREATE_GROUPS_FROM_LAYERS" ), + tr( "GROUP_NAME" )); + nameWdg->setName( h->GetGroupName() ); + if ( nameWdg->getName().isEmpty() ) + { + nameWdg->setDefaultName( type() ); + nameWdg->setChecked( false ); + } + customWidgets()->append ( nameWdg ); } else res = false; @@ -1574,6 +1613,10 @@ bool StdMeshersGUI_StdHypothesisCreator::getParamFromCustomWidget( StdParam & pa param.myValue = w->checkedId(); return true; } + if ( widget->inherits( "StdMeshersGUI_NameCheckableGrpWdg" )) + { + return true; + } return false; } diff --git a/src/StdMeshersGUI/StdMeshers_msg_en.ts b/src/StdMeshersGUI/StdMeshers_msg_en.ts index 351aed8f4..036b28413 100644 --- a/src/StdMeshersGUI/StdMeshers_msg_en.ts +++ b/src/StdMeshersGUI/StdMeshers_msg_en.ts @@ -59,6 +59,14 @@ this one for this mesh/sub-mesh. EXTMETH_FACE_OFFSET Face offset + + CREATE_GROUPS_FROM_LAYERS + Create groups from layers + + + GROUP_NAME + Group name + @default diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.cxx b/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.cxx index 376f156b0..0e03a4b1f 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.cxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.cxx @@ -222,6 +222,33 @@ throw ( SALOME::SALOME_Exception ) return GetImpl()->GetStretchFactor(); } +//================================================================================ +/*! + * \brief Set name of a group of layers elements + */ +//================================================================================ + +void StdMeshers_ViscousLayers2D_i::SetGroupName(const char* name) +{ + if ( GetImpl()->GetGroupName() != name ) + { + GetImpl()->SetGroupName( name ); + SMESH::TPythonDump() << _this() << ".SetGroupName( '" << name << "' )"; + } +} + +//================================================================================ +/*! + * \brief Return name of a group of layers elements + */ +//================================================================================ + +char* StdMeshers_ViscousLayers2D_i::GetGroupName() +{ + return CORBA::string_dup( GetImpl()->GetGroupName().c_str() ); +} + + //============================================================================= /*! * Get implementation diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.hxx b/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.hxx index 49dc69c8e..56f557c38 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.hxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers2D_i.hxx @@ -64,6 +64,10 @@ class STDMESHERS_I_EXPORT StdMeshers_ViscousLayers2D_i: void SetStretchFactor(::CORBA::Double factor) throw ( SALOME::SALOME_Exception ); ::CORBA::Double GetStretchFactor(); + void SetGroupName(const char* name); + char* GetGroupName(); + + // Get implementation ::StdMeshers_ViscousLayers2D* GetImpl(); diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx index 603ef55c3..ab47e1725 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.cxx @@ -253,6 +253,32 @@ void StdMeshers_ViscousLayers_i::SetMethod( ::StdMeshers::VLExtrusionMethod how return (::StdMeshers::VLExtrusionMethod) GetImpl()->GetMethod(); } +//================================================================================ +/*! + * \brief Set name of a group of layers elements + */ +//================================================================================ + +void StdMeshers_ViscousLayers_i::SetGroupName(const char* name) +{ + if ( GetImpl()->GetGroupName() != name ) + { + GetImpl()->SetGroupName( name ); + SMESH::TPythonDump() << _this() << ".SetGroupName( '" << name << "' )"; + } +} + +//================================================================================ +/*! + * \brief Return name of a group of layers elements + */ +//================================================================================ + +char* StdMeshers_ViscousLayers_i::GetGroupName() +{ + return CORBA::string_dup( GetImpl()->GetGroupName().c_str() ); +} + //============================================================================= /*! * Get implementation diff --git a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx index ac5f0e1ae..47200ce72 100644 --- a/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx +++ b/src/StdMeshers_I/StdMeshers_ViscousLayers_i.hxx @@ -67,10 +67,14 @@ class STDMESHERS_I_EXPORT StdMeshers_ViscousLayers_i: void SetMethod( ::StdMeshers::VLExtrusionMethod how ); ::StdMeshers::VLExtrusionMethod GetMethod(); + void SetGroupName(const char* name); + char* GetGroupName(); + + // Get implementation ::StdMeshers_ViscousLayers* GetImpl(); - // Verify whether hypothesis supports given entity type + // Verify whether hypothesis supports given entity type CORBA::Boolean IsDimSupported( SMESH::Dimension type ); // Methods for copying mesh definition to other geometry From 66ec81f68de3b17173dbf992c70feae7c30778d5 Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 7 Feb 2020 16:57:31 +0300 Subject: [PATCH 39/60] #16522 [CEA 7599] Viscous layers hypothesis: extract layers as a group 1) Fix invalid python dump (missing Compute() before GetGroups()) 2) Remove a group if group name changes 3) Remove an actor of a removed group --- src/SMESHGUI/SMESHGUI.cxx | 7 ++ src/SMESHGUI/SMESHGUI_Hypotheses.cxx | 2 + src/SMESHGUI/SMESHGUI_VTKUtils.cxx | 80 +++++++++++++++++++ src/SMESHGUI/SMESHGUI_VTKUtils.h | 6 ++ src/SMESH_I/SMESH_2smeshpy.cxx | 7 +- src/SMESH_I/SMESH_Group_i.cxx | 4 +- .../StdMeshersGUI_StdHypothesisCreator.cxx | 49 +++++++++++- .../StdMeshersGUI_StdHypothesisCreator.h | 3 + 8 files changed, 151 insertions(+), 7 deletions(-) diff --git a/src/SMESHGUI/SMESHGUI.cxx b/src/SMESHGUI/SMESHGUI.cxx index deb3711c9..8eb92b364 100644 --- a/src/SMESHGUI/SMESHGUI.cxx +++ b/src/SMESHGUI/SMESHGUI.cxx @@ -4930,7 +4930,14 @@ bool SMESHGUI::activateModule( SUIT_Study* study ) QList wndList = aDesk->windows(); SUIT_ViewWindow* wnd; foreach ( wnd, wndList ) + { connectView( wnd ); + + // remove actors whose objects are removed in GetSMESHGen()->UpdateStudy() + SMESH::UpdateActorsAfterUpdateStudy(wnd); + + wnd->update(); + } } Py_XDECREF(pluginsmanager); diff --git a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx index 521966bf7..b2e1e2496 100644 --- a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx +++ b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx @@ -340,6 +340,8 @@ void SMESHGUI_GenericHypothesisCreator::onDialogFinished( int result ) myDlg->close(); //delete myDlg; since WA_DeleteOnClose==true myDlg = 0; + + SMESH::UpdateActorsAfterUpdateStudy();// remove actors of removed groups (#16522) if (SVTK_ViewWindow* vf = SMESH::GetCurrentVtkView()) { vf->Repaint(); } diff --git a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx index d6251e7d6..f0ef5750d 100644 --- a/src/SMESHGUI/SMESHGUI_VTKUtils.cxx +++ b/src/SMESHGUI/SMESHGUI_VTKUtils.cxx @@ -232,6 +232,86 @@ namespace SMESH } } + //================================================================================ + /*! + * \brief Remove/update actors while module activation + * \param [in] wnd - window + * + * At module activation, groups and sub-meshes can be removed on engine side due + * to modification of meshed geometry, while their actors can remain. + * Here we remove/update SMESH_Actor's of changed objects. State (emptiness) of objects + * is defined by their icons in the Object Browser + */ + //================================================================================ + + void UpdateActorsAfterUpdateStudy( SUIT_ViewWindow* theWindow ) + { + const char* emptyIcon = "ICON_SMESH_TREE_MESH_WARN"; + _PTR(Study) aStudy = SMESH::getStudy(); + + if ( SVTK_ViewWindow* aViewWindow = GetVtkViewWindow( theWindow )) + { + vtkRenderer *aRenderer = aViewWindow->getRenderer(); + VTK::ActorCollectionCopy aCopy(aRenderer->GetActors()); + vtkActorCollection *aCollection = aCopy.GetActors(); + aCollection->InitTraversal(); + while ( vtkActor *actor = aCollection->GetNextActor() ) { + if ( SMESH_Actor *smeshActor = dynamic_cast( actor )) + { + if ( !smeshActor->hasIO() ) + continue; + Handle(SALOME_InteractiveObject) io = smeshActor->getIO(); + if ( !io->hasEntry() ) + continue; + _PTR(SObject) so = aStudy->FindObjectID( io->getEntry() ); + if ( !so ) + continue; // seems impossible + + CORBA::Object_var obj = SMESH::SObjectToObject( so ); + if ( CORBA::is_nil( obj )) // removed object + { + RemoveActor( theWindow, smeshActor ); + continue; + } + + bool toShow = smeshActor->GetVisibility(); + _PTR(GenericAttribute) attr; + if ( toShow && so->FindAttribute( attr, "AttributePixMap" )) // check emptiness + { + _PTR(AttributePixMap) pixMap = attr; + toShow = ( pixMap->GetPixMap() != emptyIcon ); + } + smeshActor->Update(); + UpdateView( theWindow, toShow ? eDisplay : eErase, io->getEntry() ); + } + } + } + return; + } + + //================================================================================ + /*! + * \brief Remove/update actors while module activation + * + * At module activation, groups and sub-meshes can be removed on engine side due + * to modification of meshed geometry, while their actors can remain. + * Here we remove/update SMESH_Actor's of changed objects. State (emptiness) of objects + * is defined by their icons in the Object Browser + */ + //================================================================================ + + void UpdateActorsAfterUpdateStudy() + { + SUIT_Study* study = SMESH::GetActiveStudy(); + if ( SUIT_Desktop* desk = study->application()->desktop() ) + { + QList wndList = desk->windows(); + SUIT_ViewWindow* wnd; + foreach ( wnd, wndList ) + SMESH::UpdateActorsAfterUpdateStudy(wnd); + } + } + //================================================================================ /*! * \brief Notify the user on problems during visualization diff --git a/src/SMESHGUI/SMESHGUI_VTKUtils.h b/src/SMESHGUI/SMESHGUI_VTKUtils.h index 52919afad..8874d1a80 100644 --- a/src/SMESHGUI/SMESHGUI_VTKUtils.h +++ b/src/SMESHGUI/SMESHGUI_VTKUtils.h @@ -216,6 +216,12 @@ SMESHGUI_EXPORT double& theDist ); SMESHGUI_EXPORT void RemoveVisualObjectWithActors( const char* theEntry, bool fromAllViews = false ); + + SMESHGUI_EXPORT + void UpdateActorsAfterUpdateStudy( SUIT_ViewWindow* wnd ); + + SMESHGUI_EXPORT + void UpdateActorsAfterUpdateStudy(); }; #endif // SMESHGUI_VTKUTILS_H diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index b22af73eb..c0e892d20 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -1827,7 +1827,7 @@ _pyMesh::_pyMesh(const Handle(_pyCommand) theCreationCmd, const _pyID& meshId): void _pyMesh::Process( const Handle(_pyCommand)& theCommand ) { - // some methods of SMESH_Mesh interface needs special conversion + // some methods of SMESH_Mesh interface need special conversion // to methods of Mesh python class // // 1. GetSubMesh(geom, name) + AddHypothesis(geom, algo) @@ -1980,7 +1980,7 @@ void _pyMesh::Process( const Handle(_pyCommand)& theCommand ) // if GetGroups() is just after Compute(), this can mean that the groups // were created by some algorithm and hence Compute() should not be discarded std::list< Handle(_pyCommand) >& cmdList = theGen->GetCommands(); - std::list< Handle(_pyCommand) >::iterator cmd = cmdList.begin(); + std::list< Handle(_pyCommand) >::reverse_iterator cmd = cmdList.rbegin(); while ( (*cmd)->GetMethod() == "GetGroups" ) ++cmd; if ( myLastComputeCmd == (*cmd)) @@ -3158,8 +3158,7 @@ void _pyHypothesis::ComputeDiscarded( const Handle(_pyCommand)& theComputeCmd ) continue; // check if a cmd is a sole command setting its parameter; // don't use method name for search as it can change - map >::iterator - m2cmds = myMeth2Commands.begin(); + map<_AString, list >::iterator m2cmds = myMeth2Commands.begin(); for ( ; m2cmds != myMeth2Commands.end(); ++m2cmds ) { list< Handle(_pyCommand)>& cmds = m2cmds->second; diff --git a/src/SMESH_I/SMESH_Group_i.cxx b/src/SMESH_I/SMESH_Group_i.cxx index a2dac404c..a7a7e45eb 100644 --- a/src/SMESH_I/SMESH_Group_i.cxx +++ b/src/SMESH_I/SMESH_Group_i.cxx @@ -171,13 +171,13 @@ char* SMESH_GroupBase_i::GetName() { ::SMESH_Group* aGroup = GetSmeshGroup(); if (aGroup) - return CORBA::string_dup (aGroup->GetName()); + return CORBA::string_dup( aGroup->GetName() ); return CORBA::string_dup( "NO_NAME" ); } //============================================================================= /*! - * + * */ //============================================================================= diff --git a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx index f16b06011..1a749bef8 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.cxx @@ -378,10 +378,51 @@ namespace { } } +//================================================================================ +/*! + * \brief Remove a group, whose name is stored by hypothesis, upon group name modification + * \param [in] oldName - old group name + * \param [in] newName - new group name + * \param [in] type - group type + */ +//================================================================================ + +void StdMeshersGUI_StdHypothesisCreator:: +removeOldGroup(const char* oldName, const char* newName, SMESH::ElementType type) const +{ + if ( !oldName || !oldName[0] ) + return; // old name undefined + if ( newName && strcmp( oldName, newName ) == 0 ) + return; // same name + + SMESH::SMESH_Hypothesis_var h = hypothesis(); + SMESH::SObjectList listSOmesh = SMESH::GetMeshesUsingAlgoOrHypothesis( h ); + for ( size_t i = 0; i < listSOmesh.size(); i++ ) + { + _PTR(SObject) submSO = listSOmesh[i]; + SMESH::SMESH_Mesh_var mesh = SMESH::SObjectToInterface( submSO ); + SMESH::SMESH_subMesh_var subMesh = SMESH::SObjectToInterface( submSO ); + if( !subMesh->_is_nil() ) + mesh = subMesh->GetFather(); + if ( mesh->_is_nil() ) + continue; + SMESH::ListOfGroups_var groups = mesh->GetGroups(); + for ( CORBA::ULong iG = 0; iG < groups->length(); iG++ ) + { + if ( groups[iG]->GetType() != type ) + continue; + CORBA::String_var name = groups[iG]->GetName(); + if ( strcmp( name.in(), oldName )) + continue; + mesh->RemoveGroup( groups[iG] ); + } + } +} + //================================================================================ /*! * \brief Check parameter values before accept() - * \retval bool - true if OK + * \retval bool - true if OK */ //================================================================================ @@ -733,7 +774,10 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const if ( StdMeshersGUI_NameCheckableGrpWdg* nameWg = widget< StdMeshersGUI_NameCheckableGrpWdg >( 6 )) { + CORBA::String_var oldName = h->GetGroupName(); h->SetGroupName( nameWg->getName().toUtf8().data() ); + CORBA::String_var newName = h->GetGroupName(); + removeOldGroup( oldName, newName, SMESH::VOLUME ); } } else if( hypType()=="ViscousLayers2D" ) @@ -757,7 +801,10 @@ QString StdMeshersGUI_StdHypothesisCreator::storeParams() const if ( StdMeshersGUI_NameCheckableGrpWdg* nameWg = widget< StdMeshersGUI_NameCheckableGrpWdg >( 5 )) { + CORBA::String_var oldName = h->GetGroupName(); h->SetGroupName( nameWg->getName().toUtf8().data() ); + CORBA::String_var newName = h->GetGroupName(); + removeOldGroup( oldName, newName, SMESH::FACE ); } } // else if( hypType()=="QuadrangleParams" ) diff --git a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.h b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.h index 7e382ed3d..0e984e4d2 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.h +++ b/src/StdMeshersGUI/StdMeshersGUI_StdHypothesisCreator.h @@ -66,6 +66,9 @@ protected: bool initVariableName(SMESH::SMESH_Hypothesis_var theHyp, StdParam& theParams, const char* theMethod) const; QWidget* makeReverseEdgesWdg( SMESH::long_array_var edgeIDs, CORBA::String_var shapeEntry) const; + void removeOldGroup(const char* oldName, + const char* newName, + SMESH::ElementType type) const; From 650885c1351fd0ec14226de37a67dec62329d83f Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 5 Feb 2020 14:58:45 +0300 Subject: [PATCH 40/60] #17237: Body fitting on sub-mesh, #16523: Treatment of internal faces --- doc/salome/examples/cartesian_algo.py | 27 +- .../gui/SMESH/images/cartesian3D_hyp.png | Bin 60056 -> 59740 bytes doc/salome/gui/SMESH/input/cartesian_algo.rst | 3 + idl/SMESH_BasicHypothesis.idl | 26 +- resources/StdMeshers.xml.in | 2 - src/Controls/SMESH_Controls.cxx | 23 +- src/SMESH/SMESH_MeshEditor.cxx | 3 +- src/SMESHDS/SMESHDS_SubMesh.cxx | 10 +- src/SMESHGUI/SMESHGUI_Hypotheses.cxx | 4 +- .../StdMeshers_CartesianParameters3D.cxx | 57 +- .../StdMeshers_CartesianParameters3D.hxx | 22 + src/StdMeshers/StdMeshers_Cartesian_3D.cxx | 2306 ++++++++++++++--- .../StdMeshersGUI_CartesianParamCreator.cxx | 21 +- .../StdMeshersGUI_CartesianParamCreator.h | 3 + src/StdMeshersGUI/StdMeshers_msg_en.ts | 12 + .../StdMeshers_CartesianParameters3D_i.cxx | 83 + .../StdMeshers_CartesianParameters3D_i.hxx | 27 +- 17 files changed, 2306 insertions(+), 323 deletions(-) diff --git a/doc/salome/examples/cartesian_algo.py b/doc/salome/examples/cartesian_algo.py index 26bdb8bf0..d7d64b8b7 100644 --- a/doc/salome/examples/cartesian_algo.py +++ b/doc/salome/examples/cartesian_algo.py @@ -14,10 +14,13 @@ smesh = smeshBuilder.New() # create a sphere sphere = geompy.MakeSphereR( 50 ) -geompy.addToStudy( sphere, "sphere" ) + +# cut the sphere by a box +box = geompy.MakeBoxDXDYDZ( 100, 100, 100 ) +partition = geompy.MakePartition([ sphere ], [ box ], theName="partition") # create a mesh and assign a "Body Fitting" algo -mesh = smesh.Mesh( sphere ) +mesh = smesh.Mesh( partition ) cartAlgo = mesh.BodyFitted() # define a cartesian grid using Coordinates @@ -38,7 +41,27 @@ mesh.Compute() print("nb hexahedra",mesh.NbHexas()) print("nb tetrahedra",mesh.NbTetras()) print("nb polyhedra",mesh.NbPolyhedrons()) +print("nb faces",mesh.NbFaces()) +print() +# activate creation of faces +cartHyp.SetToCreateFaces( True ) + +mesh.Compute() +print("nb hexahedra",mesh.NbHexas()) +print("nb tetrahedra",mesh.NbTetras()) +print("nb polyhedra",mesh.NbPolyhedrons()) +print("nb faces",mesh.NbFaces()) +print() + +# enable consideration of shared faces +cartHyp.SetToConsiderInternalFaces( True ) +mesh.Compute() +print("nb hexahedra",mesh.NbHexas()) +print("nb tetrahedra",mesh.NbTetras()) +print("nb polyhedra",mesh.NbPolyhedrons()) +print("nb faces",mesh.NbFaces()) +print() # define the grid by setting different spacing in 2 sub-ranges of geometry spaceFuns = ["5","10+10*t"] diff --git a/doc/salome/gui/SMESH/images/cartesian3D_hyp.png b/doc/salome/gui/SMESH/images/cartesian3D_hyp.png index c9a605fea0ca0cec4e0d8ba14290e00db423cac9..9f4c59a033bf1f2081553c72d6c564c2d25db971 100644 GIT binary patch literal 59740 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⩔JJQh7d;9zPP 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ℜ?aO6-=y3&k5*x{kP z>ankrlYucnXhM-Nppxq0cao`|qC3UyU`{KTUa6KS!SYj!K&&CcQ4 z#k#@a-EMcWI14f`lMH%K{r25#8~vZmd^8M7c1)8?n(96q@^MN(SAzo<=zM-&=AVn` z!bupD%Lf!~Ew?or@d|1 z`!glxi@Yp#%SY@dnWJ9ICao;WI+am#YG$q;UB1HfF~(?#E~k(nl#nxz?*2@0%jOoM z$xxn8%^aV&Sl+5OgJbLX{bTUpJ}lU1jDXA~LZ!P#lIrp#<$_ z;=wY2%Qn}jp3m^hLC9h@2S1dSR#3oz%!#ydJ)cqcnaZtwQnc$;IpvoG!8=gWOCApS zILtJsX$et?8Z6uJeU{%c`Hi~f`muQo3p2A2dP+c`um7XlIdByFwc@4ogMUf20da^c z@w_i*=zM5Y`OFAGFQcMDI3TY0`2_!3X>qb|N-z^D2ma$bBOAnC)q6<#A*LL_H~PRyHCIUNrd| z0w0kJ`m|j%hswky9dZmA&c#AZQoHUri2#aosR5>KiVq(q56A4sZY~ofKIr*{+3oA= z^JiLxGPy;a-hLK^#aIfjQggj?^PnEFM{?n@#W52Yw>0gUb(js3mSX|-w>C< ztIZVE9)G{)jpn)HQ@Z$Q`N?QC`Pc;N*7B@d_`_sW0q+7iA<{#q%MXLH*?nmL26+;8Xn+s_Q9v?s za)h`9**kWV_ufK9o(B#;8ucI!bA89`lllA+kcRvjQl<%_dPh-p7E^Z_Eqa+5m6oNo zkYUt`imb1z1GUkjugL26_v3Z?F|ox~tVmO1tCBB}B$ju+nvEq&;KmBwWZ%U;+XD}n z-FFNCL#lK>-HvnJX}>+N?Jyz96epMxqR*iYGI)2SA`b7CX1mmNmrHbu2L~rJ=d4#oZ-GtrT{kzkpi4-1 z2s{T>xl1jl9@MMR<*KYN4J~zJveYsN)##bo>q)BlCUSGSZ|7YLMaX+|1b*#zv>r|$ z5PwM@lRnfRU_W#R4Was_A%}VP{!)u#pbte5)}5D@a(1u}Hh#PbbmHAN#_jIX^h*xS zlp&&bup~B>}TFid$7046Hydu(-(N2vD;^tIvh>{bf^rm+x zADE^tA?q+Mc+_!wz-{|h6iAuT(ndHr?re~Vxic9CIblm=AYSO^RtV?`N@*^z*jh5e zT!0QYs2Zt*+6VN-o?>J~0n6~|mFL%WZZO`CNxu@2N;WumM*?Vy`y2C(=Va8@J8xF9 zNmj~q<{J$Cg%IBp|J)Ra;$N^EuxnMQIBqTJj#l;NXf+y_6kvCk3wOjU3XGf8{Afj_ zL3HX{QUFEAcQ6uZo@8B#E(H%V*dfjPQe$?g_IwWuZ!k-g^&NE=b>DzX$Kl2*qmc8ZB9cTtqlGn=}m@X;Tm4BuYUPr(S zRIZF4#Ln*Sk}S{9t7a8CF1OS1Z75063E^<$X{`!48hppeWYubDE6@PxmUd zspzU%f{y1dSoX0=IV~2xy3MO!@o~XizOpiFxVvJ!cW~AYRA?!nBz$WA zIR~_~L$Ivz=utx4F)dfMeI>tO9-&*m;sy$c9x6Bw+`Heoa1hUBmFH)km}u~}@|A3t zopZW-538z**kws7Y^j{~NyjUfQ)3h7x*G}o)u)S_8_%%$$q;Ip&X1ig-m)c;|EFk1 z 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 L{!Z>~gBSk;*etYI literal 60056 zcmbTe1yoe++b=wdfJ%udjVLGx(%l#cND8QQcX!8#C?O@?pn`zX-6hQo9Rm#AFm%HZ z=N^5Y|9Q_j?>gVN&bV}BGtBIL$94T`6ZBd^ir^;YO$Y=+@Jd=j83MsFfIzM`T*m=N z@Z5Y5;OUB^vea`(eh>93_yWg3R!RbLiTU%jE-MNgxnU=*?g)VpwqX8ViFrau1rFjm zy^?>4J9YKiO_Imb;ZPR{(iV-f*X3zl$vm4Djt0O@Zsb2>oNo>Po-w}0>Mi*mcVV=lRTLcz%49nS?RIyYw;VK;VoifCOMxo6H-VQ2l#IYg^Afo<5Sty z;+P>SIy&WSEdkft5aam3z&maDju2^i-W+}jiHz6E{y~A#`We398)o+z=;*#<{1^EHdg7v2I&>O6p{+dq?NOHbEMx?Gk?VlY47H1reac4&WhV^ zafe-LDa;Dmx~3ZxHqm=ZyQDHz(s|PI*l+Zv@f^_FNd`%V#H(BGo#V~o%HP6wzMns7 zSz&SMm9KBe5r4crNyd>7A{Wn-Hd=|N-sSf0aWkp_pWy*#(6i>Lfv0lO`du8x@m%Yv zU4{F;YBD|Se5UO@Ebm*ds(p9BE+YrqbUYZGp(`T|a3NdvSosnULN_ej$IzCt?tI6yZJv;Ct=d9MAiq@;NOM@Z( z-g|4P753McNK@;?@eF%Ie-ZJk;tgFA$sw~)Zn3$QB5Bm_j1;nOz6kwuWxlD*^2S-Y zJL6W5ZriZs4G7O=uQ&d^zEGL^)QpVG+wMZKpVEBV3z9BM&0nM~XF48tFeUQctkge2 z9eAGVd)2~9tdWF5PAk&CTBVuz73DG!_D^OThp*v&j(cqAs{0YH`8+F(HCtm?7Pu8KVuC91cc@{b#|xY0v?5K z`5cEn>vhiZNpY~cG5MhZdLK-l!|no;nEEzX*@tm;*wM;}KLOQq^UJ5QLKD-`^Z|!O zD=-4`P=?g?$)In3o(?+$#$xC*yzz4L*7-@~{K6MFydqYQC-KbBEJ)9`3nsL62vQfB*r=Gw4I7pmzlx?8DGiE&!e{xcAHw% z8{=j3jhoIx_!g#!a_%)~{v}Dp5AlbW&)3ECPM4}pMl(MpaLr5VM|JfP+ithzG-vDf zrX4MZ&3}qp+Fj1KqvXe*IiP4}dI$eRBTU4hKUa%#cpQ$!3AX3I)#RqWzaZoxgH~73 zYa=TzHU)FHxaz0cTW)Krd##gCt_R%AdnH{f*%dRdHbQogFs@CHYR10q4uo01s%gcaX zUa95YlK)WSw6I8FtK5`BI{9I+*#^GC$)a<{+pSTm-`qQWd*TNPdq|qZj&e-G$*JcE z)^x7At>c^#wQ<+GVs~%3WPt=CF}0^z0eKWUm}mMni#rMQ_E@9dBhCosM_zop>9`9eki!ZoZkwYnbR0BnU1<$ zFs^$+`$IOXSJ-*?;Sbomhk~mXv0R7yFa61MlRM6cNVo0y&`Fxe_0?YU!Ax#pBeowC zB224G`e>cAETTy8^KeOo>Tkg2flvu+ik55lqKFZ>I`7lT6P)gMB z$-5h$>eZutu)@V0#LZ)Q-g)og71Uo`F>&l7c+O|4(3AhT`Jd7&NAXUL8-Atni}@R9 zU4w%ARZ?nvZH)>W0|L&9fhoR%AbP2= znYWx@V2FWbcQ-e?)#v!BV!m!}8M>M@_wI$6wnFkB-NhPUKYF}malbp#zthOmU9sxs zu)RI2F}E%HaO2JPPW#Vz)6?L=&Y+fK7k$ZsA%|TApqOT#tit@6ElLvrn?o?5h5s?>=#uU8}O1<_0sjbbbKW zhwhD+ub=K6O;^m!zKwFYH(cy(O&%giO-)mfbY!b*J9hhXmLkL$ruu4{zu;+2MT*kp z#u`zl4jMO++ezBC2FA#kwL8eJ$({O*<$=rM+S{5_WO?!fL`lk0G*4#3u2@7?Z(o_L z@OXu-!gGVGFx7MTcSY5xSd&hZ8h04sf8A|{{uEb2a17a^f_Od?JLU;d5n^xmhnwe~ z*|6fp*_xteZ?^p4X|?(l);c5UU~%Jiw?al{;(E5~U`jmK9-i6D6r7YDJTqf-3ss00 zda?k5Pr>!2+WBm(Vhe&k+RAbo`^ zA!TFxk*!fRC(ann(jp-hb*NYsQqxI}?{9^);7elCspV;t!<7ksM3COpkubV%y}yB^ zWn@%Ey-Am3<+Hs=0dF>vf0=Sw>K47mdqyGV^>EYX2RZLL6Ps>e*8roWA17iLp_s&% zrHd3Zftyi>kfOYBqi)a^+;lnmB#O3FL^_~)Gcq2^L_anyVVTG4-Q3(HCy+d|eAhj| z&S~0nrLWfA5qvYaU$3RVi8!j?=m=E4YVGrHOz2K4h1uPh92Z5Ck`w;}?Rtmd{EczV z%~SkWuU?gzD_Y2`SqqOa{g+)p&dq{QZ0Xq5PYTLm>T#=1d$|7c22u0p=qJEbbvLD@pL^#b-CblJ82 z{nEK{(t{SCKn+H}FuI{P)N?Q0xi76FE;!Eye!wd2c5Y9-{cOmQDByZ=d$FsUd#_A2 z=yN?h*|ni-;GJLp1Eh;jofm7mzi4aCRuBDgQTr=--nnJfgszy{6IGVabT9Na5ccOtEwdnX&JHNs zLe~I(>&=+NYKVw7`evU#e(i`{Uo^?Ax15vQRud|H=tFd#G{l2v+!ftbwqCUtcO4HI z)vVsQ&rwO5rl$%;#=_3604TsZM`I6u^Zn4tVaelnaMaGvH;hT;Cf3W5 z1<-}FvBw_b7mrhf${+Fa#-vLrZclmA+PPiY4w-LrKsN~68#38bQ$snDR7Q&o8RvD} zuB7|Dh&|_|40UHFvfG)vo9gazd$O8maaMuGePMRw;iE?-p1oqR?Ak4@j!G^^FKsY8 zV126>dbD_;p0+(VnpL$`FH~7w&9ya&E$3w)Eq`MCWaM%X5#D!k5u1^jSq!~=M!11| z+UUKpSGs{5rj4AxR34)&XH;Aa9n-Neg*!r#sy)(Rl<_HsLMey6gA}Fb>W<D zOnJvQ`Be{Q>qBgQeBQgjeG(Y1So>o>`9Ix1TK^MkGd?RVxmnT4?|Fjw5Yn@{{9Uss zfiBV-xTdb|l2;@nlceqde!hqN-TO7=dDj{n4$ntw4j;DY(s^yMLxya3$sx~thffy# zR?jZbq?-*5qP_=0a|_WvH2btk4Un()QI~d`-+fC=!y=QZ!;@+U}WJUVX~-WNJ~o&IX+IZ zw#IWYu?8T2|oep)T;^yTu=G)?c!vTm=lJko*xf*>}IVgH-KOCmO{ zN_eenEHRDaNc9A=Mx;4)E2zfvhC*tH{t_x}sjog+uqGPv^dBwa zjEpM>c0_BFd(Sk=&7S&sT%cPf+Mcyw)9DgYW7e*I=!bhPD%#3qWW8XQ+CV{J)sOK{D`NPL z|0IMW*2nz&Guo7?U`dh7YdbfBShV)Bby-h7cMT_*pfxxkI3XS>fp-#a!Rk|wmO{&H z32MTyaZKvYhY1i>PCJwZbvwrQ)@ox81G(f1NQjNqnz}e%z;JOixGoLqCNy2F)Ianu zJXH_>^(KGhF>(CkI3_CIA5#ePr z>O52Va`HYM<`D^{hD=OOGOeyjt5?jeffq3K^=MbO#a!biW7m^NX!TDewH^-k^ZBA6-(~2d> z$CKyB95HZmDv3S;Z~Oss%9}U=nt6JypYQT)3EgRcz1m+Lj9E@il4^f8EG^=UPew*| zPzW>&wLhPD2dk(%iQjQ!oOf+F2RGx=CAig~$>0h{;au-zBgaUOB2n>@&s&IEmBWv+ z+7I%WcmJb!boq|vYu8-2824Qm;&HLP2z#QMKeN6rr>uPYCn+9IbAz$rcSEx+)lX@J zX)`gMTRWqM;hZLL@~c=^Z(PTJT@V~7UH?ZY`9>3Xg*h-`ytcZ&0A8Ey3AbKv~UMGe<+ z$nRE*m4Bfa3=q0CW;@fQ_*6pc`A%|@!d#gl?))l>KBbO( zTS+^6dxpn_vmS4Q12IUOj_#%T{c_uBG3H_G_OU0O)ZroNTPm0OMh!|<8X{4&)3ov$ zGB*hs=zDtdS&~ohq?{_~&9{YC3e>!AD!HmNrQ>3oBN2341ifBDE#lTE#4ae9z@(D- z`l!BosIGubB~$K!px{{bgcaZ3KrlNvP3cMW+U}I+))l+efkzg&!%Jr;dwj;Iq5S5t z64MQv!2{E~CBlR8?m9~+6j12lm?@9>#K&K6f9*;yOI;@-su*jyG0%90yl|Z^LFZ~F za2b<}c?ozPZDP-WUM6z1?syI7nsSZ_>**27w&Ze3XUgk%Hh{;f?-s2sZ4KhajT=o3 zvEm17BZlYI2k<1}@W%H~DS53V)YRh3tTZ$zA3S&f*j6;q6D30_UwV0+5un#3Q42#$ z)BKkT0f#i%8b6rjDf#tx;(`zIN&ho(hWE=2+X>69k!@v`h{{MkhSTSC=czVrg;@c& z@13%;YL+kf;@+DeEo3 zCPK(op@W+6o=C^Iv!!~C4J+;;t=?vG)ySgbGJJ8RDBWtb(zgsXQf%KW37MFfSh+p% ziCygSw+0^%PxR>lN+n;bDoN}`{A5BKe6M&9)uyXn1B z<2kY9XTeYW({6kI-p=XH4i>;C8kFwrmwevNet&rBMolNG*b;&K>p-*-if*fyXa|XQ2)0kablPUW(yiK&7 zWg4CeKQSRvS|$-FbS7XtJhRDrvNUS*{KX4LQO&q-ZaZ_iT4j+!&TEjqb@A@w{Sw8W z>Lm=2+1c5B6f(RoZ`GO4b|m>_8e6gYRKB$^W>*CUe)X)_{Afea+TR}m7GPhlI(PLo z*UmB;3fu{}Zeq_S-cVKd-r??2LBr+m=4Pz-A3t9EaMe%13RUj&{Mwe5gyHcI= zZ5`A7LjknBPD8sESW%HbMpi{8P!&5T^C2Oaj9nuf`d2EGxT)#C=z(a-&VF25kZ?u1 ztPwM+i@Tzgccwq}$6xqrm>&(uC2(QC4~k+?o*4VsuXq2E;B02^&?5$h-PNo#F6%X( zcez>-_dalPzRA}b(=V@?9ClhQQ9ybc)-+@eLDhLZHp1HaC2trn2U48kKk6o+u%b0Di$w;nxWt_`0 z5QsB@I_wb_3|c%`TqF@!SrM`%fUd)S5nW1^2~8bOF3Ybh%~uKtzW$X=wt?>LaBwN? zf8h<-nBBpEAY1Fp+m2Qh&KAh>yh#E_d;8g+S*ZqyeC-kU)YMeaLDgt!#eJyR3!nvK zN4vYl>OONVH(R#O}314I=rAt=qHz)hU#f2RG+}&qaSIr&Uv*GASRX<#U zOsU>`Rs_hdpB*ae;)Q8LLqk;Mh*anG#rgRcD~70Am%NUE)_abqapE^kObbj@EtANovPjbjW^N`_KH9}{vFpRia#C1 zoS>_CCcUZMUz_~Y%2zqa3KD|qZWL)nzN!nSE_oFm-H6qGz>@BRF+fbo%`d1O;D zWDj^&r-SC)`qJV{J3EfSOoe$lI7LrOa#59zXu;h4Jd=9yLjWu2xws)I z+`Sww>uR8FhKyM)HTq)VQ}V3%d&eM!#{3Uvny7`G0;8j&_rGvc?*Gry`w4pgoe10K zH`5mJ3N-UwsVTXs+|4%jSQDZ3J6UnK{IE-%d1B?pa~iu%Rd zpL>@qbvszUZqp6~NaAF(X6yQm8%1nXf))>be0-d|3h8NR*QL8Pcqup?K02QSM$l(# zRdHD(OMkpT6PDUc3L*Wbf=C$6rfN@EohSPAU$|%N(epJWPuA9wgZG%4nkqFP^grA{ z4pzQ01Se$sD_`BV*P!hQ_jb;#)RE1k`AF^O;TWM%5gPr02FOt~-9{MRxWhw2vM(t5Jh9b8v4IJZinRUIqmpnUa# zAChCKxjUXZnS&x!tHvRMoAHp?(?ERr`#?i2m7^7ZxU+Z2DZ$!cq5cch!Kf?DG|juM zGm0^R$6VPL>OioQa~AEjI4W`&mI0IhK#2J?fAe?| z7I(7kN8}E=JKlN}w{zDU14F~0kPxG_(w|Q2qe=y8#1Wbqz$OX`yu(9yNGjre%7Z6P zM89yt2N9V<*^jWf5x;LF4R<&&pk_Qezmm~kZmr2@GZAQ7hR#LN3j5%jl9^t;inZAl z=HcmX$V2IF-1fxH!-I5`u?2yM3lI|f2o_iqF0Iko#~qKD&w&$k7z8Gf@wEs&An|^w zJBh@Tf%LtYH~Mv+1@Dymk>Rf?MBvAqxMD5E%buE5gpljn(2e)+-$SaMQJFeq#1Tm@ z+;I%d&A_qyY8?MU=iNl1K301;jp?QDIRpR_gWm~5cS_`|1~z}}5N(@wC-O>2y+lR> zK2&CNX@ws^~=oyqgXht$DwB@wzgWmKHXyT8BNf<#iey1Hs4_{EIVPgLhos%&o0iq|{$tcvc^+C{%1t9$y4(S;r0&{_2i=B+)I{9tp{TPxGFU3 zr~C6-=vPtC!}X$AXBU@0FPZnFG4HR%2IZ&OplEcc%wm!G-um`^0xF(`pNwg9LqAnU zj3H%y7Dy{7sObCaV^RJDl)HKXxF?55+mcd#L(1K=Ti&?OBr@JB0m%mp-o{gSYb!S3 zJt30xsFD2nop#zR)dHQ|b9+|F^!{pUz4k+z+!K3(h%9=0dwczhvy&Ey_Co!6oRQ1Y z)WSlJBq8q0lu42Ij?2A8)@Z&qMe^S4h;#whLu!5KM^2MF_N&LN;`Iu`pSiE_-iG-aWP4`B3s@Q>sninEYwI|dXKmB_Tgb05}7paY*FHK z?tyZ6AE)b1p{S^csvXXG$8CMeBO)rgv%hb2dYOqFN;uhCU%G<9e9Av$`s+MhK{=;5 z`YnK(-`=*f9Aj@di%cl!&H$aivZjXHc7#7QxZrYnV z(hjECXl%e(WVeHU#9d5Gj1Se)@&`kp=DT8^EW0yu`5;?6F-T!y!N{_zqeFVU)CLO} z(GOTz3DWyrSk`7|_0N&C=Uh+!%>oP$$AI^svhke#{t52m$yZ|1{kyJI7+ek)aIj!y z-4xFbM>K6$-#(^`^jnEv(bUlWyUnt}HWOwBbJWt>x`VWB*nzP(aDXCyY17!`J36Y? z6~}I?BIcKs#R!NuM%I2$!l*DBZSUg3ASQNCRaF%N%3sM)#K{(w=;6-JEUZ;`4uwiG zJV7D4MSAr(jMQ~%T&%?|77PK@q4F7PTv19NJ3!=Xq4zh`ZMjX><25f7{R5?y7Ik51 z+kUgIBG+Spdrcfs6QN(RH~=n6B9=|ND_#2@hB)x4fRzr^#)N}hEdzt^-9uH5rkQdH z&m|0p-0ouv|?k}Km!!&b8%vt6rY2A`!{9vPh%+Zx9D_;b^xaOug_jZ! zZFlDfB>r@<1smh|=8cK3gd-p>awPz$LYpCLS23awG4cN{1N}c7_>Xe*uav|Dryjy~ zof_%OTf0VFwajZdC71FQv>zo9@Q7{b@{DfR<4|LSr1!@gr-DVwV{m*OFothouR(i% z+{|+e2Bpw9=Aq0}X*0x52n2_KE<{5axEKX*0*5&h=j$T;JQry6)WR-uG;Bam1B2sfe}8FXQ67jlC;Ht)>Pu2Dr6wI|ywBAo zaB*=zeE5(H11w0P&f@^gCk_C66{W1ErpqERM>{*6Tw6e%IX*tN153yP3o8;i&(zPdKZ#wUg5~p2< z=!NB{BAL=J_Lh7005Iip7?K8U6a9SWc4flyqRXNBiNoTS29?B3`ajwcX!li7n=4y^ z@(Dtp5YuinC1c4OyFgUfnJDSSs4jVlWh>JK;>*2h6>LX`y9^H>Uc1z|7|PKMKR}Ie z+ir>g1Z+N3NUX1~KT-_n)W?q>`$}J_Ts^}g=e4-Av$KPpm3~>3K|imHt8z*{+Sz}D zQHrrEnyndZFafvi`#lOh0>XlR*qL&v&tL1^0veZGf(SN?S{xO}HV}T40A~fq;83U- z>S6dY$1ml4Vzk4kIFoY z|Id63W~?fhjMC5qo}kf#-`(*@*|g#}c9&B7taK`F3ujj#~yjE!*wo;^f>W_yD<-#A+7mUHSIy8wLy%ivO18%-UX}vL#K93c8$7 zXWlcWfrN#H2?z>CBqzh$X`yn>{sf@6w2h4=07=bpd*<2i-@mWp;Tc|DoP%jqiwpSp z(Qbc5$z^lmm6{s4_+_#WY4P(5h zV8D3H`XKMYi}xm7r-{kQuQ!4odV`Q~sUw0O0F_EAd^2@rM! z+n1SYoO@D)rGVrLR>AMq*4Cw>q$Da($RO9QT`TlBumH*rc4Gp!so8ib6Qe?MM^V8a zK}Pl0n;`r7Hezb(p8MXC`vn0hs6qg$_Id|neU>4P}tlGn9CZLz+(M&2heogPeDE0T3U!GK7;PoHo0!f?# zrCy1mHBEkW4G4?(A3wehnvxs?36PUUIN(8|D6Q!wknSnbZ^l_e6et3=cmi04v(p_6 z1;I4`i02*gv{V!vb`VQM-Xkuq0h5cFXMU^wCAgZ+vL?jj- z#g92TPq1(ajgaNgULOxnPvk_~5m<11u0*>#J4Na~pgJT8fs2483p$QgJ3j%Z&^$bR zkco_&q-fWyds|dd!QUQETOn2-qe*(SHXLl4>h2HXGhZSje^)s!7uUw-+ZKqTMsol` z81Yb6Q32qX&)FEET9p?y=|Morqc{BPEkMhho`+UItjsx?el(MA>*wcY=P6zuKRGIgLG(tu{ zq3feXn6k@fJwBl<%qSQC6l{Cz9vfqRc6PAdKNb|Q0|z71=lrzPtpBQ%RD04d)O@(O z$?uOhqFK}A%f)3QER0s=g4zSi^EO6h?? zK@aHYKJ=$ckx*0TGAc*_Rg_M7K`urb# z(O`&Tf=%D#BxYiHu(Y%!3A8|vsS=0-!<}{d@(=4ZR`aC^boX>$u4EB!&biM?Inc~( z=wN48h!fvwG}n3Q8EK6yw_iWztaFsecp9KKR&4AJ{7Otl#C-Q5aI6GeH?<-t&6t`U zdJEBO6Q>hvBzy;lA}r z6J*K8$9duLg+e|)gn?R)aO5d^Dalq^Ru-#qtW$l9nwW|FL2M##3$xpsnSQo#Wq27m z*ff~WhhBIAqyYn<>!m?MjQ*kVXpJ(8!1+XyyzwoaZ9 zV7?OdEMy=e@T|A6=8&eSR}3A3fsI;P)L_RfbyrNaOixc!dI5W5-tUC=770o7YW85p zj~|@iLStEUegoBW_4u`v2|WH;kwGiu*n4m5YzulhzdhTG*~$_0QoAjeqWXwKuqfRZ zV^oh#4)`HXH#UD~yo$1OaOfJaDH($gF{!B6dGG?j2k2|x#Vu}1&cqM_0RgovOGN$- zMzjJQE0NdIzoZ1mfKK`8^Y!YIAh9X2Qrqb%N}c>xt-9MFHgAqUT}OmA6axhcGl0M> zq`3U;R)2Cxh1vy3q}ZgJlG*JYM6c-s_D6sJVyhD_FwgNk_P1^8cNkQ%Vt>2s=Y|3WX9@;&yS>?Fous^EAXEgz& z8-3tgy9A6LAP`A%3{!6N1UUHQPoF-8JS}fxqI0Ntk?ZiI3v#wUsK~F?-i{0ij3Rzn zZyT+Dsq_8%%*sk&zZrTk1DJd~*5gBomhy=_4@5*JPLCDX|4u4Lb3Rr(D{$qaotz&E z&N{?i)k)QnXqTAY$0@GKZ3gLphKmb4A|fKl?c2An-^4HW5$^&At&tT<7@#bar%`SI zl%qEVIyCS9fsV*>Tdc22&5|JA_%V#g8Hl*Tib%`DG!w7Wv8o4|9y4Ckr>5_^!#>;DFe>~BAQK8&lL8lO=ARs6_yD|eTKD-*&EfauGFxMy`EIbb^ zPi5uEDM301g!kq7(NK==RxHYW_cFD1RrB%`vCDX-=N5aSkpBZX(o3LUK^`}6D)`KG|J-B`w{-b<5G&d2x3UCLknqbv3Q6s;Y{ZmNt$3-|d#~O&3f2m;!eYs7OfS zpW)%%^Z$rx8PI3^tgPKjUmNWDpPMv@CaIU0%<7D%#OeAZxNMA%mDPbj3A1XxmA6)z z#oO=cB}AJmD=RF<8c4)_)6o)l7KCM9@8=#k>)2>I){TCS?KMveLR_yh?AX4XMfBg9IbS$tHbHAF# z7Zo}!arcTegAjtC*D;4lNdmxE9AxFVQM1*qSZ(K^qHZVzQAZWew;@pGOi2KhM**An+heig;gpQV`Q zrO5_DM;}u42|xYdMjvbi^qXZs8!nPX+#@kWTAb@&+li5j>$bSKIL39VLEQtU z7Df#PuGsZP`f#z|z!58E6*gG7j$!>sK5LLnH0qmqW;Io+Uf@E^6!{=_3@`@szOPrf zbyA)^fBvC+e>n|W?6|BzP`~*NALdn+(P?zmN|AvONQOYH=>Pf*3pIanj$TGOU~X#K z$FG^^M}6667ec?=Z3UVGuyvBDQ-I{%)Zb6c$jFFtZOLz_*O6GTQLl1%R3B5^5R(6D zo&mGpfWB^jG2!lWy2^->XDmKzhaLw$_r}17pg^v51><7`(^Q-S)wSOW8|m#jr~GmQ@Sz6AXF0W=f6e5L z(~#%^?MF{@=u3%R?&e+3TNrDo|0zp51Ze-7sUw}MketHKdO4lb*Fbo)@;8Tk1215@ zg;j(v?7Gon#$&)iZj}k&1AYhZxy4#S2R(LdU`L9vqW=lVNZYFe%;TW4x#HTgPVo_V z3N%^Lvh+43+Yb?j`$u+r9gL;sC^*6~6JJeucI8Ef9m=mSXRAm$q=) z-Y*Yc1PYQg(;_eQ;PIS5aqiAsw@vafJXb`G720D-h27H!skyO{D!rzwAXuVa?nMFo zn+OAi*>3)++MgTaDu@CqsBvr8-F0L`c=^US*TttDzGwID(m2bS;w|?i!1@iPFz*By zobkMWC?D*FNx&^2o&d>0y}?H`iQl$I1&U9_7eY)gWw+h;>|LHtYnt%oknk&s&(xl{ zfCU0OwnKgq{@-`4<(~r~D3t5gSPJSsxU=AOorZHwJw4DwyDzZN_O7lVv6{dflQSxk zzp5HVLd=+J3_agawHT3WSl^jx^4s4SjO)w$roLu9TznUgj5P!31`v$BVV4yEvMHLW z6_$$H(DQS$d-v`^0HeNjhrFd|zP-5g31gME?ljm@34Eqzy3hii8ua}{ab-!+}B|!Mg!2eSA}O6q8IHQ9E#<~fC;2|nXmlg zW`F-!fIVQ2AX;;!(V*x{Y^;C3*`Eng{Z9ZTe!0F*sqdwf6ms1Y5GL!`_CH?I4=Xgd zPfxAV>_N$NYIm-Bi5>e!5Ab&6YFsw0N9yv;4<6OJ#!yy9uXE9Z&yL#P^Mc!XO z#X8%#LrknMnodeg>>C%STD}M(ENZnzob~nf6}8=(c~1(yrP8povnP3r+s}RMb^vah zLaKZ($}Q%rM!{1K!^ZY3olvpB#3h)d8ct1 zf9~F$nbFIXPiaet=S~h1j9T=(QMJ(w7KEVF$}J`)rqw1(o|V5F=&~~##m?F(avj6v zfbaaSUN_M#hac4u3{<>5u^i1I5eB_U;+-{8ef6LlKbo@G8>De z3Jt_Rxe2X$uM__`8za|HZ^;v|<~fXa?||Fnf&nr2!yz)*%Y-(-MSu#5VaoK;BOgOtX)VPwifhum@w1bVQV30U@nmh%P|`i{Bs8EwPa4xTG+Z zOMj+Usa;W6u%fb4ewo=(cSxUDG1qv31@01w0|M(11)Z|q#72_yKgKmz^UdL{z|M{T zGpLY)F(oiDg@q_#2Xg}uCxK+Is;Y=;Jv})+-F@92z>(A~ay;V~lE$I)zrOZ+U|>gH zwPqZ{XOPa7@~0S<|6A}+WHBuV^Z0+TY8q!1-fDf92zov(2t>i*deSEEx-oCE2+x2p z)VM0B7k9dR)q-w6vU70&aq_Cyu3@Iwia=~$s4h@YPyi`^bKT(W`rwW_-s?4SVW=)k z%#VEYC4UN{XR7%+OhYM-tQV9R;=phSCxAU_VYHi_`}Dl70ECe=RR2oh(o1ORO`s|p_yOH0E-BSNq+soR83L2?F#f_=gx`@CPj zEkV0b^X-$`>r#s1AnwTtW5#*&hP&HdC$=cMH>vm45Fd3w9v5Ut@ zXF$@bJT{n(W#VSfKJD?rK95tBgVWV3Qp;8=q618sc>tXlhZ+3#Iwvjm4);lr2u}4r zR2LE%Z4$Bt=~J)$9Vr&=>iyJS%sAs1RCI@J2y;-#LqPCCk&@3M9wp>1nvDf_bH>@A zHB(%c5+b>E3#6iOkAa$Ud=i&tTd4yQ(vp6}AQ{4@Q+vF16;F*NR-__b>gXN>m<90s z-AnUR+TRz~VS{}BV&c*5o}vn{Sk}BfKHDkyKtUYWR*E~nNIYOuz}~L{#PQoGBILlq zz}8!E6O60y&Fvx^ApBsoP#~(SEqiY_;bL$kLdTO;H_ZAZ20@~e+_>;6${NSOaS1*f zLs4+23NsB8o;QbxgxIyKqk+pmkS;}(#H|hF|2c#%v`mbUfM9VoD-~y;x6l|vf}PB! zeL=u?05o}0Xq5nR<4yK!Uou^siLxgYCe*UX1Rk-ESfqP zbi_(cSDY4e|F!J0fw{_Elo~&XoxQt8$D03d79dZn6-Y=|1U&X1=cwkHOjO#RxLCmo zYHa|PT@>xw11|rSZB#+SoKlk?5HQL9T(#MK7X-NdVXv7;yf4p_A4FVwfK6>%SJDq8 zumnD5O27)DqwiqKD{vS~hB&Q8IV3}Ig4w{IK(#iLdBPL`#es!UbP z+>TB-a&qMO*iBhkBJs1v=9GJxDp?A-C;Fkz7l1L}z(XHFfB>z~7IF`3 zG?fWi?=6f0|A3UzRmyTq`|MelBX8Wi`Q0j^_zZ~2tGFkH99=Q2QhKkyYHx@*F5#20 z>3o&gxsC0kTT%j|ZDF+HJz%ZvO>82Iw&)zTItmQ6fq70YV#L910&le9z*ZpvCSwh-51hpbj~CWz!Rh?%$%~vuaFE_US$aEhT}3D*%i&ke&g3(qG7q zP+yA|vljA3AA&%rkNySj-WX7J!;n-wZnuI-S+C9vzD(n?dp$+C0%TsTDi3TReO_%S zNZ7dW;cxQWiCLNnr2}JsV@zA?hpDN`18DuR*3gV{GiL*`%=Fgu$kKb9tPfkv@ zSuJy48)5=LgK6p4eLzVab5Vm8L?BQ*pCHydv0t=d5{7c6FKDmh2m^`$r_01BT?(cb z5VY)SgPquLen|BqjrMaz96ul};3kC-UFdmCW#v9CBgUDc0J=L+^XGt((p?TtV{N&|U;y&cWr^#uz0xq6wtbr050C9zX2)FOCp_c)m{^$~ zL77vf%6qXm8f=UHmFE(r#cw3E#_x3VmT={1zOz_;E6YJP!7tYo{j&QmL8P${l-!ZR z<<>QS#NLhokpS{2`NXHeQEV^4aVeSib9gvnt@qus<#d5|1B0-z@GwY!BT!yIa`6BW zM%MXW7w?fZhk&e951DBnbYA`O8J_djl;|MWRTFG8FwJY1GSIr?-@!KtZgjgO6*OE_ zgIhCmF;#SW!|1?o+2BlLer|4IYm5Do>EaidO5go{AhJASc+^#2D=I|*X`NH6uv+B; zv3oh$IF>rg_Ji+%h{!h;HrhE2<npIGJFNI%gM%u-*iix7CNGv&$SE6`8+oS(wz@d_940=@mq61?6m-n2*FeXt zudnk6o0aRhU0{nI?5ns7JXSj zgW#<5T&H{(n|D)FM0xu>zfRn| zJ_LvY-MHYA;9u|ZnEjf*EEd|&OS*e_tW{_?s1s>ML87(&O%B^u)jU;7Ldki$0v9YmE+u}5_ixordY9}UW zI|#!T;JvU9L$q*wjsBGA-A)I8`|FL8F`BFTZrYAJ35aNX4hrp5`3Ax(kjVy2TwbZ>&50${GdGrc|EBk?m zdH}Lozh3T<-b9iytRALBgHy_0Suq29)|+yJTLWmpJucG&`DwHOR&sTGL0!DQQ&0w8 zufExvQhSI$ZCA}(IzdVCbk=OBFd{5XAXVQ(9qoCCEX}McH=n~`w3~`Qc%!HXflv*l z;vH#xhQ&Gv+Ilt^4`vPyj^6fzt{7$#uX2GnoSrCbO8>RHjaxA%Rrq zeeFs+U)Q({+)e0H#C-lTs(lmk*_DozmR6TJ%sGsg`t7&p*mGr}t(FGluel83X@=iy zvuW37R6DP&g_l4db8_~Z=*0CKML+ZRn%o>EeVH2c#t%)ovKs@FQBcTy4xuvON|b3W z>M`SrU2`cPc9X})lEJxM^xR>mH~5!oMQYE>6rcencN#@L^B1m<$!8Vx+{|4Y`I!?8 z)p;W$bMx-iQ5iZDu9(k;DV;`iI!L`*2LCG$4Sr0W;4Qzcd1w8yT>@ss7kBpFn&%ac zy(CM@O1xQBbtUtm7fZ~Q(d+4*YT!B;=!4=}+c1~#nWPAe$?{2}m*!nhv3HW`N?^MA z_7XRLdJz#M-O#i!{K^p>UHQ67c~RLpmtF^HgZaExv|1+x=T#cWo$6elQaC47sp$Iw z%I1(atl)PhK_;{xf?vJ_fjp(d{Prct-!CAyIWb?{C&t1&eF?mRc?u%O{QF)4AM+#) ze)H0QzIa`@E;FalOPDMVyGw8A)&4(7dke5Cx2|1yVSpltfGC21lF}t0Af-r3cMB5I zf`l}RAfO1+jndsAt)JR zF*^>*f(t)}?4B0uwu>ZWHNn(Tk7-r1RArzw9TE`{F-lS~U6FnM(zQ7+JR$icu>fl1 zRo(H`F196B!$zE1tgJ>v;;>_N_H4i<{Dd)4^=t;js6hitZvN+`RaMnYlWzlWssf%4 zBJdJe*SO^O)e~Gd{Gz|`w5T{06cm{CXGf_xm4~?xZDAc(g&JKFc^nMyyI}awHxgzE zOOLT192sc?;{S%nbZb~2kn5p&_~4<>WrelGCv6mmN zad8otJNcv-thDv%&`b5 z$q(ONo^~E_aBzrbck_vVd34dqZtZ6pqfkla?9u{Tk^UfQ-QMz9)Rj74ebf_kKXqsk zFi+T}oFwpVx#p&Q-|^9C6w=wx0B}ErOD{)Zu0uYm-MAWPX$*QbRUnh1mIRqf_-(g+ zeut%>pj)%wYt|Py-s;&%SW3`8oNsHfKqJdosWze8CxEmVNYUPJ-!cOJJuo6ABmuSq z5yj@aECEm~BZ`(3Ay(5_Yksaq$M>nVJOTqFK=$hgdP%WUo3SOJt|J~pRwb)Lefp|i zox~JpZv1oKL1*h~p5o6ye>c6*?Z%jYZM;5)37ttpedAQd&!g;+km3j&P%qx$qa8ML z2S^%r{vHYwbP#ok^?Eq{SMe}`g_6*Ntfimt9z zqcL9=?dsUFWe0@ksk88ujSQTC7DyZN8tjX>Eq$fkFKisT0O(Umks^Ye zR~qPNMI??cUu8a;P`)TxhR0+E+BntC1m_L zb*Mgha%)xhD<+X$qGC5bG97&Nos86i831!NIjE&ZLay`FM{%Ye9UYyLg(8(q=wIZv zHCySbZsKA-EN$cC<)sH+%0|H0d{5f+)KX}g<@8xy*+h(^pBLg$Vyd#!Zb8Y<$BoaRg zAJ@7c-)%H`4+vH^wA;w;Jr{;-$JvL2gH%q4wQ?W!hQ8l*Z;P}`;VW*$Or=3W^+-&t z)jBu<2n^wi$o*8p{fq&BeP3>8AgY;@kI+dhtUg>$+x}U-eF*nvR~}$gRX8) ztHhzl!TQ!_VYJZmpTgB^N0amk$!s z?C9icST`m+-Q5h-b)TyH@lu5!)D6W~J9m5ee};WU2K9GKwapOKiH;;`uCQBv0Nb~4m@gj1o?fUOqRDEuA3459R%)JUz?xjP;1{PQ1ab3GR`1-{NEt(Pek||1pY>Z ze+vTt7C`>~;lFt=wWLGomcfIE4{{nuiY=A?=z78OOMIF*AC>Zkc}oLz6VF$FO9O(Q z6k$Q{SrH(x7;!`ynwZdqv?P=9J50@g&3D=~mPruQp{tW??dwya5)V*L5OlMk{Dgng zGOttj%X5*Q@u*bjx6NVtY3>Rtw?&XN$|&$!e{gh+B}_}pU(iAQ>JdU zb2O*p2cr{6qo5|pdmAH|4gWHEczD1b!Z@(06RUF-$SvW_nyq~eD#4+Vk?H7|JrA?) zFMi}!2l;{rb4fT8gUQDpP^da5Z&PLJ<<$VID0i|cP-_OpK{F56Sq4PR3efl>nG=wY zVAU&SXlfX0$ov5%b9)@WTA<)YIth6X-s`cPm}s z9A|Gx?_3$G7KgB&4r*xztAXj27%whR^9rM#m&WXH$FohNP5~ zRCl(ghS^wV3DE75Az)8sS&zfkNZ2U1rzeD7qsrRGKmrgleru<+uoP(@6vHnEB*UM70|^1BS$DrAQ`*yARMFq8s>8(;Pqu=t)iBHf{4Lo7+{YH3xN2`2uI~7NpY) zWfSf_(RuxIP)pF$^3m$Ky#Tp4pW{mG$>9Xr3@ZNvKrZGk2xCyd^smnk-YI`aj49#s zZ)v`VK;4=DO7k7f<9-Iomzec2jChGGpn;Cl2Qj&bDNA?E8C&Q^F32a0`D)vrS{A!D z*h1T1G8jM~7Hd{(?>9bEW;L+p9G1sZpg_=QwHQgw31`+bF6mH`6EwPb*ECPx0U{f; zlBVZil%YV5a3OSz`*1IA{-m*?p(V-dEzO#tgMi(k!*Yq;kRsQ!pTfa*>wg{zjBINx z4_8YE_kwsC_VLH@I`^mld@mC^PgLK%15!x3tbN$_%U6n!2)y)l2qsj>$-yG4z}UJe zY}0BoY0=@_6BU+ODIRh6Y#!+5Icppk%DSb(x6>>%^aJ`^sQysZRy%j#*h%>2$>C}Q zHAIfKBrmK8pU@`(B;4KWgiY@(;tF?m@306eK*kwHJtT!w88V3hd%fya_9aNJ1k?`5yBD)~+Ab4qc zISkY+H-w)5@^r2p)PnjY97d9)f~yyR=R)Sye?s-=;S#f~aC2sX(JlQ);^@_{g)mCT z2a9 zX@Z1=WFb!ze#lI`4+v0!os46Q&T~@a8?sgYYpx7_wXVnh^W>H!q|7~_aV&~motjDp z#jbiI6(!}=(o*c=ey|}Madu-|TwG2+&?bL43gD1&D6{UkD}3gEXumV6K_=j!jFkUY z{9NfiZ+zyK%8U4}+sguH8vfJ4f&ToKygN8W+71%GYS6oE+*q#TPxU-p+FnE-VhTY6 zNzRQs2liZZ2>KARznVD@pV-g*xRE6orIcV}W3M18T2EH13-jO@P|Bzk!msO}o_xT9 z%|Ko9aP$$>ST8C2Q!PoC2Meg0yhLQvnrKWx>=sSLCQZs?(F^V)L^p0^z_z6|BeXmE zA{y##0|SGrgoIS+mJ{cd@>yE}Ax6wP={L_(4Y8YwNx%e)^g0^KYod~ zr8;ZEXZ~qdSOA0%&YJ-|eVgU+;rstPdQ$b&Um;;x=u_<(W6X45QrJJ65c!Dz(HZTv zn-gw2kQ*02zx6(T>!l*#R6W2Su^lt^r-SJRYc8<$?5eeaxtt&Ppc zw{PD92AJF*SgZ=f^SN_KMusH9pOqQAcorqXoK50EhCoxt!x3zIq>A}vWJN52ITtVc zzQa~zRaF(TQ`@~E@Q<;-0&AKI#?M#Zk-Ton;`0Ngi8VylXU8X;z^tislr|T@Gw~Bb z?p+Wp0;bb_5z5ZFqI$MaGl$ZbPf&A34bt%XzJ{WA0Dbm>ssQSg_(m*W{ZE|)b&%A2 z-MN}3&4*ddE#ak0Dw>3XJnE~^d*##m`2Soc|E;S3Q&RLlme{CZ>dHUGoUo}SK0={r z2%t!orz9+h93&`cl4`7vH!S6z%B`38&)+{1n`aWz=6b=*JH=x^%>9C*U1QF zXdrY~*6Vl@8$#G#hR?)vKRh> zj@`f@V264fFoa(EWem?<9{;$v6uB9mp0dtI>wFas%pxIlXf^5Zr6dCsK>_7u1Nw6X zufsz_MQMJL!%*MB935ThbDY@jsrM7gvYyCOF1O`vkL>y=igOE}(V4!5zF0!UgY6fo z=I~h*J5Iy*-VE|+-c7X}O?!!8yJ@M{2iudjdY+>1@iMsS>7zK=8p;JgW`ZR{EF{v^ z)pfAhoe-St8HK^;yp;}-Fx-guSZ_Y>ZM0x=!P6$i47emsy^>ttz-q0Ys@)cG7^6w| z!S-(wUR$qRU71g|@U=&<4LUjifuoO}J$H4wG-u&%ONFd?f41pT5+dZ%T!mNnDai?>yc$0{{p*)- z!x?IW`X+);&fD0TdbR%1Tw1pzqj@@&Ud-5qM+|tpJPQC*>GgVl4z_D=KgA9Sii%IT z`+N^$R4abDZcx2H6LSDkvzI(qMO*r)*?XPN}cyN z-@`pn0eOMp+AVx4!D|nVH?Ov#G3&G{oHoNvI*dgYKoI~phf`;O0}T9+>Z{t^s-s)?>3LhsB=~bS z1j4q57xU&p+7NE~o30MhkXu})-J(`jRvSAz{oe1cGi^dkqS#`Hqr^SA1jH73P$aqsYW0%ZR!K#4?%WA;BTNDbm+1N56EVg7eX~29UUH<=s%N27&D?rW z*M9D&2*U6GLBVAkiqK%m?!6S&bKUpe@mVhUp;h&);K753#ZZ(FLz)ENCk0rHS%r>1 zE4>wY1M|J}N_=ECn!$E_e~M+rV_DV)!Xm5*LPCObe2RNmj-4ct#GQoO;#m~8;o6nz zqm4Gmr&S~*d_i%E{x zQGvFOHjN}9?c<-DokDqjgtVHiJDZzIAPLMaRlB+JDNJJxqG`>v2S7e0l627?!x3>7>TbOXjpeUoxPwfOLU6Wx` zK;|l-GqIB6Zv9~q)MFa(18ok>!RI)YT8COBlTQ^OcxRnZk;X`w^#G{MN}xGA((8)ma$N3sZ_!6W~4k7ZU?#ZiScJJjH0mbBVqW_zVaE998Y{7=0fKw+iO;~}bIQ!hGW zVx-{!?H0R@-H=D_T6?h_UwfZte}?3Hv2wMh2WbyMYkx>BUoEQ8$PV)B_KVRW`f*FG zOWi5&Kq&Az!&)Ycx;>J;&g|q}wO(j#Z7mo^SZB0BFrdG6>yvWTZ(FlKx?zFO0WsW} z%afKl2a0sI_VxXoSZU{9Qyk8w0t-H zU@N^d2NTR_Sm8-~#y9eR6FZdcl-+G?`X037u#NIhh#dU0jq~t7fxol&SMA*UFXCQQ zsXo}AyIsBo>RF`xkLo}g3a||NLDHp?CL2dN)`_&-0MtS6%gwv1Py6CifmkypYe&8K z*+;PTf>o5w6mf&!AscqcbO==I#$R=on}EaFKEPfBOn*N2KQCWpEA^j{H#E>Kz-AC^ zm7ssy=kvXKouZ+~k1!hU<1@;#|Cs6Y4Rd&8Gc*jP#&#=X9<-YjL|(k^tvMUfIh`T^ zT?JchhlBU?kNRsMxB2pdjL+^TEbSthaoGgH98iiZ4Cq-4Vb6e?4UC32zho2U<8yl) zItvny9^+xka>Y$8Pv=0T0>Ttoq?vveo1zyLBaZXXiW!v9l9Ie0A9p}>y9iEJ-O!(< zk;K5jaE4uuh=AY$4I~lgk8X2$<7J%JO?&iQyc%lE3@DTz)ld{;K_2HH1NMx~6BBws z!&8J#OQFsVFhz#H*d####WcV3Vi@MPIC2^9EA{uZgLwzQ*3kkkrJ#nBLf&?cU69yw2le%kv>fR#&x7u!x^qa{^Cny5_PP)*m0&+OL>9p<;niTU_K>$z%!-%wj4!qApAkM zoRivIV~p1Zx}cDf#r0X&$Hv8FsC$ZL|B{L02)jmq26&y=I5^3aC{JRa%E-tRx$oV9 zJkYR*p(WuoeoqZ%PZ|I<@)BJP@+HdVwj43s|0H@f_lQ;>)9db2G4bjXXX&W+?(g0% zTOQ~#jeWOxBi%O6m^4F0u@+wQh=GB{d8gXO50+V_mgbACVdsw5>HxSxhp_1QrxBj5 zXB+$k-7RwvI%cZn#ZzY>fJluiFVWc`-*2i@yYB}V^U9Q)KI~>ZdgmRSoL94uqm-8W zZ2eEZ>$13eD`QK9^vw45!CtY#lPA7_wc)9x%ZovyJvp3pHN$F5Fp2Q2@Ws#*9F2o@ zEb^DaLMMAvMCS+ZP^owx#lvWRiaI(fy+ipYlaeyGwbS~Yu3L#l&2G}CHpZ2W1Lp)M z;}rL!b;q$UHqzHwMtcVfVy1p81YU-Ap)jRSaPz{-U7mj5Y@|<4KwMR|T%XP}l ze}w{x=88%E69CJ5Cbq0bztCX4-3VW2P-$gkSy^Q&d&(_WfOB3!7wUQ0{&<0|g#l|z zzlu=y+Ps0fr1+Zs)Vzb8W2xW2Bj!#oC11cxb?v_jD(vLgUQPqvW0}?FkMGRJh9ANB zRZ?fDFkUC)wPkNxp0axyP!%4bE9I}@f!48&pA1PD7)cAPeEpg)OFXb|xRS?*2Lo2n zMgK|&`d-Ezu^B)#kpZr95Ab`FkdSEH9<7o4;@4LU$Vz3Q{}u#|Aw;&}%fP$(~e!lt7{J5E!(K`$bOvDjc8{-*ny` z)4NLUE{RK`+;_CEvo#$dF7=r7pPACt{4sE0K21%%AGx&_zsgsyULp7!f|NsZwmphd z9Q-#Bq={2U@cw=6u}a4QKti`c{Lje3A`7_}RJaoL*t$?BGAo|jI3z9bzO*}6bC}7% zWU(eR>9XBIr@ucQSQTDoHO8K!*(Z%lsya~qVgRo2mdkQ@hd^o7$*sD`M2JcfAYt6B z`7lPgfdJC7xn`uo8-=EFcB7lC>s_EGGDRft*=GW=KhFbwI1!u-wZcMC=S#zj5bLiL z-PjkPGUk?^Y&=f3nRI6ef+48dir~)Gc%f|Gf0MajJAPn5D>xwv6H4fQE1>rSr$$&# zaNn@hZvzii8+C7-f-o=zCXo%u?#)WW>kT_nxy<*%isk#B-Bh&UmG%U6 zL0YCEd0PO2kwXe=Na_Xqcl7O7XN4(M@A&r^kpU9e3%ot)M|8j56D!KZ@u>hd)ID6H zF>xasJeQC*31DRzY@Z?<^S}R@n@fjW>gV8oZ>@)iOzb@c;OEqWVhMu&EfC4Ixq5wj z5@rE0yrrp0Ttq}97;!<&fbg$O4Ik+0rEaFEf0qPdVU$-?EC7!#EGg+}d~1e6k`G+v zMM-rwxwAIG6wgIY2o7xeuOTTkVk5r$a_z|}Dj#8x95%GYzwf}BB`qlBUs-^j_i}as zeQGlDY_Raie>lJs@z6uCPKe@r-|or3CSV7TmyuT!9kM_!8lVcip9r$vseQ$=(HdD3 z6KD}X$K=Xc8LI^NpoY)==nDe;LXa|pHi`zzDx~@_Ld|1iIskwokD7)a89ECu^(Rk0 z0UOvJY%Z23YV`n)N^#y@$#Ge&E|cGZ*GdATkfx9jrh+;Y^oU!LxA$1h^uZ<8s!y1> zB+q_+5*ebkK2KAUolQ{tXT@sCLwCUiJ`=Ks2rzsr)X9b779ltZ9&afjRr<|GuLy#D zCA)!b2pqj+upKakg@(Lb5PkFlRz|VBE*%eB!4B|*7aFeX{pevo%qbi1*0Q!4(EfVu z#ygcO(vmDew^fwwLjMr{>8r2y8!olX0=`eHp^2icRAF#%NHK6p-8Ne2kic6EYzUg8 zxS-GPW5eS(O+L^>!G`m}=s32OHGM!`Uees139<_4P$J<hO` z7_P9-Iyu^uv!CyN1h8_!5B`^HpEQe~>EDn$HtvUp`(Ah{TsVA${_LGWVpa8r#)pG| zLOQIDTi($7->QA1^6%7caI-E@-&4=q)te$+nZMt@oz?(=u=5;-s_|}4E>P2fpQWvo zq-@v6I`Lird3gKmx&aN^0vhHbWKWrY+Apv4?tM3K&OZ2ePzQ%Yzb6Kh}QeP zT882yC=E1Nthrty0gb8LLmk>PmSQ=_kmXTmkfaokDVx_Ubtf=Vj5njp=hsPK81PW!cB zs{_rkGO1h^y_#&0WzT%Ec9I`tVP<&vxogaQm1#>avF{)IP%ei#>lM4!oq)9r?n z2Dge;KZ2H$O4qxij~#hIBg=xD4P%BNJ}@1aN47UAEHX!Pm2M`}YCvky2|E+$gG(G{C@C3+iG0D|(jFn^leW~u@7M}3!a`iXGM!F&G?;@|Vrn$R4HOe|bt^O74tJ51-fW_#8i}P-p@v)* zz4Cgl>)r^l$m{Lpg$O;5E*R|eI`PXUUo^x)v%spSiy)rEZ)5ulyeV*ZS10s9=>yss z&@8?Ix%w95_s09TPf{XuyMgrqT{>o@OQWl+yD(PCv-+KXt6Q3N9_BPy))rHL*NI}ot_~ZLJvd_EwB>4*$wGHMY2(!cUwv=4q7PbrWmlbO6(Pp;B810-Ujxi-TmMw~ zi&Lv~VQY@B5gY}i(vPkCKlT&?)v~K)LQZq}q3q(|K|3Ef>pjaTIK^_Ujv;+d=U(CY~1- zUO$v(McOs6sUGasqH_Q@1hX5>g^{v67`2V+<1NQ?p;G-C#jl6a9> zn4B`qn#Dg88{SRL%oq>t{wHK1)ui55v)|=YneNGM8?a(}pqKnYr-pe1`n}U=u!%&< zrSREGBbdUB8@sy$FiBxkr5b;RfMJ&nj!(wqn4`<`UGrstQ2-14syoN%0uOr!ONZIP zbEUp?-G}f_B_MbKaRm6b+3x5SlM=Zh<`E#}A!RYN$$<K*XcfbJgvpcVKJ0jtJI>P z;_$gKt!!*MOxrB|I-jZ*K%2fR1v%0T7n#WAhVw%MaQ12EZ07r=@PGh66^q9f@vT_? zF(62o`tsN;81vJb0kH)bbR6Dw-g*X$@phY$`{6PVKn4=vg@aE7Nh=Y{O7uC@oeW=n z3D&I2F~EdXpriQ{_9J*e;nN36;(`6(x3O_Nq6ZjJIiA7&a&nZsA|W(nx?7fl*H~-z z9zdCX0QM4nz#6a^!24HC>^RDc5B-(BTa9IYD9_+%U;MftFR+e+-hmt`)GL2_j%xYXilc4d`4W%~0Ti|CkGm<|(ZL^&+R7(-NL7!1@n(&HuKQJ8vLS{q5 zBS1l58Aqf`{6kVLH{y=jw41GiTLq;av@6~rsW`%TfAST@K#-5`fHdxe*NaShXxq?$ z;B|xi6I$et8&KS@IT2PY%~z-|dMoVB8P4S6#{wJnEA+$PC|*3DEi=OXs0PtP^dJZX zm^hJ~J8k7PC~TAT8L^Gm(1VM5C{A$KlOLPylv0I79BWi(2Nt zKQd`Pp3@GaKeLx1+eygFsP`NDe}6wO<6;sG%OV$g*qIRVNOl_85N=fP)Ssn9a7L}%c{Ogx z)?#>M!e(?6dOS}w;1HM|-tcnE5$^L?Sd2_ej{zXwSX=uE^@a)r{y#CkG$!%BV_M6) z+w&vhJ3BiYF-vqNdp<*|pdawv5R5W|r8Snvbp zg&9m2r0GONL6HZs&lCh-nlPa2GW8t1RH{7%sm@1PZy5&Ff~u>lAz>{6)i-jpakbBD zfqW`gngAKjrDN=$7Q zBQ-KH?iI&RCIEjl1Ljh=bbnz-y%pV4%%pF&s!ZD!h{ul+(D0JhJpu`UcWe=_XZ`G1 zx+Zwd=Gnia#Lv=7%cX`h1Ke93$keW@#qCexkdd6y25G*1|K%MGU@V9PyFm_=lEyE{ z42+G;d1cNvdsVjzDTJ8|2_&^70M6SM1132H1xVTj`hwtOU`}ZP5okIRJdM%RPg2n| zPhilHmV+|1O2X)s8X5507BzB?4|frOUK1)5;6yqsY-PCSV4aS~)u&u$y+7YaaUwLE zW2ia~z@D7TVldw#LBxJ#loxo?L3W3`!tmr&F9svXeB>!B{thz~Jh|*OPkWbs8>_Y`I0Jj=z+&43 zVb6ve#qqvq+_*A<6q!Rqyn2Uru+YMowRS#qJP9i{(M%C2>H{Yr?L5b=xvn!qHGpuw zxkv~^Kqg2az++?)>S9Ksz{{v0PAU}?>TBF0sE)jSd=|hkuOFa6F6;68P#gAg)B>f8 z&uJsMkMBRyS_(9c{n1w&8XFm)bq2hMbjTeXr?->ku0cuzCsy3E1R$XPzWP0S`WB9x z_g~5fBm6N0?Qst|z6+8@WCg>cDy&L{{6jOxkq*+vZRoNg%O2t(6nimSEp!QM#u&H! zkgfFUV@mE*zQ{9(Vwu|Ih2$}{|iC+nswcJuw4n!H?Ly=^pa7!-Xc)=-=dXftfprQIx+&=bXrtL*cgybH?_nsy;#&n`TVS!fD44MH=&gIupX9wnC|$!PE^6U;o*j8$ zke3dbY4|#1U`S88O5-0+#z0+}QghIv0)cY$!#ySn%L?E6?21A~B*KSUp1oL#I zmOkrkR!FJ243!&KgNM||6~r9C9-MMZo^^fa5PbeinD5T%rfeYtM3Q2MF-?XC3>(g1 z<%NCWg1oRWDgfiwS$C@cMNsmes4u4<^2}`U$j0=KcUaU?si6LHR4-v@NKgz6)|s0{z49 zhIdH-9Lxp=1FTR`bhK>=A1l^I0Le59XLDz1EJ>AF%d~S`z`Jk)kpS5_54+hsfBv(s zojiP%Sg-s`#Z=~K)zS-xwwm5m8c>p4^&$@6`S#PaR-PItW2jFOgaxD2(5JcGL;a&| zq>aOUulYAln;~~jzL)y&vMy9-U`*cwFiDBQw7+&(%z#^mmC!%hAo(}!OqJ9KY3-~h zWwnO5RdD_f0-&wGToAdxjV`Y9TEWY7-QOVLcVcJfkR2!mQyCK1BQu9!LHr(fuiU!CrJQy zEYY37%j6&o`TSMm6+j_3c6hhZumNRka>n%)K9WYr9S30U$h1j4FS3i3(=RE6L&hCC zL4gv+ug@=*BtBI?H1OfKFKf46LDp_s*N8iuZ{vj!=erUgMdh+e-sXRRY$jxaw(4XFE9vwnO2}s+ z6mLa9F+qD&I7$IRs{Qxtw}UW*Z#U+@C3n5kM!f`dms2Ig)d^8}3`|*~Ow0En_sF_! zU-9k9PpF;K-;z5^BrxrZ0&1fINxS`o+inY>lThYG3qGv?p!X3vKY0o4G)eVkG*w}D^_~>^2+S!38J&Yt6L%Wtqn%%ktEzE(lcL>-E*l7%e zHc9Ffjl$pjRRB41;kPY!%+OwxKAvH6a{}L{9yF&GEtn7H;koO;b40QxqPuhw!BYzh znJNi6iV&Fr>jh-vBVVAmN@{wfoo&?I-WljEQ9xKda6p6bzlqHV|TsPd;r zN_x>fmos|GEXPteXFC`5&<7C*JIh&+gDe0B0!n20|EL(MNI}l}Zu2Ot?GTOc0u@m7 z5)W_NvQZgp#zUyerg&Q9;O70`zYm?D7J|jY+_TNe$hcgLbdUfB7_wrum%x33^8D_n zPv;>NWlC@=(-Kzo-`d$Om;BzXYy43aoPSc$X?nEJWP`?Uo}|Jxz{jR zdx6@atY{nTgF~KpO1@B2183W^(Aoc_11cQVbvUGy96wu)@Gb-@c7v#at=bCKf^7rA z8X6frn3qY?ug80c04zH+&hc_SIOQB0>dPf|rlP;Q5YuGye^AKH-q*tp>f!sl1rxb* z+*d2hceJ-@-x7&z9tZh5;aAkvOZ(;7R9W_|!xKqg_CygRJ6QB|^Ok(C|HxJhDSd;f z%dkc_O57I$9b9%6Bs-hG4=mQudSd(|rD{gFV#5W!c#1z>|j zkdJRgj|y|I{}mlk8{EvtK;426bfTY(jf9(j)0qA@k^J8>2+y1%Mn8TsXvek@DOnDe zey%osF&yuJbsX(3<3d>X%*7y*nbsg!xmwH~9Lxa1g5O(i-M}qF%aH}~o1pI@G`cYx zUNF1TthBQHT~fC0P)UL-He7kNn0Ix%du*ljgS!k)QnjCcu>gB^J*`1T+~6~|)@OG$ z`Ccxd#i-PDW*z~wTe>Nvvz>t;UJa$yypcn?F`K%qW6M^Ha|4cLdTQC~3Ov^1)IMD^ zRRgBG1}ShI4hgXyp}0ZA(3WR~cF#gus6$#rt(iZ4I0i2*Ex}LQ%ROat!~E?cZH631 zdkMFUpIUafRNIwR=78;eiTm=eU$=mysR9>cc zCbH~(UAKAHq2IM!j9If(9xxWB9@x`T@~ico3wGaW4kY&5wc}2hf(#mVqn)|wJ zqdIP!b*KN6h}YKRSreunZp+c+h6JX5#HKQhcn5kNS`FV~hiyhmEwL|OR#jJ5Pd)bH zW{u^JpuTPr!88j(>{(tPm=p*P75G2DtF_;cR4Y>(AX0rswQOZD?o|0~a2_ce`Ji(H zzR0viKZYoJ6>DYkIa@jm(|}XNKuarz+$so}5QZd})(dR?e7H60>RPwf&NE@S|7y8> zHok4ZA^zF!n^Nl$mR;HvsXcy?V5ARczrGZ_`HE?KggQjh!J*hu(sZ-eux+Si$ZQxq zUXr2Fz{}6e2sG{83fxi`)3p_s)l8)(*@gb<7$}gk!B}w~u-v}R7>^Ez>K_Lno@d-1 zHqV;}gD@qinLtU*1!Y~1dclL>V%upcMC1f0OK>u{_2__CH?nWe4Mc4%Mt|Vyo759f z{#NrEFc2e%8i?L{+-!FN{6zMLJKp#a-W{?oRH1~9-+dVuN6q-BgW%V1$Pr}+U4$&? zHSxM%(5tY^fFVkSv+*ku7dv5ekrZJNU9Vn%+QyN>`nHZE_oi?eQLwyFVc|H zqJT~Sa_q=mTXu_G#gD$oghq-!Xo&ZvSyNb!<}1L`1k%`#(v5CiJ#&-7B3CwRIIj;W zw;?@guB&?ps0Pd)A_1O2G8br54zZwN+@}_?LV%QASXQR?`0@Kp)vQcViK=9(O9JAJ zP`-If%UWLYdv=Y}q_r!h$drN(GqsuX<3dkbATUHeR^qY2L^iA(serFx2GGI=*<_aW z$!0*~-uo`7)u-m>B4^#JVNBcBbKn%k=znmUL^v5qG8kNOvcQmY9?k^~q6RlwNuViw z1b>`kKAl6)TGlpPhdAIuwU7IR-GslA&_RF!7Ty+(9NmO*~6 z&&;wUL{sF7+IhNCe^vY(O_A(UhGs?VJEBHYyI2tyUQ?g@NnI5mC{7+7`yzPFJ)zTS z)=iAy(!20$bHgQBV*4z5b<_kG>;H5I>PO!-fDlQvB2?+lHoG0NnYr{~GyrC$#dwv* za+ONJHguGpv;@=Smy~#mQvO!&52+$Ggc4AeEbcjU+?I|^=}5S&-hl7tBvjgF{Yxf0 zY<`cLJ34$yO1P30>EUxg2Id9*=?4s9w(U9J{K?43a=JJUcUPMr1e+gex*cve!2ZDQ z)M|L-FvIhHZ}-C0h^tv+K^M=Rx$p+fXxura#dM8xf-iP+18j3__PdQh?ny-8caE2r z_vRw*tyLe3jnI}svJz5on15I_)BcOQMdtW{O7?(}xKwNZB9!i*U^mR%=A);-2*8ix z*y>Ob9RROi#)XP>%S^|B)~c|(psuAc*}tsC<(>n^?B5QltZ{DMyK?*6ZS_j495IT< zgdBaNeW;^F6(?CO??L+Pz3s)DR~sAQ2pAJMCR1)T$+yeWWE*57LO5Hxj}rI7w{NXt zj`?B;2a*e1xpvJ5PV`6;)p&j8(JNz@2vIp6V`y2kadZ19GpOW@%7K4izRSB}or1SG zHxjqwFUmwlMTzu_`b=~j`J}c-f1lwZotl}>>@1tx-w&6d4w6dwQTAb`>ancsWrM(` z@9pilz?XQqBQ!+f=J{KFV6}?p9JuC2-qWZ2?&xiP*F$BoM3hp&vMpD)+8-pASKtY# z+moM_*O?xYbo2; zyy~Vh;4-ae(XCQ9J$}-$=%KX(bd5Tb6ug?l6aIin-9B~j*Y(WE$jJ1uVOn$;Cl#e~ z0HIj1Ssyl>JkTV%=|{4v!%%B0ouYX4`t>Yq4R?1zU?0^B;E>#5VQ>eXbD6cC>-MzT z+SZn+jZMKYMxtcRC2Z_SYqE}gXn3q`zDii1Y39*4HT7HaDEzFc8sz3V>@$L3EHcrl-m*ttZ z)`o`XydS&*j)X8n7$qxf;M4L~{cLg1>Y-`;J~5Hbpq#Wt!;T%Wvr-+Ai-EM;OIhno&ZU(SZ4J?>v9 z2Qu^T<>dfaeySBST`esa!EwIM7e7VM>M8}r#3vzNA?xZD(DF34AqipDfH{C_-X40P zar}-3V8b?8>Bw5)xY`k0`ziMNwQI=XxJap`lcYfkO#lfA3D2-FqNc(Rf%ab3^&6X; zHVaeq;P8k``SF4^j?>x7?~eL#66_U!Gl!bukPt#32k9FbozGDAhs-_^T65aw=3flV zDC)62f7S5}iwZ!&+BIDOP`9C9Qu;rR{OmcYXt z>|4W*MV49VYpmaZ$o$hocx)*M9FFRs@!D1JR9qrO@xwIc{eC@XDu=CR1-r$5TyAb| zxH1JCQGg8```5^s7lUj1T!0!?aYpEJX!MlrNHL&WpU>z9*)UznCFL`>v z-}C$Uqhm=el`6DD01QCP4DX-(`gWF>l(Y^WA0(-wP#U375OgDXU(klgH#o-y1$jb} ztlvJ&1uKKd3cB@BmVKAj%WLx}G9C&%P<5;*jZm8}Ed-XoN-o6@r{if?m`H==FX*nZ zYHFKCS4`k%wSp_u^VtqEIIDy7%q!2QLtmRJWO)qW@ItXmA=jE&o7I3aw5kgmp6X#q zzse0b-+0`0QzoDWJG?b#P~}Ki7xG*im~;#j9^-fmeQ&raz9vMhM#t*Ou?o(r9Dh)E z>O1~G+*ENAX#Y@ErATGxRe350C+F=jDbOP%F$|c$5=MD=c);;EoNFZ6116nuk2=W( z*dd>}dI$T{9r<72&LCYKkMmXdy=sg$t^?NH%9Km0U5^^*L_>>L;G?Qkr=#R$qBqBxuiSlbj~*x z?MLVDf%)!r_{xp2k>We@eOTmrA^U@$l)XEUi1dyCE+soVK9D$P8{hNN&BRIM^)KqV zAD=&afcKTP_T>9^H1g8`;`srX1<>&tjbxv#uOghEc6Nia7;*=G@UpL3)Jq7dSr~Nq zInn)bmigx=Yc;cn+N6ukfYSUb>K0F%NP=^N2>ZU;b`*@~Z(7ZL(Jk`9HzzLexyV9& zl5&@wT|_DBoZMBXFykPtR&&>n)!c1Y@IU^xwR(zKj7Tjam8Y*AIDPFDeaO1=?f?b~_OaB|)Hyh11O#mQt55Qx zcehvas7_p6tE&Pt<0}RA^0);LmpHTaYRTjH95Wqi)+Za!q9`53Yw~xwA)~p*L1`v0 zLu?Me@IQ`o8DNl}{UunZV~=j7P?J|yeglz!l9m<|79(=%7T-O4d6_&pU_|6TudUIO z(}Kg?=HO(Yb7#K2m__X5MJe%7zcjnK>{hz>y{m%sGiuT@$X6uJvl!?R6rh5Hd za5Rd_`m@n5>>wxCx3&(1ME~p44_KvEW0}!uNlH!F=m`iHI8IM5#Pahc!RbzrN+6%^ zgGc(o-R)36tdrr6bZDj#q;91apjapr?v{4z1Kh^EqpIeK;W;_zyS_(Fbo_#&hv3x` z)iKy8_|3n!GDD?i9Mp<1Ot|1BLB&T)(lQXT4t5TzkvuEPHz_Hpo=*od5^pqK*~{Ki zkkKoChChz}6T(c=mUOj`+eR)i;092+dll(aH=} zhuZXg)UUE(yzubw%qH+NFR(i1gS{A&mX=n1zKB4GVo!t1&16tETcZdwZ>S2BkdV-I z>CmPmPw%8)>|^C`c&n08cRPC2v8j8`texd&%wWjivb`S~2{A!^^ca^WXL#C!bwPe*nv z_n?u+{^TpF*m6WQX|Hd}`c9=2^M=7~DoU>gl15nR%39mD0g?g8&g%j?^sH=I zX=(0rAxMz`XE=kp6D9?8S>Zdb!%tJkWV`|b0{T}|pbwsT2q6~EjmcsQ^!9$_U7ZZ6 zw&EE2ASMt0D)AeD8X}9?UL_<@VB(QyZ=0*o-DkQ9ecQ}K`0WDTr6!0QC=}EO^+2Cf zkeBx)6I%Xx&z)%AQDQWW!hlaRM;TKinb%nZm*2Q*)uvP{%9tz5e zN6>@Iic33kGdDMXb3*J7X`2dfAf!dWkGlv)q*AL7Z(UJ#UfF2V)YSX~{al}Fw|Sl? zK=&XFw{0_<(AsZ;My0RiNSR26@}I%3r|Ns(9%z4g`MEXn*`X#XC@jnyh+n^ejuMGp zIZC^D?cK+ysEaRNys#LnAVP2Nv0lYK&+S%v@s-Fcl7>85kB3fR&A+BOmBK?&TFvCtC|cT{pWMF%N2j` zx7Id7ceG&Ch4AQ`e21IJRX8%+;0YaT#fA;_wgK`? zC^K^+cK8jjy&r6QjFgsM@qhEOVT;IaaU6G~cKmWoDL2uK(>NOAN#dgDRif+qADV8$ zksOKb=9c-u1nF}{?{|6xMMPln@bKKZiv1&sU!2QwCOax}t^eG8fP}uQorKFIu)n~B zLpIr+d!M1cLfHxj$-Xz6klSyZ3Xyh=!lNCk6MfKUgm*Db0M};9&d{)&B-{ zN^XPGwBY|DFZiz@{;x}%CJqwjW0elGwa#0t_zF5#{=;xkF$?5~$z`^4h>9A1xk+~O zqaAaGZ1~64=-;mruK+fHLb>JX!!??~m9ac{PNCW?&SJP#*0*7!*p59b z99Nv*$PnZA5yhF+%QnUJzZ?g*)qIJu{EI-?OL%?hX*%HV$){+knd%Y;V`b+p3X65xALE<_7X8P`{w9coQE!E8Qn7W8tBI zdE`nz8@Ilw;qw6Xs5h4`UcvUha^W12)iebmws(G6k;lpl?c+&?Ywr11K#(&xmc|2w zPA|N1GztXU&n7+#6zR$ymA~|Tli2--z=M2DZ`KGmNg=*mmxjn{4R*P+>U71%E7b0t@xlFf9=o>D~)3WWH4 z*__e5kDgN1maz}r=DIuPI5(6;XQ5m4E&XFsQ0Ugy_ewgs_$R=;nj6jFg)to><#_rN@0Z!onLM_(^ zM>xI|wDyRs@Qt^326@6=Qv#?DRd&Hmmu*?72UvPEG<$GZQdgD7pD}zC;6h%fG_xo zVnCBFXAPKQb7G)6WmOUJ~927CLt;L4S*{fI3MT;A{Wrk}0EX!voOU=Ky>^ZEajYj0nzFFxKn9fGYC;6876r%#RRpuv zfMI_YRRaxHII-iltJ=MeBL~b!-jDX`(S&`QEb95F_*Iubnj2h8ABF=qFEVM0_Od-2 zeiOF4q?TCZGw*;qsWwQc;PtFNe&9`pc|B~PPmGOKf3tIPavB;MD!=^E*@?%@td;2| zxA_0H_9oz1wrkt)jY@+t%1kJU$`l$Dk5DN^nPpDt7K#j!p}~|Xm55A{d8{NPGZm$b zWlS>9^US|r^{n--^}g%<{%!mIzo)H7w%j+@bzbLr9Q&~k(y7N%G}FGsonS)JH4AKR zG*x{4{O;nk1m=U^OPY(k;^Jo|U1mqxw5P{~UcI{F^Z4}4kt6(j`S_H3Jg_z;@I-|#>{^$ zbw0ok$rdH`k|qFD%7AtV$v|Rp_|+AiEF&E~J&UQx-Me?IuZDcM;5gM6G~nvwbP%Xy zY@W5L>77@vUTv&{&tO`fp{s$)W3AZ*xc(eEzccSyfvL0hhqNC?qwdw!r~Fsa$1<-X zf-ZR^KQE6@X7_nJH;BIofeTh{<=&})V(`ASJ-nsE*H-z%5jMB&00|Ij>vQ-Y~0 zmJa>3YeB|O9@9oN2tb6h{n^R+85>TXJlP88HM>I8cR$^KjdHRhA|o%}sCa2D;#U)~9!F``kAk%gdoPj6_Xe9Xn);T3 z&TkdgOqJ8sWk)1%rcJprLPB4cOyQBZ9~61ZZ;B>$LPsOEyJ450pd3IdK=e#;adAfD z&&t{Nu&}ZD5F@7aor3u)q5CP&RVl%(9N<8w)CbzSjwb=AQur_1_=9k|jdVicFLm_c zBvF94yLDf!2aofZu`w^4fy6F-ewyH6H*y{oQEXyj;#BD^)!!>GHgP3GX@~yFBcgAN($>*yf4=hPa53S85kJcB~zNBW6vE+oze3L*$2VS8u_hDYbV9X zzY>)@_G?6dPr-xMp7qF;{ph#ooT;51-;L@R=b>PPo@@=&h*N#vsW$s4bagjuSigJu z5W?7uOZ1q&Bm`cRz0S3D>pCszCgs2omr3=a3es-<*UWfSOtM&^U>=*CrXp2`9-u1q zq${mvlQ3@jOov+?c+IpjDbf`Ad&J&5G(6NYoz5#~YqYffRcqMS6?|l2?5EF4 ze;ahf<>Z-TI4GsxY|8F+ z#I+4~U2{Oo|AGuMd;b7HKj8ffVSq8ITr^`)}qAMcOE7fMdU`F^q%tdm7br&#WVZe=+b zb}Vhj|8LI5AJ_e%5i6f{%n%EH1+7}x+K-@caIA?Eo7*!4>}%~b7wXo zLN+NZ>x9+A>keI)qsOfw$|sd{act ziv3ota~qACa(S3Nhkigd&7@K+7j8HB$%o}VF&fS8ouEFU>jNJN|=7J4Gj&;Dl4f-25!rCspoSyL%Fh!XovxHVm!?C z2Jd*M#j{ar1>&BxnMu#QC6b(p2_I(FfiMAhOn@Z*p$9QH6>eL!A5bqyi+g$TGcawc zts*+9PStLAWTAi^Z+M+izc=I4Wv<~Xme;15qC_vY?|knlDCa7$?X4~Kj-;HJH*T9y zRU7kMqTM5FH(~xR4@6GICVN`w%f-AqV7l}((!JfkvcXV_tYHZw!{`0Hv zT+S$m8vY9 zbdRa2F|NG5MzuIoxo!M3-Ga(=>GOQ$*5kQ>`O+ENMrUsPTIIgyN|i?(o{meu#Nzx1 zdh6*dS9>PBUY0+r9VBhv>@(ox4!&={20zlOSIE7PBql%fm5<6EDmBy2x~b^F2XdZY zgN0_({uQJE5c#}D78aX)6evL-TeEg;G31B6UDlRnv^aknl+HCOXy!3m@|C_@Ui?z~ zYzJ!O#wzC|vK}(-y-Rj_kVDLfH7rX02DFU0Y-oMucW+JM$~hLm`4gsCca9KAu zI6|4C&LX)(Yl5AU5~DYgJFGN+f`W3^r4L?G?g%4?a%akEs8NG9QhN^;QIkSIud zka1!%@#_PzX_-Z9ps)rQr=NgQlhSS1!mD^s*-Hv;?VXn94-20m26{YzhUjgUcYU~#n33c6VW%n8Lsrkv zCXw^hg%m*=8CXFZM5JX^N6GR435D3J0-sT@d-&kNmlQ3Lw46^YR$+fUFT$fmD}cYdUfS`|!;L|4jN1dnznJwEV*a~9!d7CvqI}xkXM348 zbKd5Vw*K*L+*DO4*wMP!IeR61E#+VRYa^c3+TN=Q@D7$V$#YdmebaYYM&I*|dG>kz zayRrWV=M1DU>~J${OWq77^i3~Vbp9yCMOFW!SZmN8;j9Q(0}`WuIZOk$%41r4NSg{ znwtyCyU{e5FaP}{Xn%hbe0)2*e+xu}MOaq|AJ{-5KEQlEdH&Ar+r*6~rUaxfkez|M zE0Wy*XY{VsaLT3Kvr+NS>L+t!Z*f~af_%z5kUXTl{zN$r7-8Lk-W4!?c zj12Jd$FE;&z#P8P@|spYhT%_F6|P*vyV-{*nt@IhV{Q*+WW&37jD!SEhkB3ccS<^C z49v_ufBu|zzVYyNE-`AwPs2e|O)lqUbD4JS%qgxob-PlQP@kmz;=%^^+H+m#LZ3J5SzozA;~`Ih zcWV^g^V85kV=}QSFer$TFkuJ{5C5}(Q%*Z-?*{^*F$*7jM((Wv(8*Ddlma+?AftO0 z9Zfpq+N{=Xdvp0y6B484A%8ff8(M_+8MABG5sP)y)Ui<4tY6uuZFkE~e@JC%vLZ_L zxZcle4;$Y+3G*uJGg`me?A7VQWn^kfm37>&_3e39Hnz?N&5V-jRvNynL`~<-RrfBz z$U}YB^2(KgA?rr1Zfa}vdixAUfW%L4UM)YlH5QZc{Vqz2hItNGugl5HQxY>%lHARk zGH5th-DrPCg@^mjkA6OO^?2q$7-dh4)#Mk|EYJR9w4i)fVz1rC!`o!u8WtADw{M@i z-74XjcmE-H+Gy=JfsS9v&xp2;Z)G5{>I_&+(dt6eCM_S> z8^_3@pZA&Q=;-Xf_H#`D_u+eZPq&42&EcQCAv zc0BLx_ts=-GLzxsv03A~`0Jx;gPLhyOJ1$zmUZ&m>2b!$($XIyXNzU3@a_-YHt2&l zL3Nm1AYeBw%^}ZkkEH4A1N9p^Txa*Z7`@cxIu2M&K%+^lLl&&1u=|vTwL#Zc^9$yPc?&?zqt|uz2Rtl;dO_ z9J1akA|cEKe|PXQ1b6L9YA{}P;(1%H7;FW0g9qMQm03`k`r0i@oW*6|Z7-!fH9Xf) zUby!UkAd&-VJ=WwvF@+$l%jP$(&e9nD5~-Zo+OZw^NLVP|kKMsg=_ z?3r?kxpryEj(?^4Xj^yj*J4J17Xugm6`L@^DCr+BbgRbS%Rr%vf z`T7fo@0TP+aJC(4<`ov+L>yfoGN>gL8n^;hx|<9|qgP2w6N4f$)A!Kgg;Eo9mH>T| zr6*o2p8lba_vH%e2r@r|7ZZc`UX#_Yw)a2AiMo_CD=8@vH}>JC@Fe>?ETjM~*$P_0 zt{NFp`akV*8Cv=)WmC5VSh%R%aR~2sxO!K8x4gZ*6j1w2(H!^V+iT|zB)U?Ih^_pi z_uY)I&t$A)3R}^?4Jv!DBQ2ePC7vbYeKw1n z#a{_D!-T&AkeEUlM!IE6)tPA%I_29K7(qQD@C;|Qg{k3EUwTd~QmRSGi zI{^k{5clBc=5@Ecy$LppxBoX^_4BQwGXn31EH+&KQ1vGL*0@_PZrXKWw2>8ARqPeG z=$`7O-@Gp;#l>|FsqB-GzJm*?y6rAFVH5EwBdzc5KuF!Va03mWC$;F@Q9tD+K~dgtL9$P>y~-IT(qKVU!^@8b9{Y}>eQ5B34}mFI{7nN=y9$F5AHlVdEV+B^aaViij|;=Vg9Grx~QW zCMfaxm@E6#E=+TI8P<#5n7FVToY9^1mMmMe4m%o(xvaH%*t+fEhfIi&_gS}d&b}ON znjdUBwuDs8)G$}f)Y{q|kL)A>fb}P7Y=-xcVJ*;xi1UH`K3W2dgbRYny;PJ;Qkm$?^1L04!I1Bb*;SF;rb*ey%YL7 z^GslJ!U`X&fvlrqUSohyK=F;YzhQZL(EO5xZ*1YpU9rg{e^nf%n2@08j5s8CqJXx1 zSr2_C4Am%L-cV3x*I2D{_3G84pF4at0_UyAj=@atUGq$EbAg~)`N`dzO+kp;k9b!) z&i$mI<7-fjd~0qt`S@s;Qx?nf=ZYtZiMh%J6Qpv&NQH zKp!ATmI6%yo|aHEoG!G2UbV*ZGgKFP)}!UhR@1GQ2VV1XQUgBOFPS=|t{imyZj|f7 z1_+SQVo;El2;&)h&V=}QN)nD-f@r5)hi}UldN}m0lqAhm?OV`2y~)kZ^7K*k_VX^Y zzT{d8N0V)veMEuI{l3&@^MZuCA+qKZZ4j;M0TUI}bt)P?C!=0R*y)s#!@MxsHyGZ3 zLyD!F?JPG<9;2@;3w==L)L7sA++^R1lS6iu`O5^O?_O1zCiC|;ay&xRd8)xn-Pmr) z!)bu(*Sod9x#hBGdf#y;^>X||XRZDYtGL$6ws5Wc4vR7qZuE+U6m8?6LCUyJu+}O~ z+mb88vMqjym#sy&Bwqc(4c5}d5QkpuMe(L)Hi=*#{@O`FJTKlVXiWj3*U0wcyx7NDGapLA9UksPPf%4H{=USOs_Pr zPYg1UGy&NOk?sC^IrAhD%wme#HLa^h03=h*HrIeKT zb$|K*RX27S1{M}-35l1r7r%D!;N1ym&?5A>A4;f_s;W)E{5-UGYjTW+RoKF|Z4PCq zMsBXtv=Cm)2p!UF%jdjQW64i<1iIq#)>Yk~F#6&TqBk*m1#OxUOIFAPwnZhF{D{I~ zog@eNhG2b-zFSQ%X)k+N7hBTY{_R4=p(&d;pKd?k3j zUu!AhgwW9ELblO`wF4bdD4djjL8_bS8#uV`$=c1^BUFlH-6G{(mBhK*3;bq!RIqa# zLNX8K=Bzl)!qKxRxN$6b-@A8Pf$vr&XJCReo$l*0cu9FH8u&6~@7!pxsergVn>KCA zzN01St|;$aZUQ{wKj9_Rk9gi%>gG_8IFHp^{(l8ow#1)_6lcqiwKr}3cFnx; zEnBNV?&W=s9(*wa=bf^&{chi;3=IoQmJ0X>nBo;asCVsaonzxKr{syM292d`o!;bo zZr?p$UZH=qSK6ezt53YF`*}s-TQ~#q?%T&4BkM_KmL*O>iKpzEuW5sopQ9-CLRm}> z*L`^He1iY-W2LA#b5;aKXWs7tD2f)%4ua&rp0W{!Z{%zZSm!k@@o1fv(*43pXVNh>8R=7?pw6d;_iD~C4 z1&|#5BVR5VJbFQK<+nb80<|~j>A$+WV=&%5=(aujU6OC16!9#GEN)YK?V#F`&giT& zE;9MALJnEZf2{wBV<*wsgHIZxDrkgPB)$Lislky`OY#gx7+ZI1lK?{D_1_|M3nS&U zhx{g#sYA(f!*PR;ly> zI*Ej{OaxzI>cR%mms5xr=BplF9;NX6vFm7P*n;z$xbP$+jNY~L?f1Y_HM1*$OXEmo zQ^!sJ=%Q_F)~qRt^Vk663sJ|hAHDB-aJYU=aNv1*ND;V)3>er0^fj&wJOm8*7kgnq zcnSyZ8G&a)9Rj|oDG*Q`v{Nwt>Z?y;dbd2HccG)EVtHtB%gU$EpDTbXTm#ONHuEmg z*Uhr=z)|xu1N$W$4MdVUOretZ?oX%veU)q2HQU`*CV2>-Q@iWE->>Zk##H8Vt>PbC z0ClqKqfxk$SD?T%zH-G6VaPyg4TiIK(%|+XM;uP;~O}jb+fxJV0JM!GvirMa2RUCTX--sN&F+|j z_9o2;9k1;_NlUuV92dDOXb#@x7;l5&0hkpux2 zxj+tSx+B6a_W|4x9X^bb+jA;nnWVe& z7*dh`y7*W*HIj$CTBrTW(hXE~>bgq5VFw)mNYE}N7o5&>B|l3qb3@uY@wh^@WVq9f zqmlx^ z>11i?TB_K#ixqqptb-CU9zz-PI{LLbZqI(q=y29ND6#wLLP#P7Qx}hD3k91jdroT? z8W#*?VZ~9%1Z1Rww8v4#e`Bia9)`{-y6Xy7g z&=OGUakK1fj3pYZ#wd%jx+>pLm;pQJx%H>>2J7OMf9R1fzn+c+NzqD<6MCQkBo9mvucnM&2Pb{y1#Wn z8BLP-5+h7j(BQY6#>b*Ew>SB??NT{)>WjKSF_WUfb2h-jAiOv9oygWviUQ=KpCniy z2!ouX3K!PMDl!BaT>Eb-p@tl5f^osf*bj(;5nK_Q7zq%A3Cu^s1HhMKkoX_82I`-! zM2GW-_Tp&?naRIGGPMwc8(cZ{hu#t=$$Jz`7ySCC;^%)=8HIJ`2#^WTDr8{+CalJr z0r3z3lfQPUgws@C$V49IVn3SbnErfI99f=8Z{kA#`kR1b!au_o%p4;6uVB&7pPpIL z<7S2@qfZ)C*wOl}R{6_LM(Yt*J3zltl(=wl>=jC{ zdM!(vCQDnoj+a4(>ka04@-eECp;aS;FAxeuPWYQjbE!iXFZR}3X6X+=BqcpUEaFzr zzZoxLTp}jA=lgC7?WG&<6*(#>)HbO4?55M;k1urAGz+q*(%LxRu5!f52Ql!)^z|3Y z^D6KKwbQkO;!=tk1n@|rwrlez)^xoow`=qK!(_JbUs66Cj>KZKI?CqOC`v5Rk=h>l z-j{_DFZ1`h&B5^*xzv#{ z*>q`X`p`;4cMST1xcPfp2`eC(|-0GF`BXZrBh1t+H-@4JS=gxLF<7lI6QMM zNxcL^7?}4b+BgFyY9Z%P4a7^Au?uVtSu8xRZh6zIkGKv4 zY*fp|h;r`g$7bk>Ph-Sz4OZC&RsRkM>7M}Cxh5ihWd7BGwAhHWJpn5VOcm9OYuM=5 zzOPwx*I^`j`?M7EtTpe>oz!SRATgTW+@vFMskpe9!Z&2M=HVWY@?btVM{dy~&pvx( zDFe9;h6)~V6eNc1cWa>P4$@*j})Q0kF~*Feg}7odVn}D{pGt_-}L_&c);jhAmsBTYIj+ zQh7)dpu`JLMJ0uwITD4&*sAgVp&0XIx6}Zr;r=&tNYsr?79tS>aj<2QU%k!ir_J&V zj`%3F*=G#a6&f5JVLh=ot942TtT+Xp3XCbDOMwaN1Z5i@)Mc2^x7n&#=F8#Nn= zm^fNp9VVUrZC3xrkeOf9p6L`%)qeL(o^cZ$T?HC(wpA8})#iZtt=*@i!bM`I%}heT+*$%!=Aj^~+~3@Api+}47D1oh0BSx!*P|06kvOr1d{`Q8QN z@c8M|709!uOH$wHqi`Gdhz}_MJ2U77WgS~ySy7Sp!_zst>TeSVJN8u+mc^|loQRn& z!t=w(!ST2Kg>cs@u7EeroC}eOWFl^(6<+6n|f-Z0>N77zs}8mg@5MP2PT35 zpY34rp5rJX>N}gn4ANRr{$zW3C%+d^bV4rhZtkbAa!nTK5WqkQAoBgkkMfutD=7}4 zgIrHbtJoJ9!|oDV`~M%6=0Bo5uUQngt4CDr+3E}N90@IJL-fnc%px$o2d|5$>+jm@ z#9kBjbf`(1 zj?Nic3%@YdzjU76-K?Buio)rZ^}FSDkaLnqU{$d$WfX7e8K0UeE8D=t_)hqIZ13(u zmgiSUCMzqiDm-%iIm15H=4dWMzpFD36~Xi3Dt|CZh0Npf{U5Ej%Io$zk#_iE1}39?)Y2 z!xP>J#M8dkTnTx3d2QlKU*Y#2Jf86!* zFoiKZ$sI5^p^^azS`-jvuRhOd=r$>NoAM4A^`wVe1whastV*L6!|;7U26=Oh*P4{i z77dA|nlJya0Uc=@DXHL1F8^g2qQMf}^yp43)#lBc#~|9HB;l~{M9TIgkKCc_ebM7K zp!=kS%!wGhV0CetE$)|SUadXcmEp3K%{93&qSzA?15787{URjSU~!Jp+LBK&kngP1x3|!yd@_Eb1if#JGei|q&V+BlgFzY zm#LX&uH<;4uECu*L;H?k^=RO0onxw2;-6U}18sZuS36t`z2PPsRZ}(Vus2md1&XGs zD^kLek{Z~e1RZ1pWhT6dJVKq_Tc0N+Sb!_>@re3((*>9p>cdB?5oC=to`qB!Z;c86 zb8vqaH_T7JE&M#saXN2zd2d6^vdFQ(=H>JV-NRp$o9<;K{c`d_$^~JZ%)Sa=4_M_5 zz>3^yuG1^+n2OcRO2w=OWG$dW4}9;e8CdoowHR6rNShhmz{Ql@WZri| z(e!g{mE!QtxlC@CDYdlK!s)!LE4M!z?zf&C1y$rarc+#)6et}t6iO-FKFH@hX>xLT z_}pr9_54D;cf+)+x%Yd~t_$BV|9X_U@2?ft6>{sFEbQIZFI@rLeK46HO>yd~4AjD# zO*im3XXH$am0Hc9+wRDE*U-iTqILu5$iLHB;8lDM$rqm}HjAfToHT-6If5B|kY>Px!!xq}s*Q zkJQnSj_8zEmJ5T{k6jtY#xJde1qHw0Y)%JmN4U-FzP|Yc50Sd6>T<>$Cy$f?VH*N4 zrvcf~(@6gSL`R3L^6)bN5!AoUi7sDR(Yg5Yd(ZE?Amf*up0HIu`N@&f*Zxoq`Tr>j*1}r#^l94Ck^7ii@i*Xsv?3W4fLjT za|h`;MO8bQN~SYwN|l`sp8y>4Zg_O*#~u2qkEvR}y8AUo)ce1dy{J7DNvw7KmfZfO zrk3rOSh5FNPlklg2`-LFlWSl3z*#F!KqWl!yUV9k9;@_^N#a3jK}7aS9xG;)MU@+l$jM98=G1*|Bs@gGO!~dsPe1vDw9*} zlu~XTi=k`G4cp!iOE2d`PAt9DA?yP2IknE|lz+eW=VDHPVfGktv^qLZK7B>V1rIx) zc=dj^9&C+(yy1MVgmnOq&P>=wMO5Os#wD%A7#*2HDP7@~DnB_dQ>3FR3-5x$R!{4%wVo=Xl zYV}jpTIJN4GZ|wO;D`@5Ypht`U4v8t_?SKz?$BVBx*6(8%cJ?cLnpjY-Qc^PD8NhR z&9z$jT`Wn7%S;QoV|QJTOnS^P=aH-XCW+vurxByBJVWq2Eo)*5o^yIO{eo}}I_u#! zCVuc>Ui&I_zx}bl#9xYU;bcyYDfJZEvq$BL&XepTCN>ER9cfv|mMsicJ=JHObBW91 zN5fy5D9fhg$@ZF2BJ9PBj~ymo!T0s&7CRMn!CK8YC}ru8bF#C0AeiF~&lER#EhUe8 z?z^#LSJx!X^hkpqThyhos51jKu12!=jW4j;veZW{%klU0B~Qd$06IV2_x0%Mz2BQ7 zSDv2_&l|W!&GW@&wk-kJRQ^Qq#d@m=<=T_3C*1wSHo|7Q59SL%ah_ls4<7w3HF261^KDy(g27%EHz)M z5DlfKWtrr;z$!=FXyHA3%&r%GVJ#(1@)*%j;jQpt=qkySHJZ_sg2cPdeV$&_lVZOn zza|J1cf43lS&dex{ESIT7G)omtG@Az?yC?jTB62R(KLCFKX7cD92)~d@QeZfrrJ%M z#?RPYsruU{Xd}bGM|g&#G#Tf~J%4{9KVGA|#o5V8NJJ!~wrWVDcW0y{oFJ_`j$B;N ztC;`|QChEFHu*Vq(mdV@uF>zMK)Z)pGyV?;4oo79Fh1Hv$FwiA>yy>1mC*va4HGw9VTLbRqy3r3S?+k;~pQHX~|fdLqlT zk~|yP))+NA+>$`3M_;9?H(8%=g2N_p4dxdYEs%vJl&5>4z^bB`jUB182vUnHDw0D` zPx1Z5JRu$vse#G7km;rAn^lW?L|R`xBE`-PUb9PRX}9`(Ezfx}x_D*hV_f6+qQCbB znilWi$Lu4IA|lct#}BWKHH4Pum?c8x< z=b4Cld|+v1J2}5}`{r<4juz(8_Z$z%&)=B;I45%ycq=mJf~ND%8H7CpPj?r5;}*qd z`*XMpHB979JZ7B-KU|TrK+TTm<50A?=@9U0BS;J)fG{ozy^K9hDj@Zo`;fgEc?-w0 zCQUVTb(0<1U5{a_Bh0*yEDxOve)LH2z=7{hg=#@+gc&9s*O54SDYs}UYI;^1L#^(< zhiXwx1*yFmun&{X$Z2;vsSmr*={I?Gme$g}``Zd`e7SF^vh5$2;(ccFPdz4!4;RFf zP+f#qwtq|uhQsP7GZ;XJU|ZTwq`wnYV^y8uyDr?O{g9kild7k5Ub{YHrtI?la+R{HqEp;*B9_sNVL`6l_9yw756WL!aV3@$#rX$?#*I zo(X?4SCKR@_^q>W^!Y4eZ~&ZI98MR?-?O+84=Zs3ychlA-Db1B!W_y(Gw~-^j$~dI ze*za?tI3f<7Cdkalub;B2tPWEkc8@?yuTCN63K=K;PmXKHl) zQ$AW%y`!9r`m=Ota2ObpC@Co)MMkFMxzQDA$+if=->*d0z|6iO>D~#IH-sAk+2s8juw2n9rxD)W4r~Vxb3rO+&$P0`~ulNPGiN? z)9ucqyAb*Th)_4*J`I~4Uw?@8s180xBOrY96gYayCr$+6XH!!0#M2oICqQ``Dapy1 z8O^h2AHe)*3oEO3$~BGe#F``hz8i9$#RhXP0(2twtFju}+KI-cUYAexv3CFZB{*xIu3V)o zDRAAdr}9(NI8&v@%{1-YW44kQtq!^zJ9cakx}`i#8x^z1tDFs;cRoKAVh!XjoK}Tf zAHt1z8p!80>|5^jaPEAUdgu3TX_Us#*R@5A3Rb*oOSc{O7`{Fa0lOO5lCO+9DUqf5 zO(qHxIrFK+uX)$HJb}CpZba# zo-C6t#PKjE+?J?4f{eG78i|}Jq&*G z-0Pr>x`=OUft~}`enqBzu*<|8AG4@6gWT8doP zv0vxcJR`g2orosX!lnz@*BbBKs4OO+9yBJLo9AIvqmvLU>S;30eL$Tlc$No(7`Cq?2UAZuoA7z=4hMMYC zhUxsOX{rJK#B1YE2$OE-uUWd5~|9#x*Kz2B_$ zqZ?6xth;X<@Mf=i5>ci-56hNYCS~qbwHTmwx1*4>DqENxJyUKK^!Raw(ONlgwLhP? zYeGT~=wC_AZtwukRHh0l-so<3OZS{CDRon#2ES5giY!AC8>w+ES%r8xcO^fiq4aAG z4qEUcprS~;#H$(ABbBST{9e?ZCYCWJ9yG5#1o8PyBvRbDB;8hS9-gj0*Eo#O2{l*O zchkKn=XN1woA6deGdrNc{}>xBSS&=nHZe2Ph$%fYJNw5AYu0DkSm|>J=f8bdGweJq z<9FY9CYgu$883w1V$T8^uc)Ya8H7I~fNW;8BLjAPF4$O!c+lL+Nx0StVOz$o=QNa6 zIW)6>_^UUuH}Ubw6QO0uk3`b@F=UdQ1^UTXMv_m}oi&W;ILWWMDpAcnOAXyS+(971Y?{EM|!!Z5%&}d)KG48Au z)3@1D!3`Pl2~ZCSAz79fgn)cnkEsVTGxc(KWL?kW?+*$K3(AbAS8wRM0@HY<#Oo?a={65{rLIw>l-pKD65Brda9{#g~&B~;e7Qwe;FOu!8p*K+wB)miWDG< zykeKI@X04no**et6&V+wOc&v0ggiL`F2ruREKGd`<2IEznzP+jU5Ol4Ha50}PA{I+ zmUh=gOCWuhL7*TGN%ga5O_-wZvKm!0q5qO9u?20SYf#JWaDj1Z1ML;tW1u~ppur_0l+tTp!BJ3G+BCUaB zA(dWdBxM>=zk)FFI8lpqveaWVJNm8N7Q?}FkHyqf`2msG-1Ztj0xq=@G;I%vcoKik z17}e&c{=s8TrFcrIf?AbH(jG?Bdw8$$+M&NL6-ts1R`&SY&k~r^4RZ(g1?mf)w@lw zK&^8=kBBFrJa>y|RYJCeZv0&*v2NX~n~1fW=Ij7l&5UVa@;9P=MWBUo5 zTtdRa4`05VJj})<=W@1bQef8HD!1xp{+zCxwO#M)gph>kOCr`HTbP+yFOGEhR$hMe z@87krR@uK+LMJe{i61H~Dn!dWjQRJ!6 zuUo957$uLMi$DDpmJk30f0sqYe-#IhyLXB4s@2F$(nK*zIC_2`lrSNB=pf@Qw%@eMkhB6* z>WnQ_T7A_|B*;za5kFbVswyiJN-FDC`&Vr2uQg6xt_z-0dVr^!tH)c;(xNff_R=2h zH*zUwH7{#U?Iz+H9~wNpP|z{Ac3k)I+fy%}T}ndk3(%Ij^jG>CN5bZo=6a{=YGnI8 zWDB;TqOmLXAFIzUdNrSXU=X?TLNm>vyl*iu zd~keXDkR%%@bRHWdWUo~MdC1vP21$ww?ge=_e0oAhvj_uYxSV{JV-{j_lLk?TG=yZ zAv@lcxIhj1o(G16O{V+3Ns?4XQ+CDg2U#sdr>W#T5AbdM51&^P4@T3AbLmABx-cIl|eE47{8{@p8dKT7)=Phr%1dqHeVEF;xFkII=VWkp77i}K{s zl6Y_)eoJEX4fIVo`)|jrbjs8ypq3xUlP4We#aykYv5n^>j(sD;|4=@-=fCgKW6}?Q z9mn@yWeN(oq;;gC`$W<3bw3)GllCLC>XalS!9a)MZNBAvM14_4v*`9YWU|8W($oXu P @@ -570,7 +569,6 @@ support-submeshes="false" output ="HEXA" need-hyp ="true" - context ="GLOBAL" dim ="3"> Cartesian_3D=BodyFitted() diff --git a/src/Controls/SMESH_Controls.cxx b/src/Controls/SMESH_Controls.cxx index 95e95a2cf..fc7ba629b 100644 --- a/src/Controls/SMESH_Controls.cxx +++ b/src/Controls/SMESH_Controls.cxx @@ -4262,6 +4262,7 @@ private: bool isOutOfFace (const gp_Pnt& p); bool isOutOfEdge (const gp_Pnt& p); bool isOutOfVertex(const gp_Pnt& p); + bool isOutOfNone (const gp_Pnt& p) { return true; } bool isBox (const TopoDS_Shape& s); bool (Classifier::* myIsOutFun)(const gp_Pnt& p); @@ -4583,7 +4584,7 @@ bool ElementsOnShape::IsSatisfy (const SMDS_MeshNode* node, { isNodeOut = false; if ( okShape ) - *okShape = myWorkClassifiers[i]->Shape(); + *okShape = myClassifiers[i].Shape(); break; } } @@ -4621,17 +4622,27 @@ void ElementsOnShape::Classifier::Init( const TopoDS_Shape& theShape, { Standard_Real u1,u2,v1,v2; Handle(Geom_Surface) surf = BRep_Tool::Surface( TopoDS::Face( theShape )); - surf->Bounds( u1,u2,v1,v2 ); - myProjFace.Init(surf, u1,u2, v1,v2, myTol ); - myIsOutFun = & ElementsOnShape::Classifier::isOutOfFace; + if ( surf.IsNull() ) + myIsOutFun = & ElementsOnShape::Classifier::isOutOfNone; + else + { + surf->Bounds( u1,u2,v1,v2 ); + myProjFace.Init(surf, u1,u2, v1,v2, myTol ); + myIsOutFun = & ElementsOnShape::Classifier::isOutOfFace; + } break; } case TopAbs_EDGE: { Standard_Real u1, u2; Handle(Geom_Curve) curve = BRep_Tool::Curve( TopoDS::Edge( theShape ), u1, u2); - myProjEdge.Init(curve, u1, u2); - myIsOutFun = & ElementsOnShape::Classifier::isOutOfEdge; + if ( curve.IsNull() ) + myIsOutFun = & ElementsOnShape::Classifier::isOutOfNone; + else + { + myProjEdge.Init(curve, u1, u2); + myIsOutFun = & ElementsOnShape::Classifier::isOutOfEdge; + } break; } case TopAbs_VERTEX: diff --git a/src/SMESH/SMESH_MeshEditor.cxx b/src/SMESH/SMESH_MeshEditor.cxx index db5b6507f..2104c7920 100644 --- a/src/SMESH/SMESH_MeshEditor.cxx +++ b/src/SMESH/SMESH_MeshEditor.cxx @@ -161,8 +161,7 @@ SMESH_MeshEditor::ElemFeatures::Init( const SMDS_MeshElement* elem, bool basicOn myIsQuad = elem->IsQuadratic(); if ( myType == SMDSAbs_Volume && !basicOnly ) { - vector quant = static_cast( elem )->GetQuantities(); - myPolyhedQuantities.swap( quant ); + myPolyhedQuantities = static_cast( elem )->GetQuantities(); } } } diff --git a/src/SMESHDS/SMESHDS_SubMesh.cxx b/src/SMESHDS/SMESHDS_SubMesh.cxx index 503de16b0..8d5dee889 100644 --- a/src/SMESHDS/SMESHDS_SubMesh.cxx +++ b/src/SMESHDS/SMESHDS_SubMesh.cxx @@ -115,9 +115,12 @@ void SMESHDS_SubMesh::AddElement(const SMDS_MeshElement * elem) (LOCALIZED("add element in subshape already belonging to a subshape")); } } + else + { + ++myNbElements; + } elem->setShapeID( myIndex ); - myNbElements++; // remember element with smallest ID to optimize iteration on them add( elem ); @@ -178,8 +181,11 @@ void SMESHDS_SubMesh::AddNode(const SMDS_MeshNode * N) (LOCALIZED("a node being in sub-mesh is added to another sub-mesh")); return; // already in } + else + { + ++myNbNodes; + } N->setShapeID( myIndex ); - myNbNodes++; // remember node with smallest ID to optimize iteration on them add( N ); diff --git a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx index b2e1e2496..03767040c 100644 --- a/src/SMESHGUI/SMESHGUI_Hypotheses.cxx +++ b/src/SMESHGUI/SMESHGUI_Hypotheses.cxx @@ -813,8 +813,8 @@ HypothesesSet::HypothesesSet( const QString& theSetName, : myUseCommonSize( useCommonSize ), myQuadDominated( isQuadDominated ), myHypoSetName( theSetName ), - myHypoList { mainHypos, altHypos, intHypos }, - myAlgoList { mainAlgos, altAlgos, intAlgos }, + myHypoList { mainHypos, altHypos, intHypos }, + myAlgoList { mainAlgos, altAlgos, intAlgos }, myIsAlgo( false ), myIsCustom( false ), myIndex( 0 ) diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx index 21bc0bbb1..fe479bb1e 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.cxx @@ -64,7 +64,10 @@ StdMeshers_CartesianParameters3D::StdMeshers_CartesianParameters3D(int h SMESH_Gen * gen) : SMESH_Hypothesis(hypId, gen), _sizeThreshold( 4.0 ), // default according to the customer specification - _toAddEdges( false ) + _toAddEdges( false ), + _toConsiderInternalFaces( false ), + _toUseThresholdForInternalFaces( false ), + _toCreateFaces( false ) { _name = "CartesianParameters3D"; // used by "Cartesian_3D" _param_algo_dim = 3; // 3D @@ -739,6 +742,48 @@ bool StdMeshers_CartesianParameters3D::GetToAddEdges() const return _toAddEdges; } +//======================================================================= +//function : SetToConsiderInternalFaces +//purpose : Enables treatment of geom faces either shared by solids or internal +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetToConsiderInternalFaces(bool toTreat) +{ + if ( _toConsiderInternalFaces != toTreat ) + { + _toConsiderInternalFaces = toTreat; + NotifySubMeshesHypothesisModification(); + } +} + +//======================================================================= +//function : SetToUseThresholdForInternalFaces +//purpose : Enables applying size threshold to grid cells cut by internal geom faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetToUseThresholdForInternalFaces(bool toUse) +{ + if ( _toUseThresholdForInternalFaces != toUse ) + { + _toUseThresholdForInternalFaces = toUse; + NotifySubMeshesHypothesisModification(); + } +} + +//======================================================================= +//function : SetToCreateFaces +//purpose : Enables creation of mesh faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D::SetToCreateFaces(bool toCreate) +{ + if ( _toCreateFaces != toCreate ) + { + _toCreateFaces = toCreate; + NotifySubMeshesHypothesisModification(); + } +} + //======================================================================= //function : IsDefined //purpose : Return true if parameters are well defined @@ -786,6 +831,10 @@ std::ostream & StdMeshers_CartesianParameters3D::SaveTo(std::ostream & save) for ( int i = 0; i < 3; ++i ) save << _fixedPoint[i] << " "; + save << " " << _toConsiderInternalFaces + << " " << _toUseThresholdForInternalFaces + << " " << _toCreateFaces; + return save; } @@ -844,6 +893,12 @@ std::istream & StdMeshers_CartesianParameters3D::LoadFrom(std::istream & load) for ( int i = 0; i < 3 && ok ; ++i ) ok = static_cast( load >> _fixedPoint[i]); + if ( load >> _toConsiderInternalFaces ) + { + load >> _toUseThresholdForInternalFaces; + load >> _toCreateFaces; + } + return load; } diff --git a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx index 089d19f46..fc8ceff4c 100644 --- a/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx +++ b/src/StdMeshers/StdMeshers_CartesianParameters3D.hxx @@ -139,6 +139,25 @@ public: void SetToAddEdges(bool toAdd); bool GetToAddEdges() const; + /*! + * \brief Enables treatment of geom faces either shared by solids or internal. + */ + void SetToConsiderInternalFaces(bool toTreat); + bool GetToConsiderInternalFaces() const { return _toConsiderInternalFaces; } + + /*! + * \brief Enables applying size threshold to grid cells cut by internal geom faces. + */ + void SetToUseThresholdForInternalFaces(bool toUse); + bool GetToUseThresholdForInternalFaces() const { return _toUseThresholdForInternalFaces; } + + /*! + * \brief Enables creation of mesh faces. + */ + void SetToCreateFaces(bool toCreate); + bool GetToCreateFaces() const { return _toCreateFaces; } + + /*! * \brief Return true if parameters are well defined */ @@ -171,6 +190,9 @@ public: double _sizeThreshold; bool _toAddEdges; + bool _toConsiderInternalFaces; + bool _toUseThresholdForInternalFaces; + bool _toCreateFaces; }; #endif diff --git a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx index 85a984c30..ea641c2b1 100644 --- a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx +++ b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx @@ -23,16 +23,22 @@ // Module : SMESH // #include "StdMeshers_Cartesian_3D.hxx" +#include "StdMeshers_CartesianParameters3D.hxx" +#include "ObjectPool.hxx" #include "SMDS_MeshNode.hxx" +#include "SMDS_VolumeTool.hxx" #include "SMESHDS_Mesh.hxx" #include "SMESH_Block.hxx" #include "SMESH_Comment.hxx" +#include "SMESH_ControlsDef.hxx" #include "SMESH_Mesh.hxx" +#include "SMESH_MeshAlgos.hxx" +#include "SMESH_MeshEditor.hxx" #include "SMESH_MesherHelper.hxx" #include "SMESH_subMesh.hxx" #include "SMESH_subMeshEventListener.hxx" -#include "StdMeshers_CartesianParameters3D.hxx" +#include "StdMeshers_FaceSide.hxx" #include #include @@ -89,6 +95,8 @@ #include +#include + //#undef WITH_TBB #ifdef WITH_TBB @@ -97,7 +105,6 @@ // Windows 10 = 0x0A00 #define WINVER 0x0A00 #define _WIN32_WINNT 0x0A00 - #endif #include @@ -105,6 +112,7 @@ #endif using namespace std; +using namespace SMESH; #ifdef _DEBUG_ //#define _MY_DEBUG_ @@ -161,7 +169,7 @@ bool StdMeshers_Cartesian_3D::CheckHypothesis (SMESH_Mesh& aMesh, namespace { - typedef int TGeomID; + typedef int TGeomID; // IDs of sub-shapes //============================================================================= // Definitions of internal utils @@ -170,7 +178,72 @@ namespace Trans_TANGENT = IntCurveSurface_Tangent, Trans_IN = IntCurveSurface_In, Trans_OUT = IntCurveSurface_Out, - Trans_APEX + Trans_APEX, + Trans_INTERNAL // for INTERNAL FACE + }; + // -------------------------------------------------------------------------- + /*! + * \brief Container of IDs of SOLID sub-shapes + */ + class Solid // sole SOLID contains all sub-shapes + { + TGeomID _id; // SOLID id + bool _hasInternalFaces; + public: + virtual ~Solid() {} + virtual bool Contains( TGeomID subID ) const { return true; } + virtual bool ContainsAny( const vector< TGeomID>& subIDs ) const { return true; } + virtual TopAbs_Orientation Orientation( const TopoDS_Shape& s ) const { return s.Orientation(); } + virtual bool IsOutsideOriented( TGeomID faceID ) const { return true; } + void SetID( TGeomID id ) { _id = id; } + TGeomID ID() const { return _id; } + void SetHasInternalFaces( bool has ) { _hasInternalFaces = has; } + bool HasInternalFaces() const { return _hasInternalFaces; } + }; + // -------------------------------------------------------------------------- + class OneOfSolids : public Solid + { + TColStd_MapOfInteger _subIDs; + TopTools_MapOfShape _faces; // keep FACE orientation + TColStd_MapOfInteger _outFaceIDs; // FACEs of shape_to_mesh oriented outside the SOLID + public: + void Init( const TopoDS_Shape& solid, + TopAbs_ShapeEnum subType, + const SMESHDS_Mesh* mesh ); + virtual bool Contains( TGeomID i ) const { return i == ID() || _subIDs.Contains( i ); } + virtual bool ContainsAny( const vector< TGeomID>& subIDs ) const + { + for ( size_t i = 0; i < subIDs.size(); ++i ) if ( Contains( subIDs[ i ])) return true; + return false; + } + virtual TopAbs_Orientation Orientation( const TopoDS_Shape& face ) const + { + const TopoDS_Shape& sInMap = const_cast< OneOfSolids* >(this)->_faces.Added( face ); + return sInMap.Orientation(); + } + virtual bool IsOutsideOriented( TGeomID faceID ) const + { + return faceID == 0 || _outFaceIDs.Contains( faceID ); + } + }; + // -------------------------------------------------------------------------- + /*! + * \brief Geom data + */ + struct Geometry + { + TopoDS_Shape _mainShape; + vector< vector< TGeomID > > _solidIDsByShapeID;// V/E/F ID -> SOLID IDs + Solid _soleSolid; + map< TGeomID, OneOfSolids > _solidByID; + TColStd_MapOfInteger _boundaryFaces; // FACEs on boundary of mesh->ShapeToMesh() + TColStd_MapOfInteger _strangeEdges; // EDGEs shared by strange FACEs + TGeomID _extIntFaceID; // pseudo FACE - extension of INTERNAL FACE + + Controls::ElementsOnShape _edgeClassifier; + Controls::ElementsOnShape _vertexClassifier; + + bool IsOneSolid() const { return _solidByID.size() < 2; } }; // -------------------------------------------------------------------------- /*! @@ -194,6 +267,7 @@ namespace struct F_IntersectPoint : public B_IntersectPoint { double _paramOnLine; + double _u, _v; mutable Transition _transition; mutable size_t _indexOnLine; @@ -207,7 +281,7 @@ namespace { gp_Pnt _point; double _uvw[3]; - TGeomID _shapeID; + TGeomID _shapeID; // ID of EDGE or VERTEX }; // -------------------------------------------------------------------------- /*! @@ -220,7 +294,9 @@ namespace multiset< F_IntersectPoint > _intPoints; void RemoveExcessIntPoints( const double tol ); - bool GetIsOutBefore( multiset< F_IntersectPoint >::iterator ip, bool prevIsOut ); + TGeomID GetSolidIDBefore( multiset< F_IntersectPoint >::iterator ip, + const TGeomID prevID, + const Geometry& geom); }; // -------------------------------------------------------------------------- /*! @@ -249,7 +325,7 @@ namespace { _size[0] = sz1; _size[1] = sz2; _size[2] = sz3; _curInd[0] = _curInd[1] = _curInd[2] = 0; - _iVar1 = iv1; _iVar2 = iv2; _iConst = iConst; + _iVar1 = iv1; _iVar2 = iv2; _iConst = iConst; _name1 = nv1; _name2 = nv2; _nameConst = nConst; } @@ -288,9 +364,16 @@ namespace vector< const SMDS_MeshNode* > _nodes; // mesh nodes at grid nodes vector< const F_IntersectPoint* > _gridIntP; // grid node intersection with geometry + ObjectPool< E_IntersectPoint > _edgeIntPool; // intersections with EDGEs + ObjectPool< F_IntersectPoint > _extIntPool; // intersections with extended INTERNAL FACEs + //list< E_IntersectPoint > _edgeIntP; // intersections with EDGEs - list< E_IntersectPoint > _edgeIntP; // intersections with EDGEs - TopTools_IndexedMapOfShape _shapes; + Geometry _geometry; + bool _toAddEdges; + bool _toCreateFaces; + bool _toConsiderInternalFaces; + bool _toUseThresholdForInternalFaces; + double _sizeThreshold; SMESH_MesherHelper* _helper; @@ -308,6 +391,43 @@ namespace LineIndexer GetLineIndexer(size_t iDir) const; + E_IntersectPoint* Add( const E_IntersectPoint& ip ) + { + E_IntersectPoint* eip = _edgeIntPool.getNew(); + *eip = ip; + return eip; + } + void Remove( E_IntersectPoint* eip ) { _edgeIntPool.destroy( eip ); } + + TGeomID ShapeID( const TopoDS_Shape& s ) const; + const TopoDS_Shape& Shape( TGeomID id ) const; + TopAbs_ShapeEnum ShapeType( TGeomID id ) const { return Shape(id).ShapeType(); } + void InitGeometry( const TopoDS_Shape& theShape ); + void InitClassifier( const TopoDS_Shape& mainShape, + TopAbs_ShapeEnum shapeType, + Controls::ElementsOnShape& classifier ); + void GetEdgesToImplement( map< TGeomID, vector< TGeomID > > & edge2faceMap, + const TopoDS_Shape& shape, + const vector< TopoDS_Shape >& faces ); + void SetSolidFather( const TopoDS_Shape& s, const TopoDS_Shape& theShapeToMesh ); + bool IsShared( TGeomID faceID ) const; + bool IsAnyShared( const std::vector< TGeomID >& faceIDs ) const; + bool IsInternal( TGeomID faceID ) const { + return ( faceID == PseudoIntExtFaceID() || + Shape( faceID ).Orientation() == TopAbs_INTERNAL ); } + bool IsSolid( TGeomID shapeID ) const { + if ( _geometry.IsOneSolid() ) return _geometry._soleSolid.ID() == shapeID; + else return _geometry._solidByID.count( shapeID ); } + bool IsStrangeEdge( TGeomID id ) const { return _geometry._strangeEdges.Contains( id ); } + TGeomID PseudoIntExtFaceID() const { return _geometry._extIntFaceID; } + Solid* GetSolid( TGeomID solidID = 0 ); + Solid* GetOneOfSolids( TGeomID solidID ); + const vector< TGeomID > & GetSolidIDs( TGeomID subShapeID ) const; + bool IsCorrectTransition( TGeomID faceID, const Solid* solid ); + bool IsBoundaryFace( TGeomID face ) const { return _geometry._boundaryFaces.Contains( face ); } + void SetOnShape( const SMDS_MeshNode* n, const F_IntersectPoint& ip, bool unset=false ); + bool IsToCheckNodePos() const { return !_toAddEdges && _toCreateFaces; } + void SetCoordinates(const vector& xCoords, const vector& yCoords, const vector& zCoords, @@ -317,6 +437,49 @@ namespace void ComputeNodes(SMESH_MesherHelper& helper); }; // -------------------------------------------------------------------------- + /*! + * \brief Return cells sharing a link + */ + struct CellsAroundLink + { + int _dInd[4][3]; + size_t _nbCells[3]; + int _i,_j,_k; + Grid* _grid; + + CellsAroundLink( Grid* grid, int iDir ): + _dInd{ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} }, + _nbCells{ grid->_coords[0].size() - 1, + grid->_coords[1].size() - 1, + grid->_coords[2].size() - 1 }, + _grid( grid ) + { + const int iDirOther[3][2] = {{ 1,2 },{ 0,2 },{ 0,1 }}; + _dInd[1][ iDirOther[iDir][0] ] = -1; + _dInd[2][ iDirOther[iDir][1] ] = -1; + _dInd[3][ iDirOther[iDir][0] ] = -1; _dInd[3][ iDirOther[iDir][1] ] = -1; + } + void Init( int i, int j, int k, int link12 = 0 ) + { + int iL = link12 % 4; + _i = i - _dInd[iL][0]; + _j = j - _dInd[iL][1]; + _k = k - _dInd[iL][2]; + } + bool GetCell( int iL, int& i, int& j, int& k, int& cellIndex ) + { + i = _i + _dInd[iL][0]; + j = _j + _dInd[iL][1]; + k = _k + _dInd[iL][2]; + if ( i < 0 || i >= (int)_nbCells[0] || + j < 0 || j >= (int)_nbCells[1] || + k < 0 || k >= (int)_nbCells[2] ) + return false; + cellIndex = _grid->CellIndex( i,j,k ); + return true; + } + }; + // -------------------------------------------------------------------------- /*! * \brief Intersector of TopoDS_Face with all GridLine's */ @@ -336,7 +499,7 @@ namespace { for ( size_t i = 0; i < _intersections.size(); ++i ) { - multiset< F_IntersectPoint >::iterator ip = + multiset< F_IntersectPoint >::iterator ip = _intersections[i].first->_intPoints.insert( _intersections[i].second ); ip->_faceIDs.reserve( 1 ); ip->_faceIDs.push_back( _faceID ); @@ -368,7 +531,7 @@ namespace { double _tol; double _u, _v, _w; // params on the face and the line - Transition _transition; // transition of at intersection (see IntCurveSurface.cdl) + Transition _transition; // transition at intersection (see IntCurveSurface.cdl) Transition _transIn, _transOut; // IN and OUT transitions depending of face orientation gp_Pln _plane; @@ -406,36 +569,31 @@ namespace // -------------------------------------------------------------------------------- struct _Face; struct _Link; + enum IsInternalFlag { IS_NOT_INTERNAL, IS_INTERNAL, IS_CUT_BY_INTERNAL_FACE }; // -------------------------------------------------------------------------------- struct _Node //!< node either at a hexahedron corner or at intersection { const SMDS_MeshNode* _node; // mesh node at hexahedron corner const B_IntersectPoint* _intPoint; const _Face* _usedInFace; + char _isInternalFlags; _Node(const SMDS_MeshNode* n=0, const B_IntersectPoint* ip=0) - :_node(n), _intPoint(ip), _usedInFace(0) {} + :_node(n), _intPoint(ip), _usedInFace(0), _isInternalFlags(0) {} const SMDS_MeshNode* Node() const { return ( _intPoint && _intPoint->_node ) ? _intPoint->_node : _node; } const E_IntersectPoint* EdgeIntPnt() const { return static_cast< const E_IntersectPoint* >( _intPoint ); } + const F_IntersectPoint* FaceIntPnt() const + { return static_cast< const F_IntersectPoint* >( _intPoint ); } + const vector< TGeomID >& faces() const { return _intPoint->_faceIDs; } + TGeomID face(size_t i) const { return _intPoint->_faceIDs[ i ]; } + void SetInternal( IsInternalFlag intFlag ) { _isInternalFlags |= intFlag; } + bool IsCutByInternal() const { return _isInternalFlags & IS_CUT_BY_INTERNAL_FACE; } bool IsUsedInFace( const _Face* polygon = 0 ) { return polygon ? ( _usedInFace == polygon ) : bool( _usedInFace ); } - void Add( const E_IntersectPoint* ip ) - { - if ( !_intPoint ) { - _intPoint = ip; - } - else if ( !_intPoint->_node ) { - ip->Add( _intPoint->_faceIDs ); - _intPoint = ip; - } - else { - _intPoint->Add( ip->_faceIDs ); - } - } TGeomID IsLinked( const B_IntersectPoint* other, TGeomID avoidFace=-1 ) const // returns id of a common face { @@ -448,7 +606,7 @@ namespace gp_Pnt Point() const { if ( const SMDS_MeshNode* n = Node() ) - return SMESH_TNodeXYZ( n ); + return SMESH_NodeXYZ( n ); if ( const E_IntersectPoint* eip = dynamic_cast< const E_IntersectPoint* >( _intPoint )) return eip->_point; @@ -460,6 +618,27 @@ namespace return eip->_shapeID; return 0; } + void Add( const E_IntersectPoint* ip ) + { + // Possible cases before Add(ip): + /// 1) _node != 0 --> _Node at hex corner ( _intPoint == 0 || _intPoint._node == 0 ) + /// 2) _node == 0 && _intPoint._node != 0 --> link intersected by FACE + /// 3) _node == 0 && _intPoint._node == 0 --> _Node at EDGE intersection + // + // If ip is added in cases 1) and 2) _node position must be changed to ip._shapeID + // at creation of elements + // To recognize this case, set _intPoint._node = Node() + const SMDS_MeshNode* node = Node(); + if ( !_intPoint ) { + _intPoint = ip; + } + else { + ip->Add( _intPoint->_faceIDs ); + _intPoint = ip; + } + if ( node ) + _node = _intPoint->_node = node; + } }; // -------------------------------------------------------------------------------- struct _Link // link connecting two _Node's @@ -469,7 +648,7 @@ namespace vector< const F_IntersectPoint* > _fIntPoints; // GridLine intersections with FACEs vector< _Node* > _fIntNodes; // _Node's at _fIntPoints vector< _Link > _splits; - _Link() { _faces[0] = 0; } + _Link(): _faces{ 0, 0 } {} }; // -------------------------------------------------------------------------------- struct _OrientedLink @@ -541,6 +720,43 @@ namespace } }; // -------------------------------------------------------------------------------- + struct _SplitIterator //! set to _hexLinks splits on one side of INTERNAL FACEs + { + struct _Split // data of a link split + { + int _linkID; // hex link ID + _Node* _nodes[2]; + int _iCheckIteration; // iteration where split is tried as Hexahedron split + _Link* _checkedSplit; // split set to hex links + bool _isUsed; // used in a volume + + _Split( _Link & split, int iLink ): + _linkID( iLink ), _nodes{ split._nodes[0], split._nodes[1] }, + _iCheckIteration( 0 ), _isUsed( false ) + {} + bool IsCheckedOrUsed( bool used ) const { return used ? _isUsed : _iCheckIteration > 0; } + }; + _Link* _hexLinks; + std::vector< _Split > _splits; + int _iterationNb; + size_t _nbChecked; + size_t _nbUsed; + std::vector< _Node* > _freeNodes; // nodes reached while composing a split set + + _SplitIterator( _Link* hexLinks ): + _hexLinks( hexLinks ), _iterationNb(0), _nbChecked(0), _nbUsed(0) + { + _freeNodes.reserve( 12 ); + _splits.reserve( 24 ); + for ( int iL = 0; iL < 12; ++iL ) + for ( size_t iS = 0; iS < _hexLinks[ iL ]._splits.size(); ++iS ) + _splits.emplace_back( _hexLinks[ iL ]._splits[ iS ], iL ); + Next(); + } + bool More() const { return _nbUsed < _splits.size(); } + bool Next(); + }; + // -------------------------------------------------------------------------------- struct _Face { vector< _OrientedLink > _links; // links on GridLine's @@ -573,14 +789,37 @@ namespace // -------------------------------------------------------------------------------- struct _volumeDef // holder of nodes of a volume mesh element { - vector< _Node* > _nodes; - vector< int > _quantities; - typedef boost::shared_ptr<_volumeDef> Ptr; - void set( const vector< _Node* >& nodes, - const vector< int >& quant = vector< int >() ) - { _nodes = nodes; _quantities = quant; } - void set( _Node** nodes, int nb ) + struct _nodeDef + { + const SMDS_MeshNode* _node; // mesh node at hexahedron corner + const B_IntersectPoint* _intPoint; + + _nodeDef( _Node* n ): _node( n->_node), _intPoint( n->_intPoint ) {} + const SMDS_MeshNode* Node() const + { return ( _intPoint && _intPoint->_node ) ? _intPoint->_node : _node; } + const E_IntersectPoint* EdgeIntPnt() const + { return static_cast< const E_IntersectPoint* >( _intPoint ); } + }; + vector< _nodeDef > _nodes; + vector< int > _quantities; + _volumeDef* _next; // to store several _volumeDefs in a chain + TGeomID _solidID; + const SMDS_MeshElement* _volume; // new volume + + _volumeDef(): _next(0), _solidID(0), _volume(0) {} + ~_volumeDef() { delete _next; } + _volumeDef( _volumeDef& other ): + _next(0), _solidID( other._solidID ), _volume( other._volume ) + { _nodes.swap( other._nodes ); _quantities.swap( other._quantities ); other._volume = 0; } + + void Set( const vector< _Node* >& nodes, const vector< int >& quant = vector< int >() ) + { _nodes.assign( nodes.begin(), nodes.end() ); _quantities = quant; } + + void Set( _Node** nodes, int nb ) { _nodes.assign( nodes, nodes + nb ); } + + void SetNext( _volumeDef* vd ) + { if ( _next ) { _next->SetNext( vd ); } else { _next = vd; }} }; // topology of a hexahedron @@ -602,26 +841,33 @@ namespace vector< _Node* > _vIntNodes; // computed volume elements - //vector< _volumeDef::Ptr > _volumeDefs; _volumeDef _volumeDefs; Grid* _grid; - double _sizeThreshold, _sideLength[3]; + double _sideLength[3]; int _nbCornerNodes, _nbFaceIntNodes, _nbBndNodes; int _origNodeInd; // index of _hexNodes[0] node within the _grid size_t _i,_j,_k; + bool _hasTooSmall; + +#ifdef _DEBUG_ + int _cellID; +#endif public: - Hexahedron(const double sizeThreshold, Grid* grid); + Hexahedron(Grid* grid); int MakeElements(SMESH_MesherHelper& helper, const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap); - void ComputeElements(); - void Init() { init( _i, _j, _k ); } + void ComputeElements( const Solid* solid = 0, int solidIndex = -1 ); private: - Hexahedron(const Hexahedron& other ); - void init( size_t i, size_t j, size_t k ); + Hexahedron(const Hexahedron& other, size_t i, size_t j, size_t k, int cellID ); + void init( size_t i, size_t j, size_t k, const Solid* solid=0 ); void init( size_t i ); + void setIJK( size_t i ); + bool compute( const Solid* solid, const IsInternalFlag intFlag ); + vector< TGeomID > getSolids(); + bool isCutByInternalFace( IsInternalFlag & maxFlag ); void addEdges(SMESH_MesherHelper& helper, vector< Hexahedron* >& intersectedHex, const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap); @@ -629,7 +875,7 @@ namespace double proj, BRepAdaptor_Curve& curve, const gp_XYZ& axis, const gp_XYZ& origin ); int getEntity( const E_IntersectPoint* ip, int* facets, int& sub ); - bool addIntersection( const E_IntersectPoint& ip, + bool addIntersection( const E_IntersectPoint* ip, vector< Hexahedron* >& hexes, int ijk[], int dIJK[] ); bool findChain( _Node* n1, _Node* n2, _Face& quad, vector<_Node*>& chainNodes ); @@ -640,11 +886,22 @@ namespace size_t & iS, _Face& quad, vector<_Node*>& chn); - int addElements(SMESH_MesherHelper& helper); - bool isOutPoint( _Link& link, int iP, SMESH_MesherHelper& helper ) const; + int addVolumes(SMESH_MesherHelper& helper ); + void addFaces( SMESH_MesherHelper& helper, + const vector< const SMDS_MeshElement* > & boundaryVolumes ); + void addSegments( SMESH_MesherHelper& helper, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap ); + void getVolumes( vector< const SMDS_MeshElement* > & volumes ); + void getBoundaryElems( vector< const SMDS_MeshElement* > & boundaryVolumes ); + TGeomID getAnyFace() const; + void cutByExtendedInternal( std::vector< Hexahedron* >& hexes, + const TColStd_MapOfInteger& intEdgeIDs ); + gp_Pnt mostDistantInternalPnt( int hexIndex, const gp_Pnt& p1, const gp_Pnt& p2 ); + bool isOutPoint( _Link& link, int iP, SMESH_MesherHelper& helper, const Solid* solid ) const; void sortVertexNodes(vector<_Node*>& nodes, _Node* curNode, TGeomID face); bool isInHole() const; - bool checkPolyhedronSize() const; + bool hasStrangeEdge() const; + bool checkPolyhedronSize( bool isCutByInternalFace ) const; bool addHexa (); bool addTetra(); bool addPenta(); @@ -660,8 +917,16 @@ namespace return nodes[i]; return 0; } - bool isImplementEdges() const { return !_grid->_edgeIntP.empty(); } + bool isImplementEdges() const { return _grid->_edgeIntPool.nbElements(); } bool isOutParam(const double uvw[3]) const; + + typedef boost::container::flat_map< TGeomID, size_t > TID2Nb; + static void insertAndIncrement( TGeomID id, TID2Nb& id2nbMap ) + { + TID2Nb::value_type s0( id, 0 ); + TID2Nb::iterator id2nb = id2nbMap.insert( s0 ).first; + id2nb->second++; + } }; #ifdef WITH_TBB @@ -756,29 +1021,72 @@ namespace } //================================================================================ /* - * Return "is OUT" state for nodes before the given intersection point + * Return ID of SOLID for nodes before the given intersection point */ - bool GridLine::GetIsOutBefore( multiset< F_IntersectPoint >::iterator ip, bool prevIsOut ) + TGeomID GridLine::GetSolidIDBefore( multiset< F_IntersectPoint >::iterator ip, + const TGeomID prevID, + const Geometry& geom ) { - if ( ip->_transition == Trans_IN ) - return true; - if ( ip->_transition == Trans_OUT ) - return false; - if ( ip->_transition == Trans_APEX ) + if ( ip == _intPoints.begin() ) + return 0; + + if ( geom.IsOneSolid() ) { - // singularity point (apex of a cone) - if ( _intPoints.size() == 1 || ip == _intPoints.begin() ) - return true; - multiset< F_IntersectPoint >::iterator ipBef = ip, ipAft = ++ip; - if ( ipAft == _intPoints.end() ) - return false; - --ipBef; - if ( ipBef->_transition != ipAft->_transition ) - return ( ipBef->_transition == Trans_OUT ); - return ( ipBef->_transition != Trans_OUT ); + bool isOut = true; + switch ( ip->_transition ) { + case Trans_IN: isOut = true; break; + case Trans_OUT: isOut = false; break; + case Trans_TANGENT: isOut = ( prevID == 0 ); break; + case Trans_APEX: + { + // singularity point (apex of a cone) + multiset< F_IntersectPoint >::iterator ipBef = ip, ipAft = ++ip; + if ( ipAft == _intPoints.end() ) + isOut = false; + else + { + --ipBef; + if ( ipBef->_transition != ipAft->_transition ) + isOut = ( ipBef->_transition == Trans_OUT ); + else + isOut = ( ipBef->_transition != Trans_OUT ); + } + break; + } + case Trans_INTERNAL: isOut = false; + default:; + } + return isOut ? 0 : geom._soleSolid.ID(); } - // _transition == Trans_TANGENT - return !prevIsOut; + + const vector< TGeomID >& solids = geom._solidIDsByShapeID[ ip->_faceIDs[ 0 ]]; + + --ip; + if ( ip->_transition == Trans_INTERNAL ) + return prevID; + + const vector< TGeomID >& solidsBef = geom._solidIDsByShapeID[ ip->_faceIDs[ 0 ]]; + + if ( ip->_transition == Trans_IN || + ip->_transition == Trans_OUT ) + { + if ( solidsBef.size() == 1 ) + return ( solidsBef[0] == prevID ) ? 0 : solidsBef[0]; + + return solidsBef[ solidsBef[0] == prevID ]; + } + + if ( solidsBef.size() == 1 ) + return solidsBef[0]; + + for ( size_t i = 0; i < solids.size(); ++i ) + { + vector< TGeomID >::const_iterator it = + std::find( solidsBef.begin(), solidsBef.end(), solids[i] ); + if ( it != solidsBef.end() ) + return solids[i]; + } + return 0; } //================================================================================ /* @@ -824,6 +1132,35 @@ namespace return ( it != _faceIDs.end() ); } //================================================================================ + /* + * OneOfSolids initialization + */ + void OneOfSolids::Init( const TopoDS_Shape& solid, + TopAbs_ShapeEnum subType, + const SMESHDS_Mesh* mesh ) + { + SetID( mesh->ShapeToIndex( solid )); + + if ( subType == TopAbs_FACE ) + SetHasInternalFaces( false ); + + for ( TopExp_Explorer sub( solid, subType ); sub.More(); sub.Next() ) + { + _subIDs.Add( mesh->ShapeToIndex( sub.Current() )); + if ( subType == TopAbs_FACE ) + { + _faces.Add( sub.Current() ); + if ( sub.Current().Orientation() == TopAbs_INTERNAL ) + SetHasInternalFaces( true ); + + TGeomID faceID = mesh->ShapeToIndex( sub.Current() ); + if ( sub.Current().Orientation() == TopAbs_INTERNAL || + sub.Current().Orientation() == mesh->IndexToShape( faceID ).Orientation() ) + _outFaceIDs.Add( faceID ); + } + } + } + //================================================================================ /* * Return an iterator on GridLine's in a given direction */ @@ -928,6 +1265,290 @@ namespace } } } + //================================================================================ + /* + * Return local ID of shape + */ + TGeomID Grid::ShapeID( const TopoDS_Shape& s ) const + { + return _helper->GetMeshDS()->ShapeToIndex( s ); + } + //================================================================================ + /* + * Return a shape by its local ID + */ + const TopoDS_Shape& Grid::Shape( TGeomID id ) const + { + return _helper->GetMeshDS()->IndexToShape( id ); + } + //================================================================================ + /* + * Initialize _geometry + */ + void Grid::InitGeometry( const TopoDS_Shape& theShapeToMesh ) + { + SMESH_Mesh* mesh = _helper->GetMesh(); + + _geometry._mainShape = theShapeToMesh; + _geometry._extIntFaceID = mesh->GetMeshDS()->MaxShapeIndex() * 100; + _geometry._soleSolid.SetID( 0 ); + _geometry._soleSolid.SetHasInternalFaces( false ); + + InitClassifier( theShapeToMesh, TopAbs_VERTEX, _geometry._vertexClassifier ); + InitClassifier( theShapeToMesh, TopAbs_EDGE , _geometry._edgeClassifier ); + + TopExp_Explorer solidExp( theShapeToMesh, TopAbs_SOLID ); + + bool isSeveralSolids = false; + if ( _toConsiderInternalFaces ) // check nb SOLIDs + { + solidExp.Next(); + isSeveralSolids = solidExp.More(); + _toConsiderInternalFaces = isSeveralSolids; + solidExp.ReInit(); + + if ( !isSeveralSolids ) // look for an internal FACE + { + TopExp_Explorer fExp( theShapeToMesh, TopAbs_FACE ); + for ( ; fExp.More() && !_toConsiderInternalFaces; fExp.Next() ) + _toConsiderInternalFaces = ( fExp.Current().Orientation() == TopAbs_INTERNAL ); + + _geometry._soleSolid.SetHasInternalFaces( _toConsiderInternalFaces ); + _geometry._soleSolid.SetID( ShapeID( solidExp.Current() )); + } + else // fill Geometry::_solidByID + { + for ( ; solidExp.More(); solidExp.Next() ) + { + OneOfSolids & solid = _geometry._solidByID[ ShapeID( solidExp.Current() )]; + solid.Init( solidExp.Current(), TopAbs_FACE, mesh->GetMeshDS() ); + solid.Init( solidExp.Current(), TopAbs_EDGE, mesh->GetMeshDS() ); + solid.Init( solidExp.Current(), TopAbs_VERTEX, mesh->GetMeshDS() ); + } + } + } + else + { + _geometry._soleSolid.SetID( ShapeID( solidExp.Current() )); + } + + if ( !_toCreateFaces ) + { + int nbSolidsGlobal = _helper->Count( mesh->GetShapeToMesh(), TopAbs_SOLID, false ); + int nbSolidsLocal = _helper->Count( theShapeToMesh, TopAbs_SOLID, false ); + _toCreateFaces = ( nbSolidsLocal < nbSolidsGlobal ); + } + + TopTools_IndexedMapOfShape faces; + if ( _toCreateFaces || isSeveralSolids ) + TopExp::MapShapes( theShapeToMesh, TopAbs_FACE, faces ); + + // find boundary FACEs on boundary of mesh->ShapeToMesh() + if ( _toCreateFaces ) + for ( int i = 1; i <= faces.Size(); ++i ) + if ( faces(i).Orientation() != TopAbs_INTERNAL && + _helper->NbAncestors( faces(i), *mesh, TopAbs_SOLID ) == 1 ) + { + _geometry._boundaryFaces.Add( ShapeID( faces(i) )); + } + + if ( isSeveralSolids ) + for ( int i = 1; i <= faces.Size(); ++i ) + { + SetSolidFather( faces(i), theShapeToMesh ); + for ( TopExp_Explorer eExp( faces(i), TopAbs_EDGE ); eExp.More(); eExp.Next() ) + { + const TopoDS_Edge& edge = TopoDS::Edge( eExp.Current() ); + SetSolidFather( edge, theShapeToMesh ); + SetSolidFather( _helper->IthVertex( 0, edge ), theShapeToMesh ); + SetSolidFather( _helper->IthVertex( 1, edge ), theShapeToMesh ); + } + } + return; + } + //================================================================================ + /* + * Store ID of SOLID as father of its child shape ID + */ + void Grid::SetSolidFather( const TopoDS_Shape& s, const TopoDS_Shape& theShapeToMesh ) + { + if ( _geometry._solidIDsByShapeID.empty() ) + _geometry._solidIDsByShapeID.resize( _helper->GetMeshDS()->MaxShapeIndex() + 1 ); + + vector< TGeomID > & solidIDs = _geometry._solidIDsByShapeID[ ShapeID( s )]; + if ( !solidIDs.empty() ) + return; + solidIDs.reserve(2); + PShapeIteratorPtr solidIt = _helper->GetAncestors( s, + *_helper->GetMesh(), + TopAbs_SOLID, + & theShapeToMesh ); + while ( const TopoDS_Shape* solid = solidIt->next() ) + solidIDs.push_back( ShapeID( *solid )); + } + //================================================================================ + /* + * Return IDs of solids given sub-shape belongs to + */ + const vector< TGeomID > & Grid::GetSolidIDs( TGeomID subShapeID ) const + { + return _geometry._solidIDsByShapeID[ subShapeID ]; + } + //================================================================================ + /* + * Check if a sub-shape belongs to several SOLIDs + */ + bool Grid::IsShared( TGeomID shapeID ) const + { + return !_geometry.IsOneSolid() && ( _geometry._solidIDsByShapeID[ shapeID ].size() > 1 ); + } + //================================================================================ + /* + * Check if any of FACEs belongs to several SOLIDs + */ + bool Grid::IsAnyShared( const std::vector< TGeomID >& faceIDs ) const + { + for ( size_t i = 0; i < faceIDs.size(); ++i ) + if ( IsShared( faceIDs[ i ])) + return true; + return false; + } + //================================================================================ + /* + * Return Solid by ID + */ + Solid* Grid::GetSolid( TGeomID solidID ) + { + if ( !solidID || _geometry.IsOneSolid() || _geometry._solidByID.empty() ) + return & _geometry._soleSolid; + + return & _geometry._solidByID[ solidID ]; + } + //================================================================================ + /* + * Return OneOfSolids by ID + */ + Solid* Grid::GetOneOfSolids( TGeomID solidID ) + { + map< TGeomID, OneOfSolids >::iterator is2s = _geometry._solidByID.find( solidID ); + if ( is2s != _geometry._solidByID.end() ) + return & is2s->second; + + return & _geometry._soleSolid; + } + //================================================================================ + /* + * Check if transition on given FACE is correct for a given SOLID + */ + bool Grid::IsCorrectTransition( TGeomID faceID, const Solid* solid ) + { + if ( _geometry.IsOneSolid() ) + return true; + + const vector< TGeomID >& solidIDs = _geometry._solidIDsByShapeID[ faceID ]; + return solidIDs[0] == solid->ID(); + } + + //================================================================================ + /* + * Assign to geometry a node at FACE intersection + */ + void Grid::SetOnShape( const SMDS_MeshNode* n, const F_IntersectPoint& ip, bool unset ) + { + TopoDS_Shape s; + SMESHDS_Mesh* mesh = _helper->GetMeshDS(); + if ( ip._faceIDs.size() == 1 ) + { + mesh->SetNodeOnFace( n, ip._faceIDs[0], ip._u, ip._v ); + } + else if ( _geometry._vertexClassifier.IsSatisfy( n, &s )) + { + if ( unset ) mesh->UnSetNodeOnShape( n ); + mesh->SetNodeOnVertex( n, TopoDS::Vertex( s )); + } + else if ( _geometry._edgeClassifier.IsSatisfy( n, &s )) + { + if ( unset ) mesh->UnSetNodeOnShape( n ); + mesh->SetNodeOnEdge( n, TopoDS::Edge( s )); + } + else if ( ip._faceIDs.size() > 0 ) + { + mesh->SetNodeOnFace( n, ip._faceIDs[0], ip._u, ip._v ); + } + else if ( !unset && _geometry.IsOneSolid() ) + { + mesh->SetNodeInVolume( n, _geometry._soleSolid.ID() ); + } + } + //================================================================================ + /* + * Initialize a classifier + */ + void Grid::InitClassifier( const TopoDS_Shape& mainShape, + TopAbs_ShapeEnum shapeType, + Controls::ElementsOnShape& classifier ) + { + TopTools_IndexedMapOfShape shapes; + TopExp::MapShapes( mainShape, shapeType, shapes ); + + TopoDS_Compound compound; BRep_Builder builder; + builder.MakeCompound( compound ); + for ( int i = 1; i <= shapes.Size(); ++i ) + builder.Add( compound, shapes(i) ); + + classifier.SetMesh( _helper->GetMeshDS() ); + //classifier.SetTolerance( _tol ); // _tol is not initialised + classifier.SetShape( compound, SMDSAbs_Node ); + } + + //================================================================================ + /* + * Return EDGEs with FACEs to implement into the mesh + */ + void Grid::GetEdgesToImplement( map< TGeomID, vector< TGeomID > > & edge2faceIDsMap, + const TopoDS_Shape& shape, + const vector< TopoDS_Shape >& faces ) + { + // check if there are strange EDGEs + TopTools_IndexedMapOfShape faceMap; + TopExp::MapShapes( _helper->GetMesh()->GetShapeToMesh(), TopAbs_FACE, faceMap ); + int nbFacesGlobal = faceMap.Size(); + faceMap.Clear( false ); + TopExp::MapShapes( shape, TopAbs_FACE, faceMap ); + int nbFacesLocal = faceMap.Size(); + bool hasStrangeEdges = ( nbFacesGlobal > nbFacesLocal ); + if ( !_toAddEdges && !hasStrangeEdges ) + return; // no FACEs in contact with those meshed by other algo + + for ( size_t i = 0; i < faces.size(); ++i ) + { + _helper->SetSubShape( faces[i] ); + for ( TopExp_Explorer eExp( faces[i], TopAbs_EDGE ); eExp.More(); eExp.Next() ) + { + const TopoDS_Edge& edge = TopoDS::Edge( eExp.Current() ); + if ( hasStrangeEdges ) + { + bool hasStrangeFace = false; + PShapeIteratorPtr faceIt = _helper->GetAncestors( edge, *_helper->GetMesh(), TopAbs_FACE); + while ( const TopoDS_Shape* face = faceIt->next() ) + if (( hasStrangeFace = !faceMap.Contains( *face ))) + break; + if ( !hasStrangeFace && !_toAddEdges ) + continue; + _geometry._strangeEdges.Add( ShapeID( edge )); + _geometry._strangeEdges.Add( ShapeID( _helper->IthVertex( 0, edge ))); + _geometry._strangeEdges.Add( ShapeID( _helper->IthVertex( 1, edge ))); + } + if ( !SMESH_Algo::isDegenerated( edge ) && + !_helper->IsRealSeam( edge )) + { + edge2faceIDsMap[ ShapeID( edge )].push_back( ShapeID( faces[i] )); + } + } + } + return; + } + //================================================================================ /* * Computes coordinates of a point in the grid CS @@ -945,10 +1566,13 @@ namespace { // state of each node of the grid relative to the geometry const size_t nbGridNodes = _coords[0].size() * _coords[1].size() * _coords[2].size(); - vector< bool > isNodeOut( nbGridNodes, false ); + const TGeomID undefID = 1e+9; + vector< TGeomID > shapeIDVec( nbGridNodes, undefID ); _nodes.resize( nbGridNodes, 0 ); _gridIntP.resize( nbGridNodes, NULL ); + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + for ( int iDir = 0; iDir < 3; ++iDir ) // loop on 3 line directions { LineIndexer li = GetLineIndexer( iDir ); @@ -968,26 +1592,30 @@ namespace GridLine& line = _lines[ iDir ][ li.LineIndex() ]; const gp_XYZ lineLoc = line._line.Location().XYZ(); const gp_XYZ lineDir = line._line.Direction().XYZ(); + line.RemoveExcessIntPoints( _tol ); - multiset< F_IntersectPoint >& intPnts = line._intPoints; + multiset< F_IntersectPoint >& intPnts = line._intPoints; multiset< F_IntersectPoint >::iterator ip = intPnts.begin(); - bool isOut = true; + // Create mesh nodes at intersections with geometry + // and set OUT state of nodes between intersections + + TGeomID solidID = 0; const double* nodeCoord = & coords[0]; const double* coord0 = nodeCoord; const double* coordEnd = coord0 + coords.size(); double nodeParam = 0; for ( ; ip != intPnts.end(); ++ip ) { + solidID = line.GetSolidIDBefore( ip, solidID, _geometry ); + // set OUT state or just skip IN nodes before ip if ( nodeParam < ip->_paramOnLine - _tol ) { - isOut = line.GetIsOutBefore( ip, isOut ); - while ( nodeParam < ip->_paramOnLine - _tol ) { - if ( isOut ) - isNodeOut[ nIndex0 + nShift * ( nodeCoord-coord0 ) ] = isOut; + TGeomID & nodeShapeID = shapeIDVec[ nIndex0 + nShift * ( nodeCoord-coord0 ) ]; + nodeShapeID = Min( solidID, nodeShapeID ); if ( ++nodeCoord < coordEnd ) nodeParam = *nodeCoord - *coord0; else @@ -998,24 +1626,21 @@ namespace // create a mesh node on a GridLine at ip if it does not coincide with a grid node if ( nodeParam > ip->_paramOnLine + _tol ) { - // li.SetIndexOnLine( 0 ); - // double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; - // xyz[ li._iConst ] += ip->_paramOnLine; gp_XYZ xyz = lineLoc + ip->_paramOnLine * lineDir; - ip->_node = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + ip->_node = mesh->AddNode( xyz.X(), xyz.Y(), xyz.Z() ); ip->_indexOnLine = nodeCoord-coord0-1; + SetOnShape( ip->_node, *ip ); } - // create a mesh node at ip concident with a grid node + // create a mesh node at ip coincident with a grid node else { int nodeIndex = nIndex0 + nShift * ( nodeCoord-coord0 ); if ( !_nodes[ nodeIndex ] ) { - //li.SetIndexOnLine( nodeCoord-coord0 ); - //double xyz[3] = { _coords[0][ li.I() ], _coords[1][ li.J() ], _coords[2][ li.K() ]}; gp_XYZ xyz = lineLoc + nodeParam * lineDir; - _nodes [ nodeIndex ] = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); - _gridIntP[ nodeIndex ] = & * ip; + _nodes [ nodeIndex ] = mesh->AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + //_gridIntP[ nodeIndex ] = & * ip; + //SetOnShape( _nodes[ nodeIndex ], *ip ); } if ( _gridIntP[ nodeIndex ] ) _gridIntP[ nodeIndex ]->Add( ip->_faceIDs ); @@ -1029,7 +1654,7 @@ namespace } // set OUT state to nodes after the last ip for ( ; nodeCoord < coordEnd; ++nodeCoord ) - isNodeOut[ nIndex0 + nShift * ( nodeCoord-coord0 ) ] = true; + shapeIDVec[ nIndex0 + nShift * ( nodeCoord-coord0 ) ] = 0; } } @@ -1040,13 +1665,19 @@ namespace for ( size_t x = 0; x < _coords[0].size(); ++x ) { size_t nodeIndex = NodeIndex( x, y, z ); - if ( !isNodeOut[ nodeIndex ] && !_nodes[ nodeIndex] ) + if ( !_nodes[ nodeIndex ] && + 0 < shapeIDVec[ nodeIndex ] && shapeIDVec[ nodeIndex ] < undefID ) { - //_nodes[ nodeIndex ] = helper.AddNode( _coords[0][x], _coords[1][y], _coords[2][z] ); gp_XYZ xyz = ( _coords[0][x] * _axes[0] + _coords[1][y] * _axes[1] + _coords[2][z] * _axes[2] ); - _nodes[ nodeIndex ] = helper.AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + _nodes[ nodeIndex ] = mesh->AddNode( xyz.X(), xyz.Y(), xyz.Z() ); + mesh->SetNodeInVolume( _nodes[ nodeIndex ], shapeIDVec[ nodeIndex ]); + } + else if ( _nodes[ nodeIndex ] && _gridIntP[ nodeIndex ] /*&& + !_nodes[ nodeIndex]->GetShapeID()*/ ) + { + SetOnShape( _nodes[ nodeIndex ], *_gridIntP[ nodeIndex ]); } } @@ -1174,6 +1805,17 @@ namespace _intersections.push_back( make_pair( &gridLine, intersector._intPoints[i] )); } } + + if ( _face.Orientation() == TopAbs_INTERNAL ) + { + for ( size_t i = 0; i < _intersections.size(); ++i ) + if ( _intersections[i].second._transition == Trans_IN || + _intersections[i].second._transition == Trans_OUT ) + { + _intersections[i].second._transition = Trans_INTERNAL; + } + } + return; } //================================================================================ /* @@ -1194,6 +1836,8 @@ namespace { F_IntersectPoint p; p._paramOnLine = _w; + p._u = _u; + p._v = _v; p._transition = _transition; _intPoints.push_back( p ); } @@ -1428,8 +2072,8 @@ namespace /*! * \brief Creates topology of the hexahedron */ - Hexahedron::Hexahedron(const double sizeThreshold, Grid* grid) - : _grid( grid ), _sizeThreshold( sizeThreshold ), _nbFaceIntNodes(0) + Hexahedron::Hexahedron(Grid* grid) + : _grid( grid ), _nbFaceIntNodes(0), _hasTooSmall( false ) { _polygons.reserve(100); // to avoid reallocation; @@ -1491,11 +2135,12 @@ namespace /*! * \brief Copy constructor */ - Hexahedron::Hexahedron( const Hexahedron& other ) - :_grid( other._grid ), _sizeThreshold( other._sizeThreshold ), _nbFaceIntNodes(0) + Hexahedron::Hexahedron( const Hexahedron& other, size_t i, size_t j, size_t k, int cellID ) + :_grid( other._grid ), _nbFaceIntNodes(0), _i( i ), _j( j ), _k( k ), _hasTooSmall( false ) { _polygons.reserve(100); // to avoid reallocation; + // copy topology for ( int i = 0; i < 8; ++i ) _nodeShift[i] = other._nodeShift[i]; @@ -1520,23 +2165,192 @@ namespace tgtLink._link = _hexLinks + ( srcLink._link - other._hexLinks ); } } +#ifdef _DEBUG_ + _cellID = cellID; +#endif } //================================================================================ /*! - * \brief Initializes its data by given grid cell + * \brief Return IDs of SOLIDs interfering with this Hexahedron */ - void Hexahedron::init( size_t i, size_t j, size_t k ) + vector< TGeomID > Hexahedron::getSolids() + { + // count intersection points belonging to each SOLID + TID2Nb id2NbPoints; + id2NbPoints.reserve( 3 ); + + _origNodeInd = _grid->NodeIndex( _i,_j,_k ); + for ( int iN = 0; iN < 8; ++iN ) + { + _hexNodes[iN]._node = _grid->_nodes [ _origNodeInd + _nodeShift[iN] ]; + _hexNodes[iN]._intPoint = _grid->_gridIntP[ _origNodeInd + _nodeShift[iN] ]; + + if ( _hexNodes[iN]._intPoint ) // intersection with a FACE + { + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + const vector< TGeomID > & solidIDs = + _grid->GetSolidIDs( _hexNodes[iN]._intPoint->_faceIDs[iF] ); + for ( size_t i = 0; i < solidIDs.size(); ++i ) + insertAndIncrement( solidIDs[i], id2NbPoints ); + } + } + else if ( _hexNodes[iN]._node ) // node inside a SOLID + { + insertAndIncrement( _hexNodes[iN]._node->GetShapeID(), id2NbPoints ); + } + } + + for ( int iL = 0; iL < 12; ++iL ) + { + const _Link& link = _hexLinks[ iL ]; + for ( size_t iP = 0; iP < link._fIntPoints.size(); ++iP ) + { + for ( size_t iF = 0; iF < link._fIntPoints[iP]->_faceIDs.size(); ++iF ) + { + const vector< TGeomID > & solidIDs = + _grid->GetSolidIDs( link._fIntPoints[iP]->_faceIDs[iF] ); + for ( size_t i = 0; i < solidIDs.size(); ++i ) + insertAndIncrement( solidIDs[i], id2NbPoints ); + } + } + } + + for ( size_t iP = 0; iP < _eIntPoints.size(); ++iP ) + { + const vector< TGeomID > & solidIDs = _grid->GetSolidIDs( _eIntPoints[iP]->_shapeID ); + for ( size_t i = 0; i < solidIDs.size(); ++i ) + insertAndIncrement( solidIDs[i], id2NbPoints ); + } + + vector< TGeomID > solids; solids.reserve( id2NbPoints.size() ); + for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) + if ( id2nb->second >= 3 ) + solids.push_back( id2nb->first ); + + return solids; + } + + //================================================================================ + /*! + * \brief Count cuts by INTERNAL FACEs and set _Node::_isInternalFlags + */ + bool Hexahedron::isCutByInternalFace( IsInternalFlag & maxFlag ) + { + TID2Nb id2NbPoints; + id2NbPoints.reserve( 3 ); + + for ( size_t iN = 0; iN < _intNodes.size(); ++iN ) + for ( size_t iF = 0; iF < _intNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + if ( _grid->IsInternal( _intNodes[iN]._intPoint->_faceIDs[iF])) + insertAndIncrement( _intNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + } + for ( size_t iN = 0; iN < 8; ++iN ) + if ( _hexNodes[iN]._intPoint ) + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + if ( _grid->IsInternal( _hexNodes[iN]._intPoint->_faceIDs[iF])) + insertAndIncrement( _hexNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + } + + maxFlag = IS_NOT_INTERNAL; + for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) + { + TGeomID intFace = id2nb->first; + IsInternalFlag intFlag = ( id2nb->second >= 3 ? IS_CUT_BY_INTERNAL_FACE : IS_INTERNAL ); + if ( intFlag > maxFlag ) + maxFlag = intFlag; + + for ( size_t iN = 0; iN < _intNodes.size(); ++iN ) + if ( _intNodes[iN].IsOnFace( intFace )) + _intNodes[iN].SetInternal( intFlag ); + + for ( size_t iN = 0; iN < 8; ++iN ) + if ( _hexNodes[iN].IsOnFace( intFace )) + _hexNodes[iN].SetInternal( intFlag ); + } + + return maxFlag; + } + + //================================================================================ + /*! + * \brief Return any FACE interfering with this Hexahedron + */ + TGeomID Hexahedron::getAnyFace() const + { + TID2Nb id2NbPoints; + id2NbPoints.reserve( 3 ); + + for ( size_t iN = 0; iN < _intNodes.size(); ++iN ) + for ( size_t iF = 0; iF < _intNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + insertAndIncrement( _intNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + + for ( size_t iN = 0; iN < 8; ++iN ) + if ( _hexNodes[iN]._intPoint ) + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + insertAndIncrement( _hexNodes[iN]._intPoint->_faceIDs[iF], id2NbPoints ); + + for ( unsigned int minNb = 3; minNb > 0; --minNb ) + for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) + if ( id2nb->second >= minNb ) + return id2nb->first; + + return 0; + } + + //================================================================================ + /*! + * \brief Initializes IJK by Hexahedron index + */ + void Hexahedron::setIJK( size_t iCell ) + { + size_t iNbCell = _grid->_coords[0].size() - 1; + size_t jNbCell = _grid->_coords[1].size() - 1; + _i = iCell % iNbCell; + _j = ( iCell % ( iNbCell * jNbCell )) / iNbCell; + _k = iCell / iNbCell / jNbCell; + } + + //================================================================================ + /*! + * \brief Initializes its data by given grid cell (countered from zero) + */ + void Hexahedron::init( size_t iCell ) + { + setIJK( iCell ); + init( _i, _j, _k ); + } + + //================================================================================ + /*! + * \brief Initializes its data by given grid cell nodes and intersections + */ + void Hexahedron::init( size_t i, size_t j, size_t k, const Solid* solid ) { _i = i; _j = j; _k = k; + + if ( !solid ) + solid = _grid->GetSolid(); + // set nodes of grid to nodes of the hexahedron and // count nodes at hexahedron corners located IN and ON geometry _nbCornerNodes = _nbBndNodes = 0; _origNodeInd = _grid->NodeIndex( i,j,k ); for ( int iN = 0; iN < 8; ++iN ) { + _hexNodes[iN]._isInternalFlags = 0; + _hexNodes[iN]._node = _grid->_nodes [ _origNodeInd + _nodeShift[iN] ]; _hexNodes[iN]._intPoint = _grid->_gridIntP[ _origNodeInd + _nodeShift[iN] ]; + + if ( _hexNodes[iN]._node && !solid->Contains( _hexNodes[iN]._node->GetShapeID() )) + _hexNodes[iN]._node = 0; + if ( _hexNodes[iN]._intPoint && !solid->ContainsAny( _hexNodes[iN]._intPoint->_faceIDs )) + _hexNodes[iN]._intPoint = 0; + _nbCornerNodes += bool( _hexNodes[iN]._node ); _nbBndNodes += bool( _hexNodes[iN]._intPoint ); } @@ -1547,25 +2361,28 @@ namespace _intNodes.clear(); _vIntNodes.clear(); - if ( _nbFaceIntNodes + _eIntPoints.size() > 0 && - _nbFaceIntNodes + _nbCornerNodes + _eIntPoints.size() > 3) + if ( _nbFaceIntNodes + _eIntPoints.size() > 0 && + _nbFaceIntNodes + _eIntPoints.size() + _nbCornerNodes > 3) { _intNodes.reserve( 3 * _nbBndNodes + _nbFaceIntNodes + _eIntPoints.size() ); // this method can be called in parallel, so use own helper SMESH_MesherHelper helper( *_grid->_helper->GetMesh() ); - // create sub-links (_splits) by splitting links with _fIntPoints + // Create sub-links (_Link::_splits) by splitting links with _Link::_fIntPoints + // --------------------------------------------------------------- _Link split; for ( int iLink = 0; iLink < 12; ++iLink ) { _Link& link = _hexLinks[ iLink ]; - link._fIntNodes.resize( link._fIntPoints.size() ); + link._fIntNodes.clear(); + link._fIntNodes.reserve( link._fIntPoints.size() ); for ( size_t i = 0; i < link._fIntPoints.size(); ++i ) - { - _intNodes.push_back( _Node( 0, link._fIntPoints[i] )); - link._fIntNodes[ i ] = & _intNodes.back(); - } + if ( solid->ContainsAny( link._fIntPoints[i]->_faceIDs )) + { + _intNodes.push_back( _Node( 0, link._fIntPoints[i] )); + link._fIntNodes.push_back( & _intNodes.back() ); + } link._splits.clear(); split._nodes[ 0 ] = link._nodes[0]; @@ -1590,15 +2407,21 @@ namespace } if ( checkTransition ) { - if ( link._fIntPoints[i]->_faceIDs.size() > 1 || _eIntPoints.size() > 0 ) - isOut = isOutPoint( link, i, helper ); + const vector< TGeomID >& faceIDs = link._fIntNodes[i]->_intPoint->_faceIDs; + if ( _grid->IsInternal( faceIDs.back() )) + isOut = false; + else if ( faceIDs.size() > 1 || _eIntPoints.size() > 0 ) + isOut = isOutPoint( link, i, helper, solid ); else - switch ( link._fIntPoints[i]->_transition ) { - case Trans_OUT: isOut = true; break; - case Trans_IN : isOut = false; break; + { + bool okTransi = _grid->IsCorrectTransition( faceIDs[0], solid ); + switch ( link._fIntNodes[i]->FaceIntPnt()->_transition ) { + case Trans_OUT: isOut = okTransi; break; + case Trans_IN : isOut = !okTransi; break; default: - isOut = isOutPoint( link, i, helper ); + isOut = isOutPoint( link, i, helper, solid ); } + } } } if ( link._nodes[ 1 ]->Node() && split._nodes[ 0 ]->Node() && !isOut ) @@ -1609,12 +2432,20 @@ namespace } // Create _Node's at intersections with EDGEs. - + // -------------------------------------------- + // 1) add this->_eIntPoints to _Face::_eIntNodes + // 2) fill _intNodes and _vIntNodes + // const double tol2 = _grid->_tol * _grid->_tol; int facets[3], nbFacets, subEntity; + for ( int iF = 0; iF < 6; ++iF ) + _hexQuads[ iF ]._eIntNodes.clear(); + for ( size_t iP = 0; iP < _eIntPoints.size(); ++iP ) { + if ( !solid->ContainsAny( _eIntPoints[iP]->_faceIDs )) + continue; nbFacets = getEntity( _eIntPoints[iP], facets, subEntity ); _Node* equalNode = 0; switch( nbFacets ) { @@ -1639,10 +2470,16 @@ namespace equalNode = findEqualNode( link._fIntNodes, _eIntPoints[ iP ], tol2 ); if ( equalNode ) equalNode->Add( _eIntPoints[ iP ] ); + else if ( link._splits.size() == 1 && + link._splits[0]._nodes[0] && + link._splits[0]._nodes[1] ) + link._splits.clear(); // hex edge is divided by _eIntPoints[iP] } - else + //else + if ( !equalNode ) { _intNodes.push_back( _Node( 0, _eIntPoints[ iP ])); + bool newNodeUsed = false; for ( int iF = 0; iF < 2; ++iF ) { _Face& quad = _hexQuads[ facets[iF] - SMESH_Block::ID_FirstF ]; @@ -1652,8 +2489,11 @@ namespace } else { quad._eIntNodes.push_back( & _intNodes.back() ); + newNodeUsed = true; } } + if ( !newNodeUsed ) + _intNodes.pop_back(); } break; } @@ -1685,7 +2525,7 @@ namespace } // switch( nbFacets ) if ( nbFacets == 0 || - _grid->_shapes( _eIntPoints[ iP ]->_shapeID ).ShapeType() == TopAbs_VERTEX ) + _grid->ShapeType( _eIntPoints[ iP ]->_shapeID ) == TopAbs_VERTEX ) { equalNode = findEqualNode( _vIntNodes, _eIntPoints[ iP ], tol2 ); if ( equalNode ) { @@ -1699,6 +2539,7 @@ namespace } } // loop on _eIntPoints } + else if ( 3 < _nbCornerNodes && _nbCornerNodes < 8 ) // _nbFaceIntNodes == 0 { _Link split; @@ -1715,40 +2556,76 @@ namespace } } } + return; - } - //================================================================================ - /*! - * \brief Initializes its data by given grid cell (countered from zero) - */ - void Hexahedron::init( size_t iCell ) - { - size_t iNbCell = _grid->_coords[0].size() - 1; - size_t jNbCell = _grid->_coords[1].size() - 1; - _i = iCell % iNbCell; - _j = ( iCell % ( iNbCell * jNbCell )) / iNbCell; - _k = iCell / iNbCell / jNbCell; - init( _i, _j, _k ); - } + } // init( _i, _j, _k ) //================================================================================ /*! * \brief Compute mesh volumes resulted from intersection of the Hexahedron */ - void Hexahedron::ComputeElements() + void Hexahedron::ComputeElements( const Solid* solid, int solidIndex ) { - Init(); + if ( !solid ) + { + solid = _grid->GetSolid(); + if ( !_grid->_geometry.IsOneSolid() ) + { + vector< TGeomID > solidIDs = getSolids(); + if ( solidIDs.size() > 1 ) + { + for ( size_t i = 0; i < solidIDs.size(); ++i ) + { + solid = _grid->GetSolid( solidIDs[i] ); + ComputeElements( solid, i ); + if ( !_volumeDefs._nodes.empty() && i < solidIDs.size() - 1 ) + _volumeDefs.SetNext( new _volumeDef( _volumeDefs )); + } + return; + } + solid = _grid->GetSolid( solidIDs[0] ); + } + } + + init( _i, _j, _k, solid ); // get nodes and intersections from grid nodes and split links int nbIntersections = _nbFaceIntNodes + _eIntPoints.size(); if ( _nbCornerNodes + nbIntersections < 4 ) return; if ( _nbBndNodes == _nbCornerNodes && nbIntersections == 0 && isInHole() ) - return; + return; // cell is in a hole + IsInternalFlag intFlag = IS_NOT_INTERNAL; + if ( solid->HasInternalFaces() && this->isCutByInternalFace( intFlag )) + { + for ( _SplitIterator it( _hexLinks ); it.More(); it.Next() ) + { + if ( compute( solid, intFlag )) + _volumeDefs.SetNext( new _volumeDef( _volumeDefs )); + } + } + else + { + if ( solidIndex >= 0 ) + intFlag = IS_CUT_BY_INTERNAL_FACE; + + compute( solid, intFlag ); + } + } + + //================================================================================ + /*! + * \brief Compute mesh volumes resulted from intersection of the Hexahedron + */ + bool Hexahedron::compute( const Solid* solid, const IsInternalFlag intFlag ) + { _polygons.clear(); _polygons.reserve( 20 ); + for ( int iN = 0; iN < 8; ++iN ) + _hexNodes[iN]._usedInFace = 0; + // Create polygons from quadrangles // -------------------------------- @@ -1778,14 +2655,13 @@ namespace if (( nbSplits == 1 ) && ( quad._eIntNodes.empty() || splits[0].FirstNode()->IsLinked( splits[0].LastNode()->_intPoint ))) - //( quad._eIntNodes.empty() || _nbCornerNodes + nbIntersections > 6 )) + //( quad._eIntNodes.empty() || _nbCornerNodes + nbIntersections > 6 )) nbSplits = 0; -#ifdef _DEBUG_ for ( size_t iP = 0; iP < quad._eIntNodes.size(); ++iP ) if ( quad._eIntNodes[ iP ]->IsUsedInFace( polygon )) quad._eIntNodes[ iP ]->_usedInFace = 0; -#endif + size_t nbUsedEdgeNodes = 0; _Face* prevPolyg = 0; // polygon previously created from this quad @@ -1815,16 +2691,20 @@ namespace n1 = split.FirstNode(); if ( n1 == n2 && n1->_intPoint && - n1->_intPoint->_faceIDs.size() > 1 ) + (( n1->_intPoint->_faceIDs.size() > 1 && isImplementEdges() ) || + ( n1->_isInternalFlags ))) { // n1 is at intersection with EDGE if ( findChainOnEdge( splits, polygon->_links.back(), split, iS, quad, chainNodes )) { for ( size_t i = 1; i < chainNodes.size(); ++i ) polygon->AddPolyLink( chainNodes[i-1], chainNodes[i], prevPolyg ); - prevPolyg = polygon; - n2 = chainNodes.back(); - continue; + if ( chainNodes.back() != n1 ) // not a partial cut by INTERNAL FACE + { + prevPolyg = polygon; + n2 = chainNodes.back(); + continue; + } } } else if ( n1 != n2 ) @@ -1940,7 +2820,7 @@ namespace freeLinks.push_back( & polygon._links[ iL ]); } int nbFreeLinks = freeLinks.size(); - if ( nbFreeLinks == 1 ) return; + if ( nbFreeLinks == 1 ) return false; // put not used intersection nodes to _vIntNodes int nbVertexNodes = 0; // nb not used vertex nodes @@ -2106,7 +2986,8 @@ namespace _vIntNodes[ iN ]->_usedInFace = &polygon; chainNodes.push_back( _vIntNodes[ iN ] ); } - if ( chainNodes.size() > 1 ) + if ( chainNodes.size() > 1 && + curFace != _grid->PseudoIntExtFaceID() ) /////// TODO { sortVertexNodes( chainNodes, curNode, curFace ); } @@ -2130,7 +3011,7 @@ namespace if ( polygon._links.size() < 2 || polygon._links[0].LastNode() != polygon._links.back().FirstNode() ) - return; // closed polygon not found -> invalid polyhedron + return false; // closed polygon not found -> invalid polyhedron if ( polygon._links.size() == 2 ) { @@ -2251,15 +3132,16 @@ namespace } // end of case ( polygon._links.size() > 2 ) } // while ( nbFreeLinks > 0 ) - if ( ! checkPolyhedronSize() ) - { - return; - } + // check volume size + _hasTooSmall = ! checkPolyhedronSize( intFlag & IS_CUT_BY_INTERNAL_FACE ); for ( size_t i = 0; i < 8; ++i ) if ( _hexNodes[ i ]._intPoint == &ipTmp ) _hexNodes[ i ]._intPoint = 0; + if ( _hasTooSmall ) + return false; // too small volume + // create a classic cell if possible int nbPolygons = 0; @@ -2292,6 +3174,9 @@ namespace _volumeDefs._nodes.push_back( _polygons[ iF ]._links[ iL ].FirstNode() ); } } + _volumeDefs._solidID = solid->ID(); + + return !_volumeDefs._nodes.empty(); } //================================================================================ /*! @@ -2302,23 +3187,18 @@ namespace { SMESHDS_Mesh* mesh = helper.GetMeshDS(); - size_t nbCells[3] = { _grid->_coords[0].size() - 1, - _grid->_coords[1].size() - 1, - _grid->_coords[2].size() - 1 }; - const size_t nbGridCells = nbCells[0] * nbCells[1] * nbCells[2]; + CellsAroundLink c( _grid, 0 ); + const size_t nbGridCells = c._nbCells[0] * c._nbCells[1] * c._nbCells[2]; vector< Hexahedron* > allHexa( nbGridCells, 0 ); int nbIntHex = 0; // set intersection nodes from GridLine's to links of allHexa - int i,j,k, iDirOther[3][2] = {{ 1,2 },{ 0,2 },{ 0,1 }}; + int i,j,k, cellIndex; for ( int iDir = 0; iDir < 3; ++iDir ) { - int dInd[4][3] = { {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} }; - dInd[1][ iDirOther[iDir][0] ] = -1; - dInd[2][ iDirOther[iDir][1] ] = -1; - dInd[3][ iDirOther[iDir][0] ] = -1; dInd[3][ iDirOther[iDir][1] ] = -1; // loop on GridLine's parallel to iDir LineIndexer lineInd = _grid->GetLineIndexer( iDir ); + CellsAroundLink fourCells( _grid, iDir ); for ( ; lineInd.More(); ++lineInd ) { GridLine& line = _grid->_lines[ iDir ][ lineInd.LineIndex() ]; @@ -2327,23 +3207,15 @@ namespace { // if ( !ip->_node ) continue; // intersection at a grid node lineInd.SetIndexOnLine( ip->_indexOnLine ); + fourCells.Init( lineInd.I(), lineInd.J(), lineInd.K() ); for ( int iL = 0; iL < 4; ++iL ) // loop on 4 cells sharing a link { - i = int(lineInd.I()) + dInd[iL][0]; - j = int(lineInd.J()) + dInd[iL][1]; - k = int(lineInd.K()) + dInd[iL][2]; - if ( i < 0 || i >= (int) nbCells[0] || - j < 0 || j >= (int) nbCells[1] || - k < 0 || k >= (int) nbCells[2] ) continue; - - const size_t hexIndex = _grid->CellIndex( i,j,k ); - Hexahedron *& hex = allHexa[ hexIndex ]; + if ( !fourCells.GetCell( iL, i,j,k, cellIndex )) + continue; + Hexahedron *& hex = allHexa[ cellIndex ]; if ( !hex) { - hex = new Hexahedron( *this ); - hex->_i = i; - hex->_j = j; - hex->_k = k; + hex = new Hexahedron( *this, i, j, k, cellIndex ); ++nbIntHex; } const int iLink = iL + iDir * 4; @@ -2357,22 +3229,24 @@ namespace // implement geom edges into the mesh addEdges( helper, allHexa, edge2faceIDsMap ); - // add not split hexadrons to the mesh + // add not split hexahedra to the mesh int nbAdded = 0; - vector< Hexahedron* > intHexa( nbIntHex, (Hexahedron*) NULL ); + vector< Hexahedron* > intHexa; intHexa.reserve( nbIntHex ); + vector< const SMDS_MeshElement* > boundaryVolumes; boundaryVolumes.reserve( nbIntHex * 1.1 ); for ( size_t i = 0; i < allHexa.size(); ++i ) { + // initialize this by not cut allHexa[ i ] Hexahedron * & hex = allHexa[ i ]; - if ( hex ) + if ( hex ) // split hexahedron { intHexa.push_back( hex ); if ( hex->_nbFaceIntNodes > 0 || hex->_eIntPoints.size() > 0 ) - continue; // treat intersected hex later + continue; // treat intersected hex later in parallel this->init( hex->_i, hex->_j, hex->_k ); } else - { - this->init( i ); + { + this->init( i ); // == init(i,j,k) } if (( _nbCornerNodes == 8 ) && ( _nbBndNodes < _nbCornerNodes || !isInHole() )) @@ -2383,18 +3257,31 @@ namespace _hexNodes[3].Node(), _hexNodes[1].Node(), _hexNodes[4].Node(), _hexNodes[6].Node(), _hexNodes[7].Node(), _hexNodes[5].Node() ); - mesh->SetMeshElementOnShape( el, helper.GetSubShapeID() ); + TGeomID solidID = 0; + if ( _nbBndNodes < _nbCornerNodes ) + { + for ( int iN = 0; iN < 8 && !solidID; ++iN ) + if ( !_hexNodes[iN]._intPoint ) // no intersection + solidID = _hexNodes[iN].Node()->GetShapeID(); + } + else + { + solidID = getSolids()[0]; + } + mesh->SetMeshElementOnShape( el, solidID ); ++nbAdded; if ( hex ) intHexa.pop_back(); + if ( _grid->_toCreateFaces && _nbBndNodes >= 3 ) + { + boundaryVolumes.push_back( el ); + el->setIsMarked( true ); + } } - else if ( _nbCornerNodes > 3 && !hex ) + else if ( _nbCornerNodes > 3 && !hex ) { // all intersection of hex with geometry are at grid nodes - hex = new Hexahedron( *this ); - hex->_i = _i; - hex->_j = _j; - hex->_k = _k; + hex = new Hexahedron( *this, _i, _j, _k, i ); intHexa.push_back( hex ); } } @@ -2406,16 +3293,30 @@ namespace tbb::simple_partitioner()); // ComputeElements() is called here for ( size_t i = 0; i < intHexa.size(); ++i ) if ( Hexahedron * hex = intHexa[ i ] ) - nbAdded += hex->addElements( helper ); + nbAdded += hex->addVolumes( helper ); #else for ( size_t i = 0; i < intHexa.size(); ++i ) if ( Hexahedron * hex = intHexa[ i ] ) { hex->ComputeElements(); - nbAdded += hex->addElements( helper ); + nbAdded += hex->addVolumes( helper ); } #endif + // fill boundaryVolumes with volumes neighboring too small skipped volumes + if ( _grid->_toCreateFaces ) + { + for ( size_t i = 0; i < intHexa.size(); ++i ) + if ( Hexahedron * hex = intHexa[ i ] ) + hex->getBoundaryElems( boundaryVolumes ); + } + + // create boundary mesh faces + addFaces( helper, boundaryVolumes ); + + // create mesh edges + addSegments( helper, edge2faceIDsMap ); + for ( size_t i = 0; i < allHexa.size(); ++i ) if ( allHexa[ i ] ) delete allHexa[ i ]; @@ -2456,25 +3357,36 @@ namespace const double tol = _grid->_tol; E_IntersectPoint ip; + TColStd_MapOfInteger intEdgeIDs; // IDs of not shared INTERNAL EDGES + // Intersect EDGEs with the planes map< TGeomID, vector< TGeomID > >::const_iterator e2fIt = edge2faceIDsMap.begin(); for ( ; e2fIt != edge2faceIDsMap.end(); ++e2fIt ) { const TGeomID edgeID = e2fIt->first; - const TopoDS_Edge & E = TopoDS::Edge( _grid->_shapes( edgeID )); + const TopoDS_Edge & E = TopoDS::Edge( _grid->Shape( edgeID )); BRepAdaptor_Curve curve( E ); - TopoDS_Vertex v1 = helper.IthVertex( 0, E, false ); - TopoDS_Vertex v2 = helper.IthVertex( 1, E, false ); + TopoDS_Vertex v1 = helper.IthVertex( 0, E, false ); + TopoDS_Vertex v2 = helper.IthVertex( 1, E, false ); ip._faceIDs = e2fIt->second; ip._shapeID = edgeID; + bool isInternal = ( ip._faceIDs.size() == 1 && _grid->IsInternal( edgeID )); + if ( isInternal ) + { + intEdgeIDs.Add( edgeID ); + intEdgeIDs.Add( _grid->ShapeID( v1 )); + intEdgeIDs.Add( _grid->ShapeID( v2 )); + } + // discretize the EDGE GCPnts_UniformDeflection discret( curve, deflection, true ); if ( !discret.IsDone() || discret.NbPoints() < 2 ) continue; // perform intersection + E_IntersectPoint* eip, *vip; for ( int iDirZ = 0; iDirZ < 3; ++iDirZ ) { GridPlanes& planes = pln[ iDirZ ]; @@ -2501,7 +3413,7 @@ namespace locateValue( iY1, ip._uvw[iDirY], _grid->_coords[ iDirY ], dIJK[ iDirY ], tol ); locateValue( iZ1, ip._uvw[iDirZ], _grid->_coords[ iDirZ ], dIJK[ iDirZ ], tol ); - int ijk[3]; // grid index where a segment intersect a plane + int ijk[3]; // grid index where a segment intersects a plane ijk[ iDirX ] = iX1; ijk[ iDirY ] = iY1; ijk[ iDirZ ] = iZ1; @@ -2510,10 +3422,12 @@ namespace if ( iDirZ == 0 ) { ip._point = p1; - ip._shapeID = _grid->_shapes.Add( v1 ); - _grid->_edgeIntP.push_back( ip ); - if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, d000 )) - _grid->_edgeIntP.pop_back(); + ip._shapeID = _grid->ShapeID( v1 ); + vip = _grid->Add( ip ); + if ( isInternal ) + vip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + if ( !addIntersection( vip, hexes, ijk, d000 )) + _grid->Remove( vip ); ip._shapeID = edgeID; } for ( int iP = 2; iP <= discret.NbPoints(); ++iP ) @@ -2541,15 +3455,17 @@ namespace ijk[ iDirZ ] = iZ; // add ip to hex "above" the plane - _grid->_edgeIntP.push_back( ip ); + eip = _grid->Add( ip ); + if ( isInternal ) + eip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); dIJK[ iDirZ ] = 0; - bool added = addIntersection(_grid->_edgeIntP.back(), hexes, ijk, dIJK); + bool added = addIntersection( eip, hexes, ijk, dIJK); // add ip to hex "below" the plane ijk[ iDirZ ] = iZ-1; - if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, dIJK ) && - !added) - _grid->_edgeIntP.pop_back(); + if ( !addIntersection( eip, hexes, ijk, dIJK ) && + !added ) + _grid->Remove( eip ); } } iZ1 = iZ2; @@ -2560,20 +3476,250 @@ namespace // add the 2nd vertex point to a hexahedron if ( iDirZ == 0 ) { - ip._shapeID = _grid->_shapes.Add( v2 ); - ip._point = p1; + ip._point = p1; + ip._shapeID = _grid->ShapeID( v2 ); _grid->ComputeUVW( p1, ip._uvw ); locateValue( ijk[iDirX], ip._uvw[iDirX], _grid->_coords[iDirX], dIJK[iDirX], tol ); locateValue( ijk[iDirY], ip._uvw[iDirY], _grid->_coords[iDirY], dIJK[iDirY], tol ); ijk[ iDirZ ] = iZ1; - _grid->_edgeIntP.push_back( ip ); - if ( !addIntersection( _grid->_edgeIntP.back(), hexes, ijk, d000 )) - _grid->_edgeIntP.pop_back(); + bool sameV = ( v1.IsSame( v2 )); + if ( !sameV ) + vip = _grid->Add( ip ); + if ( isInternal && !sameV ) + vip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + if ( !addIntersection( vip, hexes, ijk, d000 ) && !sameV ) + _grid->Remove( vip ); ip._shapeID = edgeID; } } // loop on 3 grid directions } // loop on EDGEs + + if ( intEdgeIDs.Size() > 0 ) + cutByExtendedInternal( hexes, intEdgeIDs ); + + return; + } + + //================================================================================ + /*! + * \brief Fully cut hexes that are partially cut by INTERNAL FACE. + * Cut them by extended INTERNAL FACE. + */ + void Hexahedron::cutByExtendedInternal( std::vector< Hexahedron* >& hexes, + const TColStd_MapOfInteger& intEdgeIDs ) + { + IntAna_IntConicQuad intersection; + SMESHDS_Mesh* meshDS = _grid->_helper->GetMeshDS(); + const double tol2 = _grid->_tol * _grid->_tol; + + for ( size_t iH = 0; iH < hexes.size(); ++iH ) + { + Hexahedron* hex = hexes[ iH ]; + if ( !hex || hex->_eIntPoints.size() < 2 ) + continue; + if ( !intEdgeIDs.Contains( hex->_eIntPoints.back()->_shapeID )) + continue; + + // get 3 points on INTERNAL FACE to construct a cutting plane + gp_Pnt p1 = hex->_eIntPoints[0]->_point; + gp_Pnt p2 = hex->_eIntPoints[1]->_point; + gp_Pnt p3 = hex->mostDistantInternalPnt( iH, p1, p2 ); + + gp_Vec norm = gp_Vec( p1, p2 ) ^ gp_Vec( p1, p3 ); + gp_Pln pln; + try { + pln = gp_Pln( p1, norm ); + } + catch(...) + { + continue; + } + + TGeomID intFaceID = hex->_eIntPoints.back()->_faceIDs.front(); // FACE being "extended" + TGeomID solidID = _grid->GetSolid( intFaceID )->ID(); + + // cut links by the plane + //bool isCut = false; + for ( int iLink = 0; iLink < 12; ++iLink ) + { + _Link& link = hex->_hexLinks[ iLink ]; + if ( !link._fIntPoints.empty() ) + { + // if ( link._fIntPoints[0]->_faceIDs.back() == _grid->PseudoIntExtFaceID() ) + // isCut = true; + continue; // already cut link + } + if ( !link._nodes[0]->Node() || + !link._nodes[1]->Node() ) + continue; // outside link + + if ( link._nodes[0]->IsOnFace( intFaceID )) + { + if ( link._nodes[0]->_intPoint->_faceIDs.back() != _grid->PseudoIntExtFaceID() ) + if ( p1.SquareDistance( link._nodes[0]->Point() ) < tol2 || + p2.SquareDistance( link._nodes[0]->Point() ) < tol2 ) + link._nodes[0]->_intPoint->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + continue; // link is cut by FACE being "extended" + } + if ( link._nodes[1]->IsOnFace( intFaceID )) + { + if ( link._nodes[1]->_intPoint->_faceIDs.back() != _grid->PseudoIntExtFaceID() ) + if ( p1.SquareDistance( link._nodes[1]->Point() ) < tol2 || + p2.SquareDistance( link._nodes[1]->Point() ) < tol2 ) + link._nodes[1]->_intPoint->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + continue; // link is cut by FACE being "extended" + } + gp_Pnt p4 = link._nodes[0]->Point(); + gp_Pnt p5 = link._nodes[1]->Point(); + gp_Lin line( p4, gp_Vec( p4, p5 )); + + intersection.Perform( line, pln ); + if ( !intersection.IsDone() || + intersection.IsInQuadric() || + intersection.IsParallel() || + intersection.NbPoints() < 1 ) + continue; + + double u = intersection.ParamOnConic(1); + if ( u + _grid->_tol < 0 ) + continue; + int iDir = iLink / 4; + int index = (&hex->_i)[iDir]; + double linkLen = _grid->_coords[iDir][index+1] - _grid->_coords[iDir][index]; + if ( u - _grid->_tol > linkLen ) + continue; + + if ( u < _grid->_tol || + u > linkLen - _grid->_tol ) // intersection at grid node + { + int i = ! ( u < _grid->_tol ); // [0,1] + int iN = link._nodes[ i ] - hex->_hexNodes; // [0-7] + + const F_IntersectPoint * & ip = _grid->_gridIntP[ hex->_origNodeInd + _nodeShift[iN] ]; + if ( !ip ) + { + ip = _grid->_extIntPool.getNew(); + ip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + //ip->_transition = Trans_INTERNAL; + } + else if ( ip->_faceIDs.back() != _grid->PseudoIntExtFaceID() ) + { + ip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + } + hex->_nbFaceIntNodes++; + //isCut = true; + } + else + { + const gp_Pnt& p = intersection.Point( 1 ); + F_IntersectPoint* ip = _grid->_extIntPool.getNew(); + ip->_node = meshDS->AddNode( p.X(), p.Y(), p.Z() ); + ip->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + ip->_transition = Trans_INTERNAL; + meshDS->SetNodeInVolume( ip->_node, solidID ); + + CellsAroundLink fourCells( _grid, iDir ); + fourCells.Init( hex->_i, hex->_j, hex->_k, iLink ); + int i,j,k, cellIndex; + for ( int iC = 0; iC < 4; ++iC ) // loop on 4 cells sharing the link + { + if ( !fourCells.GetCell( iC, i,j,k, cellIndex )) + continue; + Hexahedron * h = hexes[ cellIndex ]; + if ( !h ) + h = hexes[ cellIndex ] = new Hexahedron( *this, i, j, k, cellIndex ); + const int iL = iC + iDir * 4; + h->_hexLinks[iL]._fIntPoints.push_back( ip ); + h->_nbFaceIntNodes++; + //isCut = true; + } + } + } + + // if ( isCut ) + // for ( size_t i = 0; i < hex->_eIntPoints.size(); ++i ) + // { + // if ( _grid->IsInternal( hex->_eIntPoints[i]->_shapeID ) && + // ! hex->_eIntPoints[i]->IsOnFace( _grid->PseudoIntExtFaceID() )) + // hex->_eIntPoints[i]->_faceIDs.push_back( _grid->PseudoIntExtFaceID() ); + // } + continue; + + } // loop on all hexes + return; + } + + //================================================================================ + /*! + * \brief Return intersection point on INTERNAL FACE most distant from given ones + */ + gp_Pnt Hexahedron::mostDistantInternalPnt( int hexIndex, const gp_Pnt& p1, const gp_Pnt& p2 ) + { + gp_Pnt resultPnt = p1; + + double maxDist2 = 0; + for ( int iLink = 0; iLink < 12; ++iLink ) // check links + { + _Link& link = _hexLinks[ iLink ]; + for ( size_t i = 0; i < link._fIntPoints.size(); ++i ) + if ( _grid->PseudoIntExtFaceID() != link._fIntPoints[i]->_faceIDs[0] && + _grid->IsInternal( link._fIntPoints[i]->_faceIDs[0] ) && + link._fIntPoints[i]->_node ) + { + gp_Pnt p = SMESH_NodeXYZ( link._fIntPoints[i]->_node ); + double d = p1.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + else + { + d = p2.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + } + } + } + setIJK( hexIndex ); + _origNodeInd = _grid->NodeIndex( _i,_j,_k ); + + for ( size_t iN = 0; iN < 8; ++iN ) // check corners + { + _hexNodes[iN]._node = _grid->_nodes [ _origNodeInd + _nodeShift[iN] ]; + _hexNodes[iN]._intPoint = _grid->_gridIntP[ _origNodeInd + _nodeShift[iN] ]; + if ( _hexNodes[iN]._intPoint ) + for ( size_t iF = 0; iF < _hexNodes[iN]._intPoint->_faceIDs.size(); ++iF ) + { + if ( _grid->IsInternal( _hexNodes[iN]._intPoint->_faceIDs[iF])) + { + gp_Pnt p = SMESH_NodeXYZ( _hexNodes[iN]._node ); + double d = p1.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + else + { + d = p2.SquareDistance( p ); + if ( d > maxDist2 ) + { + resultPnt = p; + maxDist2 = d; + } + } + } + } + } + if ( maxDist2 < _grid->_tol * _grid->_tol ) + return p1; + + return resultPnt; } //================================================================================ @@ -2683,7 +3829,7 @@ namespace /*! * \brief Adds intersection with an EDGE */ - bool Hexahedron::addIntersection( const E_IntersectPoint& ip, + bool Hexahedron::addIntersection( const E_IntersectPoint* ip, vector< Hexahedron* >& hexes, int ijk[], int dIJK[] ) { @@ -2697,16 +3843,17 @@ namespace }; for ( int i = 0; i < 4; ++i ) { - if ( /*0 <= hexIndex[i] &&*/ hexIndex[i] < hexes.size() && hexes[ hexIndex[i] ] ) + if ( hexIndex[i] < hexes.size() && hexes[ hexIndex[i] ] ) { Hexahedron* h = hexes[ hexIndex[i] ]; - // check if ip is really inside the hex + h->_eIntPoints.reserve(2); + h->_eIntPoints.push_back( ip ); + added = true; #ifdef _DEBUG_ - if ( h->isOutParam( ip._uvw )) + // check if ip is really inside the hex + if ( h->isOutParam( ip->_uvw )) throw SALOME_Exception("ip outside a hex"); #endif - h->_eIntPoints.push_back( & ip ); - added = true; } } return added; @@ -2798,8 +3945,10 @@ namespace /*! * \brief Finds nodes on the same EDGE as the first node of avoidSplit. * - * This function is for a case where an EDGE lies on a quad which lies on a FACE - * so that a part of quad in ON and another part in IN + * This function is for + * 1) a case where an EDGE lies on a quad which lies on a FACE + * so that a part of quad in ON and another part is IN + * 2) INTERNAL FACE passes through the 1st node of avoidSplit */ bool Hexahedron::findChainOnEdge( const vector< _OrientedLink >& splits, const _OrientedLink& prevSplit, @@ -2808,19 +3957,16 @@ namespace _Face& quad, vector<_Node*>& chn ) { - if ( !isImplementEdges() ) - return false; - _Node* pn1 = prevSplit.FirstNode(); _Node* pn2 = prevSplit.LastNode(); int avoidFace = pn1->IsLinked( pn2->_intPoint ); // FACE under the quad if ( avoidFace < 1 && pn1->_intPoint ) return false; - _Node* n, *stopNode = avoidSplit.LastNode(); + _Node* n = 0, *stopNode = avoidSplit.LastNode(); chn.clear(); - if ( !quad._eIntNodes.empty() ) + if ( !quad._eIntNodes.empty() ) // connect pn2 with EDGE intersections { chn.push_back( pn2 ); bool found; @@ -2841,7 +3987,7 @@ namespace } int i; - for ( i = splits.size()-1; i >= 0; --i ) + for ( i = splits.size()-1; i >= 0; --i ) // connect new pn2 (at _eIntNodes) with a split { if ( !splits[i] ) continue; @@ -2862,7 +4008,7 @@ namespace break; n = 0; } - if ( n && n != stopNode) + if ( n && n != stopNode ) { if ( chn.empty() ) chn.push_back( pn2 ); @@ -2870,17 +4016,29 @@ namespace iS = i-1; return true; } + else if ( !chn.empty() && chn.back()->_isInternalFlags ) + { + // INTERNAL FACE partially cuts the quad + for ( int i = chn.size() - 2; i >= 0; --i ) + chn.push_back( chn[ i ]); + return true; + } return false; } //================================================================================ /*! * \brief Checks transition at the ginen intersection node of a link */ - bool Hexahedron::isOutPoint( _Link& link, int iP, SMESH_MesherHelper& helper ) const + bool Hexahedron::isOutPoint( _Link& link, int iP, + SMESH_MesherHelper& helper, const Solid* solid ) const { bool isOut = false; - const bool moreIntPoints = ( iP+1 < (int) link._fIntPoints.size() ); + if ( link._fIntNodes[iP]->faces().size() == 1 && + _grid->IsInternal( link._fIntNodes[iP]->face(0) )) + return false; + + const bool moreIntPoints = ( iP+1 < (int) link._fIntNodes.size() ); // get 2 _Node's _Node* n1 = link._fIntNodes[ iP ]; @@ -2894,16 +4052,16 @@ namespace // get all FACEs under n1 and n2 set< TGeomID > faceIDs; - if ( moreIntPoints ) faceIDs.insert( link._fIntPoints[iP+1]->_faceIDs.begin(), - link._fIntPoints[iP+1]->_faceIDs.end() ); + if ( moreIntPoints ) faceIDs.insert( link._fIntNodes[iP+1]->faces().begin(), + link._fIntNodes[iP+1]->faces().end() ); if ( n2->_intPoint ) faceIDs.insert( n2->_intPoint->_faceIDs.begin(), n2->_intPoint->_faceIDs.end() ); if ( faceIDs.empty() ) return false; // n2 is inside if ( n1->_intPoint ) faceIDs.insert( n1->_intPoint->_faceIDs.begin(), n1->_intPoint->_faceIDs.end() ); - faceIDs.insert( link._fIntPoints[iP]->_faceIDs.begin(), - link._fIntPoints[iP]->_faceIDs.end() ); + faceIDs.insert( link._fIntNodes[iP]->faces().begin(), + link._fIntNodes[iP]->faces().end() ); // get a point between 2 nodes gp_Pnt p1 = n1->Point(); @@ -2916,10 +4074,9 @@ namespace for ( ; faceID != faceIDs.end(); ++faceID ) { // project pOnLink on a FACE - if ( *faceID < 1 ) continue; - const TopoDS_Face& face = TopoDS::Face( _grid->_shapes( *faceID )); - GeomAPI_ProjectPointOnSurf& proj = - helper.GetProjector( face, loc, 0.1*_grid->_tol ); + if ( *faceID < 1 || !solid->Contains( *faceID )) continue; + const TopoDS_Face& face = TopoDS::Face( _grid->Shape( *faceID )); + GeomAPI_ProjectPointOnSurf& proj = helper.GetProjector( face, loc, 0.1*_grid->_tol ); gp_Pnt testPnt = pOnLink.Transformed( loc.Transformation().Inverted() ); proj.Perform( testPnt ); if ( proj.IsDone() && proj.NbPoints() > 0 ) @@ -2940,7 +4097,7 @@ namespace 0.1*_grid->_tol, normal ) < 3 ) { - if ( face.Orientation() == TopAbs_REVERSED ) + if ( solid->Orientation( face ) == TopAbs_REVERSED ) normal.Reverse(); gp_Vec v( proj.NearestPoint(), testPnt ); isOut = ( v * normal > 0 ); @@ -2980,7 +4137,7 @@ namespace return; // get shapes of the FACE - const TopoDS_Face& face = TopoDS::Face( _grid->_shapes( faceID )); + const TopoDS_Face& face = TopoDS::Face( _grid->Shape( faceID )); list< TopoDS_Edge > edges; list< int > nbEdges; int nbW = SMESH_Block::GetOrderedEdges (face, edges, nbEdges); @@ -2995,8 +4152,8 @@ namespace for ( int i = 0; i < 2; ++i ) { TGeomID id = i==0 ? - _grid->_shapes.FindIndex( *e ) : - _grid->_shapes.FindIndex( SMESH_MesherHelper::IthVertex( 0, *e )); + _grid->ShapeID( *e ) : + _grid->ShapeID( SMESH_MesherHelper::IthVertex( 0, *e )); if (( id > 0 ) && ( std::find( &nShapeIds[0], nShapeIdsEnd, id ) != nShapeIdsEnd )) { @@ -3015,7 +4172,7 @@ namespace list< TopoDS_Edge >::iterator e = edges.begin(), eMidOut = edges.end(); for ( ; e != edges.end(); ++e ) { - if ( !_grid->_shapes.FindIndex( *e )) + if ( !_grid->ShapeID( *e )) continue; bool isOut = false; gp_Pnt p; @@ -3063,14 +4220,14 @@ namespace TGeomID id, *pID = 0; for ( e = edges.begin(); e != edges.end(); ++e ) { - if (( id = _grid->_shapes.FindIndex( SMESH_MesherHelper::IthVertex( 0, *e ))) && + if (( id = _grid->ShapeID( SMESH_MesherHelper::IthVertex( 0, *e ))) && (( pID = std::find( &nShapeIds[0], nShapeIdsEnd, id )) != nShapeIdsEnd )) { //orderShapeIDs[ nbN ] = id; orderNodes [ nbN++ ] = nodes[ pID - &nShapeIds[0] ]; *pID = -1; } - if (( id = _grid->_shapes.FindIndex( *e )) && + if (( id = _grid->ShapeID( *e )) && (( pID = std::find( &nShapeIds[0], nShapeIdsEnd, id )) != nShapeIdsEnd )) { //orderShapeIDs[ nbN ] = id; @@ -3092,46 +4249,81 @@ namespace /*! * \brief Adds computed elements to the mesh */ - int Hexahedron::addElements(SMESH_MesherHelper& helper) + int Hexahedron::addVolumes( SMESH_MesherHelper& helper ) { + F_IntersectPoint noIntPnt; + const bool toCheckNodePos = _grid->IsToCheckNodePos(); + int nbAdded = 0; // add elements resulted from hexahedron intersection - //for ( size_t i = 0; i < _volumeDefs.size(); ++i ) + for ( _volumeDef* volDef = &_volumeDefs; volDef; volDef = volDef->_next ) { - vector< const SMDS_MeshNode* > nodes( _volumeDefs._nodes.size() ); + vector< const SMDS_MeshNode* > nodes( volDef->_nodes.size() ); for ( size_t iN = 0; iN < nodes.size(); ++iN ) - if ( !( nodes[iN] = _volumeDefs._nodes[iN]->Node() )) + { + if ( !( nodes[iN] = volDef->_nodes[iN].Node() )) { - if ( const E_IntersectPoint* eip = _volumeDefs._nodes[iN]->EdgeIntPnt() ) - nodes[iN] = _volumeDefs._nodes[iN]->_intPoint->_node = + if ( const E_IntersectPoint* eip = volDef->_nodes[iN].EdgeIntPnt() ) + { + nodes[iN] = volDef->_nodes[iN]._intPoint->_node = helper.AddNode( eip->_point.X(), eip->_point.Y(), eip->_point.Z() ); + if ( _grid->ShapeType( eip->_shapeID ) == TopAbs_VERTEX ) + helper.GetMeshDS()->SetNodeOnVertex( nodes[iN], eip->_shapeID ); + else + helper.GetMeshDS()->SetNodeOnEdge( nodes[iN], eip->_shapeID ); + } else throw SALOME_Exception("Bug: no node at intersection point"); } + else if ( volDef->_nodes[iN]._intPoint && + volDef->_nodes[iN]._intPoint->_node == volDef->_nodes[iN]._node ) + { + // Update position of node at EDGE intersection; + // see comment to _Node::Add( E_IntersectPoint ) + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + TGeomID shapeID = volDef->_nodes[iN].EdgeIntPnt()->_shapeID; + mesh->UnSetNodeOnShape( nodes[iN] ); + if ( _grid->ShapeType( shapeID ) == TopAbs_VERTEX ) + mesh->SetNodeOnVertex( nodes[iN], shapeID ); + else + mesh->SetNodeOnEdge( nodes[iN], shapeID ); + } + else if ( toCheckNodePos && + !nodes[iN]->isMarked() && + _grid->ShapeType( nodes[iN]->GetShapeID() ) == TopAbs_FACE ) + { + _grid->SetOnShape( nodes[iN], noIntPnt, /*unset=*/true ); + nodes[iN]->setIsMarked( true ); + } + } - if ( !_volumeDefs._quantities.empty() ) + const SMDS_MeshElement* v = 0; + if ( !volDef->_quantities.empty() ) { - helper.AddPolyhedralVolume( nodes, _volumeDefs._quantities ); + v = helper.AddPolyhedralVolume( nodes, volDef->_quantities ); } else { switch ( nodes.size() ) { - case 8: helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3], - nodes[4],nodes[5],nodes[6],nodes[7] ); + case 8: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3], + nodes[4],nodes[5],nodes[6],nodes[7] ); break; - case 4: helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3] ); + case 4: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3] ); break; - case 6: helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3], nodes[4],nodes[5] ); + case 6: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3],nodes[4],nodes[5] ); break; - case 5: - helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3],nodes[4] ); + case 5: v = helper.AddVolume( nodes[0],nodes[1],nodes[2],nodes[3],nodes[4] ); break; } } - nbAdded += int ( _volumeDefs._nodes.size() > 0 ); + if (( volDef->_volume = v )) + { + helper.GetMeshDS()->SetMeshElementOnShape( v, volDef->_solidID ); + ++nbAdded; + } } return nbAdded; @@ -3182,7 +4374,8 @@ namespace if ( firstIntPnt ) { hasLinks = true; - allLinksOut = ( firstIntPnt->_transition == Trans_OUT ); + allLinksOut = ( firstIntPnt->_transition == Trans_OUT && + !_grid->IsShared( firstIntPnt->_faceIDs[0] )); } } if ( hasLinks && allLinksOut ) @@ -3191,12 +4384,68 @@ namespace return false; } + //================================================================================ + /*! + * \brief Check if a polyherdon has an edge lying on EDGE shared by strange FACE + * that will be meshed by other algo + */ + bool Hexahedron::hasStrangeEdge() const + { + if ( _eIntPoints.size() < 2 ) + return false; + + TopTools_MapOfShape edges; + for ( size_t i = 0; i < _eIntPoints.size(); ++i ) + { + if ( !_grid->IsStrangeEdge( _eIntPoints[i]->_shapeID )) + continue; + const TopoDS_Shape& s = _grid->Shape( _eIntPoints[i]->_shapeID ); + if ( s.ShapeType() == TopAbs_EDGE ) + { + if ( ! edges.Add( s )) + return true; // an EDGE encounters twice + } + else + { + PShapeIteratorPtr edgeIt = _grid->_helper->GetAncestors( s, + *_grid->_helper->GetMesh(), + TopAbs_EDGE ); + while ( const TopoDS_Shape* edge = edgeIt->next() ) + if ( ! edges.Add( *edge )) + return true; // an EDGE encounters twice + } + } + return false; + } + //================================================================================ /*! * \brief Return true if a polyhedron passes _sizeThreshold criterion */ - bool Hexahedron::checkPolyhedronSize() const + bool Hexahedron::checkPolyhedronSize( bool cutByInternalFace ) const { + if ( cutByInternalFace && !_grid->_toUseThresholdForInternalFaces ) + { + // check if any polygon fully lies on shared/internal FACEs + for ( size_t iP = 0; iP < _polygons.size(); ++iP ) + { + const _Face& polygon = _polygons[iP]; + if ( polygon._links.empty() ) + continue; + bool allNodesInternal = true; + for ( size_t iL = 0; iL < polygon._links.size() && allNodesInternal; ++iL ) + { + _Node* n = polygon._links[ iL ].FirstNode(); + allNodesInternal = (( n->IsCutByInternal() ) || + ( n->_intPoint && _grid->IsAnyShared( n->_intPoint->_faceIDs ))); + } + if ( allNodesInternal ) + return true; + } + } + if ( this->hasStrangeEdge() ) + return true; + double volume = 0; for ( size_t iP = 0; iP < _polygons.size(); ++iP ) { @@ -3217,7 +4466,7 @@ namespace double initVolume = _sideLength[0] * _sideLength[1] * _sideLength[2]; - return volume > initVolume / _sizeThreshold; + return volume > initVolume / _grid->_sizeThreshold; } //================================================================================ /*! @@ -3263,7 +4512,7 @@ namespace } } if ( nbN == 8 ) - _volumeDefs.set( &nodes[0], 8 ); + _volumeDefs.Set( &nodes[0], 8 ); return nbN == 8; } @@ -3295,7 +4544,7 @@ namespace if ( tria->_links[i]._link == link ) { nodes[3] = tria->_links[(i+1)%3].LastNode(); - _volumeDefs.set( &nodes[0], 4 ); + _volumeDefs.Set( &nodes[0], 4 ); return true; } @@ -3340,7 +4589,7 @@ namespace } } if ( nbN == 6 ) - _volumeDefs.set( &nodes[0], 6 ); + _volumeDefs.Set( &nodes[0], 6 ); return ( nbN == 6 ); } @@ -3375,7 +4624,7 @@ namespace if ( tria->_links[i]._link == link ) { nodes[4] = tria->_links[(i+1)%3].LastNode(); - _volumeDefs.set( &nodes[0], 5 ); + _volumeDefs.Set( &nodes[0], 5 ); return true; } @@ -3408,6 +4657,467 @@ namespace ( _grid->_coords[2][ _k ] - _grid->_tol > uvw[2] ) || ( _grid->_coords[2][ _k+1 ] + _grid->_tol < uvw[2] )); } + //================================================================================ + /*! + * \brief Divide a polygon into triangles and modify accordingly an adjacent polyhedron + */ + void splitPolygon( const SMDS_MeshElement* polygon, + SMDS_VolumeTool & volume, + const int facetIndex, + const TGeomID faceID, + const TGeomID solidID, + SMESH_MeshEditor::ElemFeatures& face, + SMESH_MeshEditor& editor, + const bool reinitVolume) + { + SMESH_MeshAlgos::Triangulate divider(/*optimize=*/false); + int nbTrias = divider.GetTriangles( polygon, face.myNodes ); + face.myNodes.resize( nbTrias * 3 ); + + SMESH_MeshEditor::ElemFeatures newVolumeDef; + newVolumeDef.Init( volume.Element() ); + newVolumeDef.SetID( volume.Element()->GetID() ); + + newVolumeDef.myPolyhedQuantities.reserve( volume.NbFaces() + nbTrias ); + newVolumeDef.myNodes.reserve( volume.NbNodes() + nbTrias * 3 ); + + SMESHDS_Mesh* meshDS = editor.GetMeshDS(); + SMDS_MeshElement* newTriangle; + for ( int iF = 0, nF = volume.NbFaces(); iF < nF; iF++ ) + { + if ( iF == facetIndex ) + { + newVolumeDef.myPolyhedQuantities.push_back( 3 ); + newVolumeDef.myNodes.insert( newVolumeDef.myNodes.end(), + face.myNodes.begin(), + face.myNodes.begin() + 3 ); + meshDS->RemoveFreeElement( polygon, 0, false ); + newTriangle = meshDS->AddFace( face.myNodes[0], face.myNodes[1], face.myNodes[2] ); + meshDS->SetMeshElementOnShape( newTriangle, faceID ); + } + else + { + const SMDS_MeshNode** nn = volume.GetFaceNodes( iF ); + const size_t nbFaceNodes = volume.NbFaceNodes ( iF ); + newVolumeDef.myPolyhedQuantities.push_back( nbFaceNodes ); + newVolumeDef.myNodes.insert( newVolumeDef.myNodes.end(), nn, nn + nbFaceNodes ); + } + } + + for ( size_t iN = 3; iN < face.myNodes.size(); iN += 3 ) + { + newVolumeDef.myPolyhedQuantities.push_back( 3 ); + newVolumeDef.myNodes.insert( newVolumeDef.myNodes.end(), + face.myNodes.begin() + iN, + face.myNodes.begin() + iN + 3 ); + newTriangle = meshDS->AddFace( face.myNodes[iN], face.myNodes[iN+1], face.myNodes[iN+2] ); + meshDS->SetMeshElementOnShape( newTriangle, faceID ); + } + + meshDS->RemoveFreeElement( volume.Element(), 0, false ); + SMDS_MeshElement* newVolume = editor.AddElement( newVolumeDef.myNodes, newVolumeDef ); + meshDS->SetMeshElementOnShape( newVolume, solidID ); + + if ( reinitVolume ) + { + volume.Set( 0 ); + volume.Set( newVolume ); + } + return; + } + //================================================================================ + /*! + * \brief Create mesh faces at free facets + */ + void Hexahedron::addFaces( SMESH_MesherHelper& helper, + const vector< const SMDS_MeshElement* > & boundaryVolumes ) + { + if ( !_grid->_toCreateFaces ) + return; + + SMDS_VolumeTool vTool; + vector bndFacets; + SMESH_MeshEditor editor( helper.GetMesh() ); + SMESH_MeshEditor::ElemFeatures face( SMDSAbs_Face ); + SMESHDS_Mesh* meshDS = helper.GetMeshDS(); + + // check if there are internal or shared FACEs + bool hasInternal = ( !_grid->_geometry.IsOneSolid() || + _grid->_geometry._soleSolid.HasInternalFaces() ); + + for ( size_t iV = 0; iV < boundaryVolumes.size(); ++iV ) + { + if ( !vTool.Set( boundaryVolumes[ iV ])) + continue; + + TGeomID solidID = vTool.Element()->GetShapeID(); + Solid * solid = _grid->GetOneOfSolids( solidID ); + + // find boundary facets + + bndFacets.clear(); + for ( int iF = 0, n = vTool.NbFaces(); iF < n; iF++ ) + { + bool isBoundary = vTool.IsFreeFace( iF ); + if ( isBoundary ) + { + bndFacets.push_back( iF ); + } + else if ( hasInternal ) + { + // check if all nodes are on internal/shared FACEs + isBoundary = true; + const SMDS_MeshNode** nn = vTool.GetFaceNodes( iF ); + const size_t nbFaceNodes = vTool.NbFaceNodes ( iF ); + for ( size_t iN = 0; iN < nbFaceNodes && isBoundary; ++iN ) + isBoundary = ( nn[ iN ]->GetShapeID() != solidID ); + if ( isBoundary ) + bndFacets.push_back( -( iF+1 )); // !!! minus ==> to check the FACE + } + } + if ( bndFacets.empty() ) + continue; + + // create faces + + if ( !vTool.IsPoly() ) + vTool.SetExternalNormal(); + for ( size_t i = 0; i < bndFacets.size(); ++i ) // loop on boundary facets + { + const bool isBoundary = ( bndFacets[i] >= 0 ); + const int iFacet = isBoundary ? bndFacets[i] : -bndFacets[i]-1; + const SMDS_MeshNode** nn = vTool.GetFaceNodes( iFacet ); + const size_t nbFaceNodes = vTool.NbFaceNodes ( iFacet ); + face.myNodes.assign( nn, nn + nbFaceNodes ); + + TGeomID faceID = 0; + const SMDS_MeshElement* existFace = 0, *newFace = 0; + + if (( existFace = meshDS->FindElement( face.myNodes, SMDSAbs_Face ))) + { + if ( existFace->isMarked() ) + continue; // created by this method + faceID = existFace->GetShapeID(); + } + else + { + // look for a supporting FACE + for ( size_t iN = 0; iN < nbFaceNodes && !faceID; ++iN ) // look for a node on FACE + { + if ( nn[ iN ]->GetPosition()->GetDim() == 2 ) + faceID = nn[ iN ]->GetShapeID(); + } + for ( size_t iN = 0; iN < nbFaceNodes && !faceID; ++iN ) + { + // look for a father FACE of EDGEs and VERTEXes + const TopoDS_Shape& s1 = _grid->Shape( nn[ iN ]->GetShapeID() ); + const TopoDS_Shape& s2 = _grid->Shape( nn[ iN+1 ]->GetShapeID() ); + if ( s1 != s2 && s1.ShapeType() == TopAbs_EDGE && s2.ShapeType() == TopAbs_EDGE ) + { + TopoDS_Shape f = helper.GetCommonAncestor( s1, s2, *helper.GetMesh(), TopAbs_FACE ); + if ( !f.IsNull() ) + faceID = _grid->ShapeID( f ); + } + } + + bool toCheckFace = faceID && (( !isBoundary ) || + ( hasInternal && _grid->_toUseThresholdForInternalFaces )); + if ( toCheckFace ) // check if all nodes are on the found FACE + { + SMESH_subMesh* faceSM = helper.GetMesh()->GetSubMeshContaining( faceID ); + for ( size_t iN = 0; iN < nbFaceNodes && faceID; ++iN ) + { + TGeomID subID = nn[ iN ]->GetShapeID(); + if ( subID != faceID && !faceSM->DependsOn( subID )) + faceID = 0; + } + if ( !faceID && !isBoundary ) + continue; + } + } + // orient a new face according to supporting FACE orientation in shape_to_mesh + if ( !solid->IsOutsideOriented( faceID )) + { + if ( existFace ) + editor.Reorient( existFace ); + else + std::reverse( face.myNodes.begin(), face.myNodes.end() ); + } + + if ( ! ( newFace = existFace )) + { + face.SetPoly( nbFaceNodes > 4 ); + newFace = editor.AddElement( face.myNodes, face ); + if ( !newFace ) + continue; + newFace->setIsMarked( true ); // to distinguish from face created in getBoundaryElems() + } + + if ( faceID && _grid->IsBoundaryFace( faceID )) // face is not shared + { + // set newFace to the found FACE provided that it fully lies on the FACE + for ( size_t iN = 0; iN < nbFaceNodes && faceID; ++iN ) + if ( nn[iN]->GetShapeID() == solidID ) + { + if ( existFace ) + meshDS->UnSetMeshElementOnShape( existFace, _grid->Shape( faceID )); + faceID = 0; + } + } + + // split a polygon that will be used by other 3D algorithm + if ( faceID && nbFaceNodes > 4 && + !_grid->IsInternal( faceID ) && + !_grid->IsShared( faceID ) && + !_grid->IsBoundaryFace( faceID )) + { + splitPolygon( newFace, vTool, iFacet, faceID, solidID, + face, editor, i+1 < bndFacets.size() ); + } + else + { + if ( faceID ) + meshDS->SetMeshElementOnShape( newFace, faceID ); + else + meshDS->SetMeshElementOnShape( newFace, solidID ); + } + } // loop on bndFacets + } // loop on boundaryVolumes + + + // Orient coherently mesh faces on INTERNAL FACEs + + if ( hasInternal ) + { + TopExp_Explorer faceExp( _grid->_geometry._mainShape, TopAbs_FACE ); + for ( ; faceExp.More(); faceExp.Next() ) + { + if ( faceExp.Current().Orientation() != TopAbs_INTERNAL ) + continue; + + SMESHDS_SubMesh* sm = meshDS->MeshElements( faceExp.Current() ); + if ( !sm ) continue; + + TIDSortedElemSet facesToOrient; + for ( SMDS_ElemIteratorPtr fIt = sm->GetElements(); fIt->more(); ) + facesToOrient.insert( facesToOrient.end(), fIt->next() ); + if ( facesToOrient.size() < 2 ) + continue; + + gp_Dir direction(1,0,0); + const SMDS_MeshElement* anyFace = *facesToOrient.begin(); + editor.Reorient2D( facesToOrient, direction, anyFace ); + } + } + return; + } + + //================================================================================ + /*! + * \brief Create mesh segments. + */ + void Hexahedron::addSegments( SMESH_MesherHelper& helper, + const map< TGeomID, vector< TGeomID > >& edge2faceIDsMap ) + { + SMESHDS_Mesh* mesh = helper.GetMeshDS(); + + std::vector nodes; + std::vector elems; + map< TGeomID, vector< TGeomID > >::const_iterator e2ff = edge2faceIDsMap.begin(); + for ( ; e2ff != edge2faceIDsMap.end(); ++e2ff ) + { + const TopoDS_Edge& edge = TopoDS::Edge( _grid->Shape( e2ff->first )); + const TopoDS_Face& face = TopoDS::Face( _grid->Shape( e2ff->second[0] )); + StdMeshers_FaceSide side( face, edge, helper.GetMesh(), /*isFwd=*/true, /*skipMed=*/true ); + nodes = side.GetOrderedNodes(); + + elems.clear(); + if ( nodes.size() == 2 ) + // check that there is an element connecting two nodes + if ( !mesh->GetElementsByNodes( nodes, elems )) + continue; + + for ( size_t i = 1; i < nodes.size(); i++ ) + { + SMDS_MeshElement* segment = mesh->AddEdge( nodes[i-1], nodes[i] ); + mesh->SetMeshElementOnShape( segment, e2ff->first ); + } + } + return; + } + + //================================================================================ + /*! + * \brief Return created volumes and volumes that can have free facet because of + * skipped small volume. Also create mesh faces on free facets + * of adjacent not-cut volumes id the result volume is too small. + */ + void Hexahedron::getBoundaryElems( vector< const SMDS_MeshElement* > & boundaryElems ) + { + if ( _hasTooSmall /*|| _volumeDefs.IsEmpty()*/ ) + { + // create faces around a missing small volume + TGeomID faceID = 0; + SMESH_MeshEditor editor( _grid->_helper->GetMesh() ); + SMESH_MeshEditor::ElemFeatures polygon( SMDSAbs_Face ); + SMESHDS_Mesh* meshDS = _grid->_helper->GetMeshDS(); + std::vector adjVolumes(2); + for ( size_t iF = 0; iF < _polygons.size(); ++iF ) + { + const size_t nbLinks = _polygons[ iF ]._links.size(); + if ( nbLinks != 4 ) continue; + polygon.myNodes.resize( nbLinks ); + polygon.myNodes.back() = 0; + for ( size_t iL = 0, iN = nbLinks - 1; iL < nbLinks; ++iL, --iN ) + if ( ! ( polygon.myNodes[iN] = _polygons[ iF ]._links[ iL ].FirstNode()->Node() )) + break; + if ( !polygon.myNodes.back() ) + continue; + + meshDS->GetElementsByNodes( polygon.myNodes, adjVolumes, SMDSAbs_Volume ); + if ( adjVolumes.size() != 1 ) + continue; + if ( !adjVolumes[0]->isMarked() ) + { + boundaryElems.push_back( adjVolumes[0] ); + adjVolumes[0]->setIsMarked( true ); + } + + bool sameShape = true; + TGeomID shapeID = polygon.myNodes[0]->GetShapeID(); + for ( size_t i = 1; i < polygon.myNodes.size() && sameShape; ++i ) + sameShape = ( shapeID == polygon.myNodes[i]->GetShapeID() ); + + if ( !sameShape || !_grid->IsSolid( shapeID )) + continue; // some of shapes must be FACE + + if ( !faceID ) + { + faceID = getAnyFace(); + if ( !faceID ) + break; + if ( _grid->IsInternal( faceID ) || + _grid->IsShared( faceID ) || + _grid->IsBoundaryFace( faceID )) + break; // create only if a new face will be used by other 3D algo + } + + Solid * solid = _grid->GetOneOfSolids( adjVolumes[0]->GetShapeID() ); + if ( !solid->IsOutsideOriented( faceID )) + std::reverse( polygon.myNodes.begin(), polygon.myNodes.end() ); + + //polygon.SetPoly( polygon.myNodes.size() > 4 ); + const SMDS_MeshElement* newFace = editor.AddElement( polygon.myNodes, polygon ); + meshDS->SetMeshElementOnShape( newFace, faceID ); + } + } + + // return created volumes + for ( _volumeDef* volDef = &_volumeDefs; volDef; volDef = volDef->_next ) + { + if ( volDef->_volume && !volDef->_volume->isMarked() ) + { + volDef->_volume->setIsMarked( true ); + boundaryElems.push_back( volDef->_volume ); + + if ( _grid->IsToCheckNodePos() ) // un-mark nodes marked in addVolumes() + for ( size_t iN = 0; iN < volDef->_nodes.size(); ++iN ) + volDef->_nodes[iN].Node()->setIsMarked( false ); + } + } + } + + //================================================================================ + /*! + * \brief Set to _hexLinks a next portion of splits located on one side of INTERNAL FACEs + */ + bool Hexahedron::_SplitIterator::Next() + { + if ( _iterationNb > 0 ) + // count used splits + for ( size_t i = 0; i < _splits.size(); ++i ) + { + if ( _splits[i]._iCheckIteration == _iterationNb ) + { + _splits[i]._isUsed = _splits[i]._checkedSplit->_faces[1]; + _nbUsed += _splits[i]._isUsed; + } + if ( !More() ) + return false; + } + + ++_iterationNb; + + bool toTestUsed = ( _nbChecked >= _splits.size() ); + if ( toTestUsed ) + { + // all splits are checked; find all not used splits + for ( size_t i = 0; i < _splits.size(); ++i ) + if ( !_splits[i].IsCheckedOrUsed( toTestUsed )) + _splits[i]._iCheckIteration = _iterationNb; + + _nbUsed = _splits.size(); // to stop iteration + } + else + { + // get any not used/checked split to start from + _freeNodes.clear(); + for ( size_t i = 0; i < _splits.size(); ++i ) + { + if ( !_splits[i].IsCheckedOrUsed( toTestUsed )) + { + _freeNodes.push_back( _splits[i]._nodes[0] ); + _freeNodes.push_back( _splits[i]._nodes[1] ); + _splits[i]._iCheckIteration = _iterationNb; + break; + } + } + // find splits connected to the start one via _freeNodes + for ( size_t iN = 0; iN < _freeNodes.size(); ++iN ) + { + for ( size_t iS = 0; iS < _splits.size(); ++iS ) + { + if ( _splits[iS].IsCheckedOrUsed( toTestUsed )) + continue; + int iN2 = -1; + if ( _freeNodes[iN] == _splits[iS]._nodes[0] ) + iN2 = 1; + else if ( _freeNodes[iN] == _splits[iS]._nodes[1] ) + iN2 = 0; + else + continue; + if ( _freeNodes[iN]->_isInternalFlags > 0 ) + { + if ( _splits[iS]._nodes[ iN2 ]->_isInternalFlags == 0 ) + continue; + if ( !_splits[iS]._nodes[ iN2 ]->IsLinked( _freeNodes[iN]->_intPoint )) + continue; + } + _splits[iS]._iCheckIteration = _iterationNb; + _freeNodes.push_back( _splits[iS]._nodes[ iN2 ]); + } + } + } + // set splits to hex links + + for ( int iL = 0; iL < 12; ++iL ) + _hexLinks[ iL ]._splits.clear(); + + _Link split; + for ( size_t i = 0; i < _splits.size(); ++i ) + { + if ( _splits[i]._iCheckIteration == _iterationNb ) + { + split._nodes[0] = _splits[i]._nodes[0]; + split._nodes[1] = _splits[i]._nodes[1]; + _Link & hexLink = _hexLinks[ _splits[i]._linkID ]; + hexLink._splits.push_back( split ); + _splits[i]._checkedSplit = & hexLink._splits.back(); + ++_nbChecked; + } + } + return More(); + } //================================================================================ /*! @@ -3524,50 +5234,46 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, _computeCanceled = false; SMESH_MesherHelper helper( theMesh ); + SMESHDS_Mesh* meshDS = theMesh.GetMeshDS(); try { Grid grid; - grid._helper = &helper; + grid._helper = &helper; + grid._toAddEdges = _hyp->GetToAddEdges(); + grid._toCreateFaces = _hyp->GetToCreateFaces(); + grid._toConsiderInternalFaces = _hyp->GetToConsiderInternalFaces(); + grid._toUseThresholdForInternalFaces = _hyp->GetToUseThresholdForInternalFaces(); + grid._sizeThreshold = _hyp->GetSizeThreshold(); + grid.InitGeometry( theShape ); vector< TopoDS_Shape > faceVec; { TopTools_MapOfShape faceMap; TopExp_Explorer fExp; for ( fExp.Init( theShape, TopAbs_FACE ); fExp.More(); fExp.Next() ) - if ( !faceMap.Add( fExp.Current() )) - faceMap.Remove( fExp.Current() ); // remove a face shared by two solids - - for ( fExp.ReInit(); fExp.More(); fExp.Next() ) - if ( faceMap.Contains( fExp.Current() )) - faceVec.push_back( fExp.Current() ); + { + bool isNewFace = faceMap.Add( fExp.Current() ); + if ( !grid._toConsiderInternalFaces ) + if ( !isNewFace || fExp.Current().Orientation() == TopAbs_INTERNAL ) + // remove an internal face + faceMap.Remove( fExp.Current() ); + } + faceVec.reserve( faceMap.Extent() ); + faceVec.assign( faceMap.cbegin(), faceMap.cend() ); } vector facesItersectors( faceVec.size() ); - map< TGeomID, vector< TGeomID > > edge2faceIDsMap; - TopExp_Explorer eExp; Bnd_Box shapeBox; for ( size_t i = 0; i < faceVec.size(); ++i ) { - facesItersectors[i]._face = TopoDS::Face ( faceVec[i] ); - facesItersectors[i]._faceID = grid._shapes.Add( faceVec[i] ); + facesItersectors[i]._face = TopoDS::Face( faceVec[i] ); + facesItersectors[i]._faceID = grid.ShapeID( faceVec[i] ); facesItersectors[i]._grid = &grid; shapeBox.Add( facesItersectors[i].GetFaceBndBox() ); - - if ( _hyp->GetToAddEdges() ) - { - helper.SetSubShape( faceVec[i] ); - for ( eExp.Init( faceVec[i], TopAbs_EDGE ); eExp.More(); eExp.Next() ) - { - const TopoDS_Edge& edge = TopoDS::Edge( eExp.Current() ); - if ( !SMESH_Algo::isDegenerated( edge ) && - !helper.IsRealSeam( edge )) - edge2faceIDsMap[ grid._shapes.Add( edge )].push_back( facesItersectors[i]._faceID ); - } - } } - getExactBndBox( faceVec, _hyp->GetAxisDirs(), shapeBox ); + vector xCoords, yCoords, zCoords; _hyp->GetCoordinates( xCoords, yCoords, zCoords, shapeBox ); @@ -3581,7 +5287,7 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, BRepBuilderAPI_Copy copier; for ( size_t i = 0; i < facesItersectors.size(); ++i ) { - if ( !facesItersectors[i].IsThreadSafe(tshapes) ) + if ( !facesItersectors[i].IsThreadSafe( tshapes )) { copier.Perform( facesItersectors[i]._face ); facesItersectors[i]._face = TopoDS::Face( copier ); @@ -3603,33 +5309,36 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, for ( size_t i = 0; i < facesItersectors.size(); ++i ) facesItersectors[i].StoreIntersections(); - TopExp_Explorer solidExp (theShape, TopAbs_SOLID); - helper.SetSubShape( solidExp.Current() ); - helper.SetElementsOnShape( true ); - if ( _computeCanceled ) return false; // create nodes on the geometry - grid.ComputeNodes(helper); + grid.ComputeNodes( helper ); if ( _computeCanceled ) return false; + // get EDGEs to take into account + map< TGeomID, vector< TGeomID > > edge2faceIDsMap; + grid.GetEdgesToImplement( edge2faceIDsMap, theShape, faceVec ); + // create volume elements - Hexahedron hex( _hyp->GetSizeThreshold(), &grid ); + Hexahedron hex( &grid ); int nbAdded = hex.MakeElements( helper, edge2faceIDsMap ); - SMESHDS_Mesh* meshDS = theMesh.GetMeshDS(); if ( nbAdded > 0 ) { - // make all SOLIDs computed - if ( SMESHDS_SubMesh* sm1 = meshDS->MeshElements( solidExp.Current()) ) + if ( !grid._toConsiderInternalFaces ) { - SMDS_ElemIteratorPtr volIt = sm1->GetElements(); - for ( ; solidExp.More() && volIt->more(); solidExp.Next() ) + // make all SOLIDs computed + TopExp_Explorer solidExp( theShape, TopAbs_SOLID ); + if ( SMESHDS_SubMesh* sm1 = meshDS->MeshElements( solidExp.Current()) ) { - const SMDS_MeshElement* vol = volIt->next(); - sm1->RemoveElement( vol ); - meshDS->SetMeshElementOnShape( vol, solidExp.Current() ); + SMDS_ElemIteratorPtr volIt = sm1->GetElements(); + for ( ; solidExp.More() && volIt->more(); solidExp.Next() ) + { + const SMDS_MeshElement* vol = volIt->next(); + sm1->RemoveElement( vol ); + meshDS->SetMeshElementOnShape( vol, solidExp.Current() ); + } } } // make other sub-shapes computed @@ -3637,9 +5346,9 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, } // remove free nodes - if ( SMESHDS_SubMesh * smDS = meshDS->MeshElements( helper.GetSubShapeID() )) + //if ( SMESHDS_SubMesh * smDS = meshDS->MeshElements( helper.GetSubShapeID() )) { - TIDSortedNodeSet nodesToRemove; + std::vector< const SMDS_MeshNode* > nodesToRemove; // get intersection nodes for ( int iDir = 0; iDir < 3; ++iDir ) { @@ -3648,19 +5357,25 @@ bool StdMeshers_Cartesian_3D::Compute(SMESH_Mesh & theMesh, { multiset< F_IntersectPoint >::iterator ip = lines[i]._intPoints.begin(); for ( ; ip != lines[i]._intPoints.end(); ++ip ) - if ( ip->_node && ip->_node->NbInverseElements() == 0 ) - nodesToRemove.insert( nodesToRemove.end(), ip->_node ); + if ( ip->_node && ip->_node->NbInverseElements() == 0 && !ip->_node->isMarked() ) + { + nodesToRemove.push_back( ip->_node ); + ip->_node->setIsMarked( true ); + } } } // get grid nodes for ( size_t i = 0; i < grid._nodes.size(); ++i ) - if ( grid._nodes[i] && grid._nodes[i]->NbInverseElements() == 0 ) - nodesToRemove.insert( nodesToRemove.end(), grid._nodes[i] ); + if ( grid._nodes[i] && grid._nodes[i]->NbInverseElements() == 0 && + !grid._nodes[i]->isMarked() ) + { + nodesToRemove.push_back( grid._nodes[i] ); + grid._nodes[i]->setIsMarked( true ); + } // do remove - TIDSortedNodeSet::iterator n = nodesToRemove.begin(); - for ( ; n != nodesToRemove.end(); ++n ) - meshDS->RemoveFreeNode( *n, smDS, /*fromGroups=*/false ); + for ( size_t i = 0; i < nodesToRemove.size(); ++i ) + meshDS->RemoveFreeNode( nodesToRemove[i], /*smD=*/0, /*fromGroups=*/false ); } return nbAdded; @@ -3795,4 +5510,3 @@ void StdMeshers_Cartesian_3D::setSubmeshesComputed(SMESH_Mesh& theMesh, for ( TopExp_Explorer soExp( theShape, TopAbs_SOLID ); soExp.More(); soExp.Next() ) _EventListener::setAlwaysComputed( true, theMesh.GetSubMesh( soExp.Current() )); } - diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx index b70d05091..f1271c105 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.cxx @@ -204,8 +204,8 @@ namespace StdMeshersGUI axisTabLayout->setSpacing( SPACING ); axisTabLayout->addWidget( modeBox , 0, 0, 1, 3 ); - axisTabLayout->addWidget( myInsertBtn , 1, 0, 1, 2 ); - axisTabLayout->addWidget( myDeleteBtn , 2, 0, 1, 2 ); + axisTabLayout->addWidget( myInsertBtn, 1, 0, 1, 2 ); + axisTabLayout->addWidget( myDeleteBtn, 2, 0, 1, 2 ); axisTabLayout->addWidget( myStepLabel, 3, 0 ); axisTabLayout->addWidget( myStepSpin , 3, 1 ); axisTabLayout->addWidget( csFrame , 1, 2, 4, 1 ); @@ -821,6 +821,15 @@ QFrame* StdMeshersGUI_CartesianParamCreator::buildFrame() myAddEdges = new QCheckBox( tr("ADD_EDGES"), GroupC1 ); argGroupLayout->addWidget( myAddEdges, row, 0, 1, 2 ); row++; + myCreateFaces = new QCheckBox( tr("CREATE_FACES"), GroupC1 ); + argGroupLayout->addWidget( myCreateFaces, row, 0, 1, 2 ); + row++; + myConsiderInternalFaces = new QCheckBox( tr("CONSIDER_INTERNAL_FACES"), GroupC1 ); + argGroupLayout->addWidget( myConsiderInternalFaces, row, 0, 1, 2 ); + row++; + myUseThresholdForInternalFaces = new QCheckBox( tr("USE_THRESHOLD_FOR_INTERNAL_FACES"), GroupC1 ); + argGroupLayout->addWidget( myUseThresholdForInternalFaces, row, 0, 1, 2 ); + row++; // 3) Grid definition QTabWidget* tabWdg = new QTabWidget( fr ); @@ -924,6 +933,8 @@ QFrame* StdMeshersGUI_CartesianParamCreator::buildFrame() connect( myOrthogonalChk, SIGNAL( toggled(bool)), SLOT( onOrthogonalAxes(bool))); connect( optimBtn, SIGNAL( clicked(bool)), SLOT( onOptimalAxes(bool))); connect( resetBtn, SIGNAL( clicked(bool)), SLOT( onResetAxes(bool))); + connect( myConsiderInternalFaces, SIGNAL( toggled(bool)), + myUseThresholdForInternalFaces, SLOT( setEnabled(bool))); for ( int i = 0; i < 3; ++i ) { connect( myXDirSpin[i], SIGNAL(valueChanged (const QString&)), @@ -999,6 +1010,9 @@ void StdMeshersGUI_CartesianParamCreator::retrieveParams() const myThreshold->setText( varName ); myAddEdges->setChecked( h->GetToAddEdges() ); + myCreateFaces->setChecked( h->GetToCreateFaces() ); + myConsiderInternalFaces->setChecked( h->GetToConsiderInternalFaces() ); + myUseThresholdForInternalFaces->setChecked( h->GetToUseThresholdForInternalFaces() ); // grid definition for ( int ax = 0; ax < 3; ++ax ) @@ -1086,6 +1100,9 @@ QString StdMeshersGUI_CartesianParamCreator::storeParams() const h->SetVarParameter( myThreshold->text().toLatin1().constData(), "SetSizeThreshold" ); h->SetSizeThreshold( myThreshold->text().toDouble() ); h->SetToAddEdges( myAddEdges->isChecked() ); + h->SetToCreateFaces( myCreateFaces->isChecked() ); + h->SetToConsiderInternalFaces( myConsiderInternalFaces->isChecked() ); + h->SetToUseThresholdForInternalFaces( myUseThresholdForInternalFaces->isChecked() ); // grid for ( int ax = 0; ax < 3; ++ax ) diff --git a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h index 9ae66b9d8..f72b1e531 100644 --- a/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h +++ b/src/StdMeshersGUI/StdMeshersGUI_CartesianParamCreator.h @@ -152,6 +152,9 @@ private: QLineEdit* myName; SMESHGUI_SpinBox* myThreshold; QCheckBox* myAddEdges; + QCheckBox* myCreateFaces; + QCheckBox* myConsiderInternalFaces; + QCheckBox* myUseThresholdForInternalFaces; StdMeshersGUI::GridAxisTab* myAxisTabs[3]; QGroupBox* myFixedPointGrp; diff --git a/src/StdMeshersGUI/StdMeshers_msg_en.ts b/src/StdMeshersGUI/StdMeshers_msg_en.ts index 036b28413..0c0509f19 100644 --- a/src/StdMeshersGUI/StdMeshers_msg_en.ts +++ b/src/StdMeshersGUI/StdMeshers_msg_en.ts @@ -580,6 +580,18 @@ this one for this mesh/sub-mesh. ADD_EDGES Implement Edges + + CREATE_FACES + Create Faces + + + CONSIDER_INTERNAL_FACES + Consider Shared and Internal Faces + + + USE_THRESHOLD_FOR_INTERNAL_FACES + Apply Threshold to Shared / Internal Faces + AXIS_X Axis X diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx index ec36baf67..fa1cadfcb 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.cxx @@ -240,6 +240,14 @@ void StdMeshers_CartesianParameters3D_i::SetAxesDirs(const SMESH::DirStruct& xDi coords[6] = zDir.PS.x; coords[7] = zDir.PS.y; coords[8] = zDir.PS.z; + + const double* oldCoords = GetImpl()->GetAxisDirs(); + bool isSame = true; + for ( int i = 0; i < 9 && isSame; ++i ) + isSame = ( oldCoords[i] == coords[i] ); + if ( isSame ) + return; + try { this->GetImpl()->SetAxisDirs(coords); @@ -283,6 +291,11 @@ void StdMeshers_CartesianParameters3D_i::GetAxesDirs(SMESH::DirStruct& xDir, void StdMeshers_CartesianParameters3D_i::SetFixedPoint(const SMESH::PointStruct& ps, CORBA::Boolean toUnset) { + SMESH::PointStruct oldPS; + GetFixedPoint( oldPS ); + if ( oldPS.x == ps.x && oldPS.y == ps.y && oldPS.z == ps.z ) + return; + double p[3] = { ps.x, ps.y, ps.z }; GetImpl()->SetFixedPoint( p, toUnset ); @@ -335,6 +348,76 @@ CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToAddEdges() return GetImpl()->GetToAddEdges(); } +//======================================================================= +//function : SetToConsiderInternalFaces +//purpose : Enables treatment of geom faces, either shared by solids or internal. +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetToConsiderInternalFaces(CORBA::Boolean toTreat) +{ + if ( GetToConsiderInternalFaces() == toTreat ) + return; + GetImpl()->SetToConsiderInternalFaces( toTreat ); + SMESH::TPythonDump() << _this() << ".SetToConsiderInternalFaces( " << toTreat << " )"; +} + +//======================================================================= +//function : GetToConsiderInternalFaces +//purpose : Return true if treatment of internal geom faces is enabled +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToConsiderInternalFaces() +{ + return GetImpl()->GetToConsiderInternalFaces(); +} + +//======================================================================= +//function : SetToUseThresholdForInternalFaces +//purpose : Enables applying size threshold to grid cells cut by internal geom faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetToUseThresholdForInternalFaces(CORBA::Boolean toUse) +{ + if ( GetToUseThresholdForInternalFaces() == toUse ) + return; + GetImpl()->SetToUseThresholdForInternalFaces( toUse ); + SMESH::TPythonDump() << _this() << ".SetToUseThresholdForInternalFaces( " << toUse << " )"; +} + +//======================================================================= +//function : GetToUseThresholdForInternalFaces +//purpose : Return true if applying size threshold to grid cells cut by +// internal geom faces is enabled +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToUseThresholdForInternalFaces() +{ + return GetImpl()->GetToUseThresholdForInternalFaces(); +} + +//======================================================================= +//function : SetToCreateFaces +//purpose : Enables creation of mesh faces. +//======================================================================= + +void StdMeshers_CartesianParameters3D_i::SetToCreateFaces(CORBA::Boolean toCreate) +{ + if ( GetToCreateFaces() == toCreate ) + return; + GetImpl()->SetToCreateFaces( toCreate ); + SMESH::TPythonDump() << _this() << ".SetToCreateFaces( " << toCreate << " )"; +} + +//======================================================================= +//function : GetToCreateFaces +//purpose : Check if creation of mesh faces enabled +//======================================================================= + +CORBA::Boolean StdMeshers_CartesianParameters3D_i::GetToCreateFaces() +{ + return GetImpl()->GetToCreateFaces(); +} + //======================================================================= //function : IsGridBySpacing //purpose : Return true if the grid is defined by spacing functions and diff --git a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx index 03dad0cfb..1b273ce77 100644 --- a/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx +++ b/src/StdMeshers_I/StdMeshers_CartesianParameters3D_i.hxx @@ -100,7 +100,7 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: /*! - * \brief Enables implementation of geometrical edges into the mesh. If this feature + * \brief Enable implementation of geometrical edges into the mesh. If this feature * is disabled, sharp edges of the shape are lost ("smoothed") in the mesh if * they don't coincide with the grid lines */ @@ -108,13 +108,32 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: CORBA::Boolean GetToAddEdges(); /*! - * \brief Return true if the grid is defined by spacing functions and + * Enable treatment of geom faces, either shared by solids or internal. + */ + void SetToConsiderInternalFaces(CORBA::Boolean toTreat); + CORBA::Boolean GetToConsiderInternalFaces(); + + /*! + * Enable applying size threshold to grid cells cut by internal geom faces. + */ + void SetToUseThresholdForInternalFaces(CORBA::Boolean toUse); + CORBA::Boolean GetToUseThresholdForInternalFaces(); + + /*! + * Enable creation of mesh faces. + */ + void SetToCreateFaces(CORBA::Boolean toCreate); + CORBA::Boolean GetToCreateFaces(); + + + /*! + * \brief Return true if the grid is defined by spacing functions and * not by node coordinates */ CORBA::Boolean IsGridBySpacing(CORBA::Short axis); /*! - * Returns axes at which number of hexahedra is maximal + * Return axes at which number of hexahedra is maximal */ void ComputeOptimalAxesDirs(GEOM::GEOM_Object_ptr shape, CORBA::Boolean isOrthogonal, @@ -122,7 +141,7 @@ class STDMESHERS_I_EXPORT StdMeshers_CartesianParameters3D_i: SMESH::DirStruct& y, SMESH::DirStruct& z) throw (SALOME::SALOME_Exception); /*! - * \brief Computes node coordinates by spacing functions + * \brief Compute node coordinates by spacing functions * \param x0 - lower coordinate * \param x1 - upper coordinate * \param spaceFuns - space functions From 847109ca105992b415893dd2ef416b45e258b537 Mon Sep 17 00:00:00 2001 From: Anthony Geay Date: Mon, 17 Feb 2020 13:02:16 +0100 Subject: [PATCH 41/60] Fix compilation problem when vtkIdType is 64bits integer --- src/SMDS/SMDS_BallElement.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMDS/SMDS_BallElement.cxx b/src/SMDS/SMDS_BallElement.cxx index f73b4ec76..86dc634c7 100644 --- a/src/SMDS/SMDS_BallElement.cxx +++ b/src/SMDS/SMDS_BallElement.cxx @@ -29,7 +29,7 @@ void SMDS_BallElement::init(const SMDS_MeshNode * node, double diameter ) { - int nodeVtkID = node->GetVtkID(); + vtkIdType nodeVtkID = node->GetVtkID(); int vtkID = getGrid()->InsertNextLinkedCell( toVtkType( SMDSEntity_Ball ), 1, &nodeVtkID ); setVtkID( vtkID ); getGrid()->SetBallDiameter( GetVtkID(), diameter ); From 0fe03353fe170f68df0aa4802ed28c0212388c94 Mon Sep 17 00:00:00 2001 From: rnv Date: Mon, 17 Feb 2020 19:17:08 +0300 Subject: [PATCH 42/60] Fix for '#18728[CEA][Windows] SMESH compilation broken __cdecl SMESH::GetMeshesUsingAlgoOrHypothesis' --- src/SMESHGUI/SMESHGUI_HypothesesUtils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SMESHGUI/SMESHGUI_HypothesesUtils.h b/src/SMESHGUI/SMESHGUI_HypothesesUtils.h index 904a04b67..4f8840b2b 100644 --- a/src/SMESHGUI/SMESHGUI_HypothesesUtils.h +++ b/src/SMESHGUI/SMESHGUI_HypothesesUtils.h @@ -116,6 +116,7 @@ namespace SMESH SMESH::SMESH_Hypothesis_ptr ); typedef std::vector<_PTR(SObject)> SObjectList; + SMESHGUI_EXPORT SObjectList GetMeshesUsingAlgoOrHypothesis( SMESH::SMESH_Hypothesis_ptr ); SMESHGUI_EXPORT From 48d9b5a07c9503326bb36321b3fa4cbc215c1e97 Mon Sep 17 00:00:00 2001 From: eap Date: Mon, 17 Feb 2020 21:34:19 +0300 Subject: [PATCH 43/60] Fix negative volume of valid but small polyhedron https://salome-platform.org/forum/forum_10/547695356 --- src/SMDS/SMDS_VolumeTool.cxx | 15 +++++++++++++-- src/SMESH_I/SMESH_PythonDump.cxx | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/SMDS/SMDS_VolumeTool.cxx b/src/SMDS/SMDS_VolumeTool.cxx index ba0798484..21accdc9d 100644 --- a/src/SMDS/SMDS_VolumeTool.cxx +++ b/src/SMDS/SMDS_VolumeTool.cxx @@ -706,11 +706,22 @@ double SMDS_VolumeTool::GetSize() const if ( !myPolyedre ) return 0.; + SaveFacet savedFacet( myCurFace ); + + // get an origin not lying in plane of any facet + int faceIndex = std::max( myCurFace.myIndex, 0 ); + setFace( faceIndex ); + double minProj, maxProj; + XYZ normal, origin; + projectNodesToNormal( faceIndex, minProj, maxProj, normal.data()); + GetFaceBaryCenter( faceIndex, origin.x, origin.y, origin.z ); + origin.x += normal.x * ( maxProj - minProj ) * 1e-1; + origin.y += normal.y * ( maxProj - minProj ) * 1e-2; + origin.z += normal.z * ( maxProj - minProj ) * 1e-3; + // split a polyhedron into tetrahedrons - SaveFacet savedFacet( myCurFace ); SMDS_VolumeTool* me = const_cast< SMDS_VolumeTool* > ( this ); - XYZ origin( 1 + 1e-6, 22 + 2e-6, 333 + 3e-6 ); // for invalid poly: avoid lying on a facet plane for ( int f = 0; f < NbFaces(); ++f ) { me->setFace( f ); diff --git a/src/SMESH_I/SMESH_PythonDump.cxx b/src/SMESH_I/SMESH_PythonDump.cxx index 7be8cb7ef..391b55527 100644 --- a/src/SMESH_I/SMESH_PythonDump.cxx +++ b/src/SMESH_I/SMESH_PythonDump.cxx @@ -696,7 +696,7 @@ namespace SMESH //function : DumpPython //purpose : //======================================================================= -Engines::TMPFile* SMESH_Gen_i::DumpPython (CORBA::Boolean isPublished, +Engines::TMPFile* SMESH_Gen_i::DumpPython( CORBA::Boolean isPublished, CORBA::Boolean isMultiFile, CORBA::Boolean& isValidScript) { From 3e0873e7ffbfa17c1d211ae38bb1a7bda9d31595 Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 18 Feb 2020 14:39:31 +0300 Subject: [PATCH 44/60] Fix regression of #17828: Polyhedron Mesh volume calculation --- src/SMDS/SMDS_VolumeTool.cxx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/SMDS/SMDS_VolumeTool.cxx b/src/SMDS/SMDS_VolumeTool.cxx index 21accdc9d..fbb4f8b00 100644 --- a/src/SMDS/SMDS_VolumeTool.cxx +++ b/src/SMDS/SMDS_VolumeTool.cxx @@ -708,19 +708,9 @@ double SMDS_VolumeTool::GetSize() const SaveFacet savedFacet( myCurFace ); - // get an origin not lying in plane of any facet - int faceIndex = std::max( myCurFace.myIndex, 0 ); - setFace( faceIndex ); - double minProj, maxProj; - XYZ normal, origin; - projectNodesToNormal( faceIndex, minProj, maxProj, normal.data()); - GetFaceBaryCenter( faceIndex, origin.x, origin.y, origin.z ); - origin.x += normal.x * ( maxProj - minProj ) * 1e-1; - origin.y += normal.y * ( maxProj - minProj ) * 1e-2; - origin.z += normal.z * ( maxProj - minProj ) * 1e-3; - // split a polyhedron into tetrahedrons + bool oriOk = true; SMDS_VolumeTool* me = const_cast< SMDS_VolumeTool* > ( this ); for ( int f = 0; f < NbFaces(); ++f ) { @@ -732,9 +722,12 @@ double SMDS_VolumeTool::GetSize() const area = area + p1.Crossed( p2 ); p1 = p2; } - V += ( p1 - origin ).Dot( area ); + V += p1.Dot( area ); + oriOk = oriOk && IsFaceExternal( f ); } V /= 6; + if ( !oriOk && V > 0 ) + V *= -1; } else { From 78e005bc86ea14612332a228d6e75fedc834ac1c Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 19 Feb 2020 17:15:03 +0300 Subject: [PATCH 45/60] Enable creation of group of edges lying on wires using Mesh.Group() --- src/SMESH_SWIG/smeshBuilder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMESH_SWIG/smeshBuilder.py b/src/SMESH_SWIG/smeshBuilder.py index 69c4024e9..56a32dec0 100644 --- a/src/SMESH_SWIG/smeshBuilder.py +++ b/src/SMESH_SWIG/smeshBuilder.py @@ -2639,7 +2639,7 @@ class Mesh(metaclass = MeshMeta): tgeo = str(shape.GetShapeType()) if tgeo == "VERTEX": typ = NODE - elif tgeo == "EDGE": + elif tgeo == "EDGE" or tgeo == "WIRE": typ = EDGE elif tgeo == "FACE" or tgeo == "SHELL": typ = FACE From 90741df83406c4a8a2b0e90ebc33319158d7b667 Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 20 Feb 2020 19:28:41 +0300 Subject: [PATCH 46/60] Fix regression of Extrusion 3D caused by OCCT changes --- src/SMESHUtils/SMESH_Delaunay.cxx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/SMESHUtils/SMESH_Delaunay.cxx b/src/SMESHUtils/SMESH_Delaunay.cxx index 4771c6da8..92bbc6e3b 100644 --- a/src/SMESHUtils/SMESH_Delaunay.cxx +++ b/src/SMESHUtils/SMESH_Delaunay.cxx @@ -225,11 +225,8 @@ const BRepMesh_Triangle* SMESH_Delaunay::FindTriangle( const gp_XY& gp_XY seg = uv - gc; tria->Edges( linkIDs, ori ); -#if OCC_VERSION_LARGE <= 0x07030000 - int triaID = _triaDS->IndexOf( *tria ); -#else - int triaID = tria - & ( _triaDS->GetElement( 0 )); -#endif + + const BRepMesh_Triangle* prevTria = tria; tria = 0; for ( int i = 0; i < 3; ++i ) @@ -252,7 +249,9 @@ const BRepMesh_Triangle* SMESH_Delaunay::FindTriangle( const gp_XY& double uSeg = ( uv1 - gc ) ^ lin / crossSegLin; if ( 0. <= uSeg && uSeg <= 1. ) { - tria = & _triaDS->GetElement( triIDs.Index( 1 + ( triIDs.Index(1) == triaID ))); + tria = & _triaDS->GetElement( triIDs.Index( 1 )); + if ( tria == prevTria ) + tria = & _triaDS->GetElement( triIDs.Index( 2 )); if ( tria->Movability() != BRepMesh_Deleted ) break; } From 13b3c4bbb9681a68f761fb305e7940698eed6fb2 Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 21 Feb 2020 13:47:31 +0300 Subject: [PATCH 47/60] Fix regression of smesh/bugs_13/N3 (Body Fitting) and SIGSEGV at killSalome.py --- src/SMESH/SMESH_Mesh.cxx | 26 ++++++++++------------ src/StdMeshers/StdMeshers_Cartesian_3D.cxx | 19 +++++++++++----- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/SMESH/SMESH_Mesh.cxx b/src/SMESH/SMESH_Mesh.cxx index 77d19984f..cd5e4315c 100644 --- a/src/SMESH/SMESH_Mesh.cxx +++ b/src/SMESH/SMESH_Mesh.cxx @@ -176,10 +176,18 @@ SMESH_Mesh::~SMESH_Mesh() { if(MYDEBUG) MESSAGE("SMESH_Mesh::~SMESH_Mesh"); - // avoid usual removal of elements while processing RemoveHypothesis( algo ) event - SMESHDS_SubMeshIteratorPtr smIt = _myMeshDS->SubMeshes(); - while ( smIt->more() ) - const_cast( smIt->next() )->Clear(); + if ( _myDocument ) // avoid destructing _myMeshDS from ~SMESH_Gen() + _myDocument->RemoveMesh( _id ); + _myDocument = 0; + + // remove self from studyContext + if ( _gen ) + { + StudyContextStruct * studyContext = _gen->GetStudyContext(); + studyContext->mapMesh.erase( _id ); + } + + _myMeshDS->ClearMesh(); // issue 0020340: EDF 1022 SMESH : Crash with FindNodeClosestTo in a second new study // Notify event listeners at least that something happens @@ -200,16 +208,6 @@ SMESH_Mesh::~SMESH_Mesh() if ( _callUp) delete _callUp; _callUp = 0; - // remove self from studyContext - if ( _gen ) - { - StudyContextStruct * studyContext = _gen->GetStudyContext(); - studyContext->mapMesh.erase( _id ); - } - if ( _myDocument ) - _myDocument->RemoveMesh( _id ); - _myDocument = 0; - if ( _myMeshDS ) { // delete _myMeshDS, in a thread in order not to block closing a study with large meshes #ifndef WIN32 diff --git a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx index ea641c2b1..78059016f 100644 --- a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx +++ b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx @@ -375,6 +375,7 @@ namespace bool _toUseThresholdForInternalFaces; double _sizeThreshold; + vector< TGeomID > _shapeIDs; // returned by Hexahedron::getSolids() SMESH_MesherHelper* _helper; size_t CellIndex( size_t i, size_t j, size_t k ) const @@ -866,7 +867,7 @@ namespace void init( size_t i ); void setIJK( size_t i ); bool compute( const Solid* solid, const IsInternalFlag intFlag ); - vector< TGeomID > getSolids(); + const vector< TGeomID >& getSolids(); bool isCutByInternalFace( IsInternalFlag & maxFlag ); void addEdges(SMESH_MesherHelper& helper, vector< Hexahedron* >& intersectedHex, @@ -2174,8 +2175,14 @@ namespace /*! * \brief Return IDs of SOLIDs interfering with this Hexahedron */ - vector< TGeomID > Hexahedron::getSolids() + const vector< TGeomID >& Hexahedron::getSolids() { + _grid->_shapeIDs.clear(); + if ( _grid->_geometry.IsOneSolid() ) + { + _grid->_shapeIDs.push_back( _grid->GetSolid()->ID() ); + return _grid->_shapeIDs; + } // count intersection points belonging to each SOLID TID2Nb id2NbPoints; id2NbPoints.reserve( 3 ); @@ -2224,12 +2231,12 @@ namespace insertAndIncrement( solidIDs[i], id2NbPoints ); } - vector< TGeomID > solids; solids.reserve( id2NbPoints.size() ); + _grid->_shapeIDs.reserve( id2NbPoints.size() ); for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) if ( id2nb->second >= 3 ) - solids.push_back( id2nb->first ); + _grid->_shapeIDs.push_back( id2nb->first ); - return solids; + return _grid->_shapeIDs; } //================================================================================ @@ -2571,7 +2578,7 @@ namespace solid = _grid->GetSolid(); if ( !_grid->_geometry.IsOneSolid() ) { - vector< TGeomID > solidIDs = getSolids(); + const vector< TGeomID >& solidIDs = getSolids(); if ( solidIDs.size() > 1 ) { for ( size_t i = 0; i < solidIDs.size(); ++i ) From dfcc0eb72b126380c8da3d725f90c9ee43f59827 Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 21 Feb 2020 20:30:54 +0300 Subject: [PATCH 48/60] Fix Body Fitting regression on smesh/imps_11/M3 --- src/StdMeshers/StdMeshers_Cartesian_3D.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx index 78059016f..96bbf63f5 100644 --- a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx +++ b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx @@ -1037,7 +1037,7 @@ namespace switch ( ip->_transition ) { case Trans_IN: isOut = true; break; case Trans_OUT: isOut = false; break; - case Trans_TANGENT: isOut = ( prevID == 0 ); break; + case Trans_TANGENT: isOut = ( prevID != 0 ); break; case Trans_APEX: { // singularity point (apex of a cone) From afef8ccaa79e91391ccd567bc2607e3584137d36 Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 21 Feb 2020 21:19:12 +0300 Subject: [PATCH 49/60] Restore usage of GenericObject wraper ( _wrap ) --- src/PluginUtils/GeomSelectionTools.cxx | 2 +- src/SMESHGUI/SMESHGUI_GroupDlg.cxx | 6 +++--- src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx | 2 +- src/SMESHGUI/SMESHGUI_MeshOp.cxx | 4 ++-- src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx | 2 +- src/SMESH_I/SMESH_Gen_i.cxx | 2 +- src/SMESH_I/SMESH_Mesh_i.cxx | 12 ++++++------ 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/PluginUtils/GeomSelectionTools.cxx b/src/PluginUtils/GeomSelectionTools.cxx index ddf87e5ea..936afcb31 100644 --- a/src/PluginUtils/GeomSelectionTools.cxx +++ b/src/PluginUtils/GeomSelectionTools.cxx @@ -213,7 +213,7 @@ TopAbs_ShapeEnum GeomSelectionTools::entryToShapeType(std::string entry){ // if the Geom Object is a group if (aShape->GetType() == GEOM_GROUP){ // MESSAGE("It's a group"); - GEOM::GEOM_IGroupOperations_ptr aGroupOp = + GEOM::GEOM_IGroupOperations_wrap aGroupOp = _geomEngine->GetIGroupOperations(); ShapeType= (TopAbs_ShapeEnum)aGroupOp->GetType(aShape); } diff --git a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx index 0da119054..dae81ae51 100644 --- a/src/SMESHGUI/SMESHGUI_GroupDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_GroupDlg.cxx @@ -1063,7 +1063,7 @@ bool SMESHGUI_GroupDlg::onApply() if (geomGen->_is_nil()) return false; - GEOM::GEOM_IGroupOperations_ptr op = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_wrap op = geomGen->GetIGroupOperations(); if (op->_is_nil()) return false; @@ -1414,7 +1414,7 @@ void SMESHGUI_GroupDlg::onObjectSelectionChanged() GEOM::GEOM_Object_var aGroupMainShape; if (aGeomGroup->GetType() == 37) { - GEOM::GEOM_IGroupOperations_ptr anOp = + GEOM::GEOM_IGroupOperations_wrap anOp = SMESH::GetGEOMGen( aGeomGroup )->GetIGroupOperations(); aGroupMainShape = anOp->GetMainShape( aGeomGroup ); // aGroupMainShape is an existing servant => GEOM_Object_var not GEOM_Object_wrap @@ -2012,7 +2012,7 @@ void SMESHGUI_GroupDlg::onAdd() } else if ( myCurrentLineEdit == myGeomGroupLine && myGeomObjects->length() == 1 ) { - GEOM::GEOM_IGroupOperations_ptr aGroupOp = + GEOM::GEOM_IGroupOperations_wrap aGroupOp = SMESH::GetGEOMGen( myGeomObjects[0] )->GetIGroupOperations(); SMESH::ElementType aGroupType = SMESH::ALL; diff --git a/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx b/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx index 9054ad855..3bcc8b402 100644 --- a/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_GroupOnShapeDlg.cxx @@ -225,7 +225,7 @@ SMESH::ElementType SMESHGUI_GroupOnShapeOp::ElementType(GEOM::GEOM_Object_var ge if ( geom->GetType() == 37 ) // geom group { - GEOM::GEOM_IGroupOperations_ptr aGroupOp = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_wrap aGroupOp = geomGen->GetIGroupOperations(); if ( !aGroupOp->_is_nil() ) { // mainShape is an existing servant => GEOM_Object_var not GEOM_Object_wrap GEOM::GEOM_Object_var mainShape = aGroupOp->GetMainShape( geom ); diff --git a/src/SMESHGUI/SMESHGUI_MeshOp.cxx b/src/SMESHGUI/SMESHGUI_MeshOp.cxx index ed9e8d63c..500481081 100644 --- a/src/SMESHGUI/SMESHGUI_MeshOp.cxx +++ b/src/SMESHGUI/SMESHGUI_MeshOp.cxx @@ -349,7 +349,7 @@ bool SMESHGUI_MeshOp::isSubshapeOk() const GEOM::GEOM_Gen_var geomGen = mainGeom->GetGen(); if (geomGen->_is_nil()) return false; - GEOM::GEOM_IGroupOperations_ptr op = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_wrap op = geomGen->GetIGroupOperations(); if (op->_is_nil()) return false; // check all selected shapes @@ -2140,7 +2140,7 @@ bool SMESHGUI_MeshOp::createSubMesh( QString& theMess, QStringList& theEntryList // create a GEOM group GEOM::GEOM_Gen_var geomGen = mainGeom->GetGen(); if ( !geomGen->_is_nil() ) { - GEOM::GEOM_IGroupOperations_ptr op = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_wrap op = geomGen->GetIGroupOperations(); if ( !op->_is_nil() ) { // check and add all selected GEOM objects: they must be diff --git a/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx b/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx index 19397b871..b57a24197 100644 --- a/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx +++ b/src/SMESHGUI/SMESHGUI_ShapeByMeshDlg.cxx @@ -360,7 +360,7 @@ void SMESHGUI_ShapeByMeshOp::commitOperation() } else if (aNumberOfGO > 1) { - GEOM::GEOM_IGroupOperations_ptr aGroupOp = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_wrap aGroupOp = geomGen->GetIGroupOperations(); if ( aGroupOp->_is_nil() ) return; diff --git a/src/SMESH_I/SMESH_Gen_i.cxx b/src/SMESH_I/SMESH_Gen_i.cxx index ac0b69600..fc87fc517 100644 --- a/src/SMESH_I/SMESH_Gen_i.cxx +++ b/src/SMESH_I/SMESH_Gen_i.cxx @@ -3296,7 +3296,7 @@ namespace // utils for CopyMeshWithGeom() { int groupType = getShapeType( myNewMesh_i, newIndices[0] ); - GEOM::GEOM_IGroupOperations_ptr grOp = geomGen->GetIGroupOperations(); + GEOM::GEOM_IGroupOperations_wrap grOp = geomGen->GetIGroupOperations(); newShape = grOp->CreateGroup( mainShapeNew, groupType ); GEOM::ListOfLong_var newIndicesList = new GEOM::ListOfLong(); diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index d4e396cbd..06b0abcde 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -2028,9 +2028,9 @@ void SMESH_Mesh_i::addGeomGroupData(GEOM::GEOM_Object_ptr theGeomObj, if ( groupSO->_is_nil() ) return; // group indices - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( theGeomObj ); - GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); - GEOM::ListOfLong_var ids = groupOp->GetObjects( theGeomObj ); + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( theGeomObj ); + GEOM::GEOM_IGroupOperations_wrap groupOp = geomGen->GetIGroupOperations(); + GEOM::ListOfLong_var ids = groupOp->GetObjects( theGeomObj ); // store data _geomGroupData.push_back( TGeomGroupData() ); @@ -2104,9 +2104,9 @@ TopoDS_Shape SMESH_Mesh_i::newGroupShape( TGeomGroupData & groupData, int how ) // get indices of group items set curIndices; - GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); - GEOM::GEOM_IGroupOperations_ptr groupOp = geomGen->GetIGroupOperations(); - GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); + GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( geomGroup ); + GEOM::GEOM_IGroupOperations_wrap groupOp = geomGen->GetIGroupOperations(); + GEOM::ListOfLong_var ids = groupOp->GetObjects( geomGroup ); for ( CORBA::ULong i = 0; i < ids->length(); ++i ) curIndices.insert( ids[i] ); From 0e63df0f0346e2f8fcb18081840355493223a05c Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 26 Feb 2020 11:59:42 +0300 Subject: [PATCH 50/60] Fix regression of smesh/bugs_14/P7 --- src/SMESH_I/SMESH_Mesh_i.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SMESH_I/SMESH_Mesh_i.cxx b/src/SMESH_I/SMESH_Mesh_i.cxx index 06b0abcde..aa25d7d07 100644 --- a/src/SMESH_I/SMESH_Mesh_i.cxx +++ b/src/SMESH_I/SMESH_Mesh_i.cxx @@ -2295,6 +2295,8 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) if ( !geomClient ) return; GEOM::GEOM_Gen_var geomGen = _gen_i->GetGeomEngine( mainGO ); if ( geomGen->_is_nil() ) return; + CORBA::String_var geomComponentType = geomGen->ComponentDataType(); + bool isShaper = ( strcmp( geomComponentType.in(), "SHAPERSTUDY" ) == 0 ); CORBA::String_var ior = geomGen->GetStringFromIOR( mainGO ); geomClient->RemoveShapeFromBuffer( ior.in() ); @@ -2306,7 +2308,7 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) _preMeshInfo->ForgetAllData(); - if (isBreakLink) + if ( isBreakLink || !isShaper ) _impl->Clear(); TopoDS_Shape newShape = _gen_i->GeomObjectToShape( mainGO ); if ( newShape.IsNull() ) @@ -2506,7 +2508,7 @@ void SMESH_Mesh_i::CheckGeomModif( bool isBreakLink ) _gen_i->UpdateIcons( me ); - if ( !isBreakLink ) + if ( !isBreakLink && isShaper ) { SALOMEDS::SObject_wrap meshSO = _gen_i->ObjectToSObject( me ); if ( !meshSO->_is_nil() ) From e6f1c5ae12a569c30e0c71e8e8926ab0bb6f7489 Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 26 Feb 2020 12:02:38 +0300 Subject: [PATCH 51/60] Fix issue reported in https://www.salome-platform.org/forum/forum_10/180510604 --- src/StdMeshers/StdMeshers_CompositeHexa_3D.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/StdMeshers/StdMeshers_CompositeHexa_3D.cxx b/src/StdMeshers/StdMeshers_CompositeHexa_3D.cxx index b0f10f8d0..666c4be2c 100644 --- a/src/StdMeshers/StdMeshers_CompositeHexa_3D.cxx +++ b/src/StdMeshers/StdMeshers_CompositeHexa_3D.cxx @@ -420,9 +420,9 @@ namespace const TopTools_MapOfShape& cornerVV, TopTools_MapOfShape& internEE) { - TopTools_IndexedMapOfShape subEE; + TopTools_IndexedMapOfShape subEE, subFF; TopExp::MapShapes( shape, TopAbs_EDGE, subEE ); - //TopExp::MapShapes( shape, TopAbs_FACE, subFF ); + TopExp::MapShapes( shape, TopAbs_FACE, subFF ); TopoDS_Vertex VV[2]; TopTools_MapOfShape subChecked, ridgeEE; @@ -460,6 +460,8 @@ namespace { if ( !SMESH_MesherHelper::IsSubShape( ridgeE, *F )) continue; + if ( !subFF.Contains( *F )) + continue; if ( isContinuousMesh( ridgeE, TopoDS::Edge( *E ), TopoDS::Face( *F ), mesh )) { nextRidgeE = *E; From cc7e17c1ba6bf84e1c5566d27093faef4371edc8 Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 26 Feb 2020 12:03:13 +0300 Subject: [PATCH 52/60] Fix doc on Update menu --- doc/salome/gui/SMESH/input/viewing_meshes_overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/salome/gui/SMESH/input/viewing_meshes_overview.rst b/doc/salome/gui/SMESH/input/viewing_meshes_overview.rst index 7dc0dff98..06c9197f6 100644 --- a/doc/salome/gui/SMESH/input/viewing_meshes_overview.rst +++ b/doc/salome/gui/SMESH/input/viewing_meshes_overview.rst @@ -31,7 +31,7 @@ right-clicking on the selected mesh. * **Rename** - allows to rename the object in the Object browser. * **Hide all** - allows to hide all objects in the viewer. -* **Update** - refreshes the presentation of your mesh in the Object Browser, applying all recent changes. +* **Update** - refreshes the presentation of your mesh in the 3D Viewer, applying all recent changes. * :ref:`Mesh Information ` - provides information about the mesh. * :ref:`Find Element by Point ` - allows to find all mesh elements, to which belongs a point with the given coordinates. * **Auto Color** - switch on / off auto-assigning colors for the groups. If switched on, a default color of a new group in :ref:`Create Group ` dialog is chosen randomly. From d7e7b8a466336561da07e6566c2d15248d37bdf5 Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 28 Feb 2020 16:05:59 +0300 Subject: [PATCH 53/60] Fix regression of 3D_mesh_GHS3D_01/B1 --- src/StdMeshers/StdMeshers_ViscousLayers.cxx | 34 ++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/StdMeshers/StdMeshers_ViscousLayers.cxx b/src/StdMeshers/StdMeshers_ViscousLayers.cxx index 270330be4..d82b30bcc 100644 --- a/src/StdMeshers/StdMeshers_ViscousLayers.cxx +++ b/src/StdMeshers/StdMeshers_ViscousLayers.cxx @@ -1771,7 +1771,7 @@ namespace VISCOUS_3D PyDump(SMESH_Mesh& m) { int tag = 3 + m.GetId(); const char* fname = "/tmp/viscous.py"; - cout << "execfile('"<IsSame( face ) || + _periodicFaces[ i ]._shriFace[1]->IsSame( face )) + _periodicFaces[ i ].Clear(); + } }; //================================================================================ @@ -10765,6 +10774,7 @@ namespace VISCOUS_3D bool PeriodicFaces::IncludeShrunk( const TopoDS_Face& face, const TopTools_MapOfShape& shrunkFaces ) const { + if ( IsEmpty() ) return false; return (( _shriFace[0]->IsSame( face ) && _shriFace[1]->IsShrunk( shrunkFaces )) || ( _shriFace[1]->IsSame( face ) && _shriFace[0]->IsShrunk( shrunkFaces ))); } @@ -10785,7 +10795,8 @@ namespace VISCOUS_3D if ( iSrc != 0 ) { trsfInverse = _trsf; - trsfInverse.Invert(); + if ( !trsfInverse.Invert()) + return false; trsf = &trsfInverse; } SMESHDS_Mesh* meshDS = dataSrc->GetHelper().GetMeshDS(); @@ -10823,10 +10834,10 @@ namespace VISCOUS_3D } } bool done = ( n2n == _nnMap.end() ); - // cout << "MMMMMMMOOOOOOOOOOVVVVVVVVVVVEEEEEEEE " - // << _shriFace[iSrc]->_subMesh->GetId() << " -> " - // << _shriFace[iTgt]->_subMesh->GetId() << " -- " - // << ( done ? "DONE" : "FAIL") << endl; + debugMsg( "PeriodicFaces::MoveNodes " + << _shriFace[iSrc]->_subMesh->GetId() << " -> " + << _shriFace[iTgt]->_subMesh->GetId() << " -- " + << ( done ? "DONE" : "FAIL")); return done; } @@ -11286,6 +11297,8 @@ bool _ViscousBuilder::shrink(_SolidData& theData) getMeshDS()->RemoveFreeNode( n, smDS, /*fromGroups=*/false ); } } + _periodicity->ClearPeriodic( F ); + // restore position and UV of target nodes gp_Pnt p; for ( size_t iS = 0; iS < subEOS.size(); ++iS ) @@ -11528,6 +11541,13 @@ bool _ViscousBuilder::prepareEdgeToShrink( _LayerEdge& edge, if ( !n2 ) return error(SMESH_Comment("Wrongly meshed EDGE ") << getMeshDS()->ShapeToIndex( E )); + if ( n2 == tgtNode ) // for 3D_mesh_GHS3D_01/B1 + { + // shrunk by other SOLID + edge.Set( _LayerEdge::SHRUNK ); // ??? + return true; + } + double uSrc = helper.GetNodeU( E, srcNode, n2 ); double uTgt = helper.GetNodeU( E, tgtNode, srcNode ); double u2 = helper.GetNodeU( E, n2, srcNode ); From 155c6427afd2f7517d00bb5e9cb56aa2da867ddf Mon Sep 17 00:00:00 2001 From: eap Date: Fri, 28 Feb 2020 16:06:43 +0300 Subject: [PATCH 54/60] #17336 [CEA 17333] Mesh offset generates a segmentation violation --- src/SMESHUtils/SMESH_Offset.cxx | 96 +++++++++++++++++++++++++++------ src/SMESH_I/SMESH_2smeshpy.cxx | 2 +- 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/SMESHUtils/SMESH_Offset.cxx b/src/SMESHUtils/SMESH_Offset.cxx index 34e3c9d26..71f53213d 100644 --- a/src/SMESHUtils/SMESH_Offset.cxx +++ b/src/SMESHUtils/SMESH_Offset.cxx @@ -48,7 +48,7 @@ namespace //-------------------------------------------------------------------------------- /*! * \brief Intersected face side storing a node created at this intersection - * and a intersected face + * and an intersected face */ struct CutLink { @@ -105,7 +105,7 @@ namespace int myIndex; // positive -> side index, negative -> State const SMDS_MeshElement* myFace; - enum State { _INTERNAL = -1, _COPLANAR = -2 }; + enum State { _INTERNAL = -1, _COPLANAR = -2, _PENDING = -3 }; void Set( const SMDS_MeshNode* Node1, const SMDS_MeshNode* Node2, @@ -1524,7 +1524,8 @@ namespace SMESH_MeshAlgos SMESH_MeshAlgos::GetBarycentricCoords( p2D( p ), p2D( nodes[0] ), p2D( nodes[1] ), p2D( nodes[2] ), bc1, bc2 ); - return ( 0. < bc1 && 0. < bc2 && bc1 + bc2 < 1. ); + //return ( 0. < bc1 && 0. < bc2 && bc1 + bc2 < 1. ); + return ( myTol < bc1 && myTol < bc2 && bc1 + bc2 + myTol < 1. ); } //================================================================================ @@ -1676,7 +1677,11 @@ namespace SMESH_MeshAlgos size_t limit = cf.myLinks.size() * cf.myLinks.size() * 2; - for ( size_t i1 = 3; i1 < cf.myLinks.size(); ++i1 ) + size_t i1 = 3; + while ( cf.myLinks[i1-1].IsInternal() && i1 > 0 ) + --i1; + + for ( ; i1 < cf.myLinks.size(); ++i1 ) { if ( !cf.myLinks[i1].IsInternal() ) continue; @@ -1998,6 +2003,28 @@ namespace SMESH_MeshAlgos myMesh->RemoveFreeElement( f ); } + // remove faces that are merged off + for ( cutFacesIt = myCutFaces.cbegin(); cutFacesIt != myCutFaces.cend(); ++cutFacesIt ) + { + const CutFace& cf = *cutFacesIt; + if ( !cf.myLinks.empty() || cf.myInitFace->IsNull() ) + continue; + + nodes.assign( cf.myInitFace->begin_nodes(), cf.myInitFace->end_nodes() ); + for ( size_t i = 0; i < nodes.size(); ++i ) + { + const SMDS_MeshNode* n = nodes[ i ]; + while ( myRemove2KeepNodes.IsBound( n )) + n = myRemove2KeepNodes( n ); + if ( n != nodes[ i ] && cf.myInitFace->GetNodeIndex( n ) >= 0 ) + { + theNew2OldFaces[ cf.myInitFace->GetID() ].first = 0; + myMesh->RemoveFreeElement( cf.myInitFace ); + break; + } + } + } + // remove faces connected to cut off parts of cf.myInitFace nodes.resize(2); @@ -2010,9 +2037,9 @@ namespace SMESH_MeshAlgos if ( nodes[0] != nodes[1] && myMesh->GetElementsByNodes( nodes, faces )) { - if ( cutOffLinks[i].myFace && - cutOffLinks[i].myIndex != EdgePart::_COPLANAR && - faces.size() == 2 ) + if ( // cutOffLinks[i].myFace && + cutOffLinks[i].myIndex != EdgePart::_COPLANAR && + faces.size() != 1 ) continue; for ( size_t iF = 0; iF < faces.size(); ++iF ) { @@ -2045,13 +2072,22 @@ namespace SMESH_MeshAlgos for ( size_t i = 0; i < touchedFaces.size(); ++i ) { const CutFace& cf = *touchedFaces[i]; + if ( cf.myInitFace->IsNull() ) + continue; int index = cf.myInitFace->GetID(); // index in theNew2OldFaces if ( !theNew2OldFaces[ index ].first ) continue; // already cut off + cf.InitLinks(); if ( !cf.ReplaceNodes( myRemove2KeepNodes )) - continue; // just keep as is + { + if ( cf.myLinks.size() == 3 && + cf.myInitFace->GetNodeIndex( cf.myLinks[0].myNode1 ) >= 0 && + cf.myInitFace->GetNodeIndex( cf.myLinks[1].myNode1 ) >= 0 && + cf.myInitFace->GetNodeIndex( cf.myLinks[2].myNode1 ) >= 0 ) + continue; // just keep as is + } if ( cf.myLinks.size() == 3 ) { @@ -2737,8 +2773,16 @@ namespace { theLoops.AddNewLoop(); theLoops.AddEdge( myLinks[0] ); - theLoops.AddEdge( myLinks[1] ); - theLoops.AddEdge( myLinks[2] ); + if ( myLinks[0].myNode2 == myLinks[1].myNode1 ) + { + theLoops.AddEdge( myLinks[1] ); + theLoops.AddEdge( myLinks[2] ); + } + else + { + theLoops.AddEdge( myLinks[2] ); + theLoops.AddEdge( myLinks[1] ); + } return; } @@ -2828,6 +2872,8 @@ namespace TLinkMap& theCutOffCoplanarLinks) const { EdgePart sideEdge; + boost::container::flat_set< const SMDS_MeshElement* > checkedCoplanar; + for ( size_t i = 0; i < myLinks.size(); ++i ) { if ( !myLinks[i].myFace ) @@ -2875,6 +2921,21 @@ namespace loop = theLoops.GetLoopOf( twin ); toErase = ( loop && !loop->myLinks.empty() ); } + + if ( toErase ) // do not erase if cutFace is connected to a co-planar cutFace + { + checkedCoplanar.clear(); + for ( size_t iE = 0; iE < myLinks.size() && toErase; ++iE ) + { + if ( !myLinks[iE].myFace || myLinks[iE].myIndex != EdgePart::_COPLANAR ) + continue; + bool isAdded = checkedCoplanar.insert( myLinks[iE].myFace ).second; + if ( !isAdded ) + continue; + toErase = SMESH_MeshAlgos::GetCommonNodes( myLinks[i ].myFace, + myLinks[iE].myFace ).size() < 1; + } + } } if ( toErase ) @@ -2886,8 +2947,8 @@ namespace { if ( !loop->myLinks[ iE ]->myFace && !loop->myLinks[ iE ]->IsInternal() )// && - // !loop->myLinks[ iE ]->myNode1->isMarked() && // cut nodes are marked - // !loop->myLinks[ iE ]->myNode2->isMarked() ) + // !loop->myLinks[ iE ]->myNode1->isMarked() && // cut nodes are marked + // !loop->myLinks[ iE ]->myNode2->isMarked() ) { int i = loop->myLinks[ iE ]->myIndex; sideEdge.Set( myInitFace->GetNode ( i ), @@ -2963,7 +3024,9 @@ namespace if ( myIndex + e.myIndex == _COPLANAR + _INTERNAL ) { //check if the faces are connected - int nbCommonNodes = SMESH_MeshAlgos::GetCommonNodes( e.myFace, myFace ).size(); + int nbCommonNodes = 0; + if ( e.myFace && myFace ) + nbCommonNodes = SMESH_MeshAlgos::GetCommonNodes( e.myFace, myFace ).size(); bool toReplace = (( myIndex == _INTERNAL && nbCommonNodes > 1 ) || ( myIndex == _COPLANAR && nbCommonNodes < 2 )); if ( toReplace ) @@ -3070,7 +3133,7 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, { p.Set( nodes[i] ); double dist = ( pPrev - p ).SquareModulus(); - if ( dist > std::numeric_limits::min() ) + if ( dist < minNodeDist && dist > std::numeric_limits::min() ) minNodeDist = dist; pPrev = p; } @@ -3240,7 +3303,10 @@ SMDS_Mesh* SMESH_MeshAlgos::MakeOffset( SMDS_ElemIteratorPtr theFaceIt, break; if ( !isConcaveNode2 && nbCommonNodes > 0 ) - continue; + { + if ( normals[ newFace->GetID() ] * normals[ closeFace->GetID() ] < 1.0 ) + continue; // not co-planar + } } intersector.Cut( newFace, closeFace, nbCommonNodes ); diff --git a/src/SMESH_I/SMESH_2smeshpy.cxx b/src/SMESH_I/SMESH_2smeshpy.cxx index 689f49861..821eedd41 100644 --- a/src/SMESH_I/SMESH_2smeshpy.cxx +++ b/src/SMESH_I/SMESH_2smeshpy.cxx @@ -2495,7 +2495,7 @@ void _pyMeshEditor::Process( const Handle(_pyCommand)& theCommand) "ExtrusionByNormal", "ExtrusionSweepObject2D","ExtrusionAlongPath","ExtrusionAlongPathObject", "ExtrusionAlongPathX","ExtrusionAlongPathObject1D","ExtrusionAlongPathObject2D", "ExtrusionSweepObjects","RotationSweepObjects","ExtrusionAlongPathObjects", - "Mirror","MirrorObject","Translate","TranslateObject","Rotate","RotateObject", + "Mirror","MirrorObject","Translate","TranslateObject","Rotate","RotateObject","Offset", "FindCoincidentNodes","MergeNodes","FindEqualElements","FillHole", "MergeElements","MergeEqualElements","SewFreeBorders","SewConformFreeBorders", "FindCoincidentFreeBorders", "SewCoincidentFreeBorders", From b3f2b68fc2763fbe63d15a417e285557a6a7ffc5 Mon Sep 17 00:00:00 2001 From: eap Date: Mon, 2 Mar 2020 21:07:59 +0300 Subject: [PATCH 55/60] #18782 EDF 20946 - Free nodes with biquadratic --- src/Controls/SMESH_Controls.cxx | 8 ++++---- src/SMESH/SMESH_MeshEditor.cxx | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Controls/SMESH_Controls.cxx b/src/Controls/SMESH_Controls.cxx index fc7ba629b..9a3f828fb 100644 --- a/src/Controls/SMESH_Controls.cxx +++ b/src/Controls/SMESH_Controls.cxx @@ -1081,7 +1081,7 @@ double AspectRatio3D::GetValue( const TSequenceOfXYZ& P ) case 5:{ { gp_XYZ aXYZ[4] = {P( 1 ),P( 2 ),P( 3 ),P( 5 )}; - aQuality = std::max(GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[4])),aQuality); + aQuality = GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[4])); } { gp_XYZ aXYZ[4] = {P( 1 ),P( 3 ),P( 4 ),P( 5 )}; @@ -1100,7 +1100,7 @@ double AspectRatio3D::GetValue( const TSequenceOfXYZ& P ) case 6:{ { gp_XYZ aXYZ[4] = {P( 1 ),P( 2 ),P( 4 ),P( 6 )}; - aQuality = std::max(GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[4])),aQuality); + aQuality = GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[4])); } { gp_XYZ aXYZ[4] = {P( 1 ),P( 2 ),P( 4 ),P( 3 )}; @@ -1127,7 +1127,7 @@ double AspectRatio3D::GetValue( const TSequenceOfXYZ& P ) case 8:{ { gp_XYZ aXYZ[4] = {P( 1 ),P( 2 ),P( 5 ),P( 3 )}; - aQuality = std::max(GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[4])),aQuality); + aQuality = GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[4])); } { gp_XYZ aXYZ[4] = {P( 1 ),P( 2 ),P( 5 ),P( 4 )}; @@ -1262,7 +1262,7 @@ double AspectRatio3D::GetValue( const TSequenceOfXYZ& P ) case 12: { gp_XYZ aXYZ[8] = {P( 1 ),P( 2 ),P( 4 ),P( 5 ),P( 7 ),P( 8 ),P( 10 ),P( 11 )}; - aQuality = std::max(GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[8])),aQuality); + aQuality = GetValue(TSequenceOfXYZ(&aXYZ[0],&aXYZ[8])); } { gp_XYZ aXYZ[8] = {P( 2 ),P( 3 ),P( 5 ),P( 6 ),P( 8 ),P( 9 ),P( 11 ),P( 12 )}; diff --git a/src/SMESH/SMESH_MeshEditor.cxx b/src/SMESH/SMESH_MeshEditor.cxx index 2104c7920..b8a4dc81d 100644 --- a/src/SMESH/SMESH_MeshEditor.cxx +++ b/src/SMESH/SMESH_MeshEditor.cxx @@ -296,6 +296,18 @@ SMESH_MeshEditor::AddElement(const vector & node, node[8], node[9], node[10],node[11], node[12],node[13],node[14] ); } + else if (nbnode == 18) { + if ( ID >= 1 ) e = mesh->AddVolumeWithID(node[0], node[1], node[2], node[3], + node[4], node[5], node[6], node[7], + node[8], node[9], node[10],node[11], + node[12],node[13],node[14], + node[15],node[16],node[17],ID ); + else e = mesh->AddVolume (node[0], node[1], node[2], node[3], + node[4], node[5], node[6], node[7], + node[8], node[9], node[10],node[11], + node[12],node[13],node[14], + node[15],node[16],node[17] ); + } else if (nbnode == 20) { if ( ID >= 1 ) e = mesh->AddVolumeWithID(node[0], node[1], node[2], node[3], node[4], node[5], node[6], node[7], From d46c8ad01481eb9298958859eb020fdaf54a55a9 Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 3 Mar 2020 14:57:40 +0300 Subject: [PATCH 56/60] Fix regression of cartesian_algo.py example --- src/StdMeshers/StdMeshers_Cartesian_3D.cxx | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx index 96bbf63f5..079b2b3a9 100644 --- a/src/StdMeshers/StdMeshers_Cartesian_3D.cxx +++ b/src/StdMeshers/StdMeshers_Cartesian_3D.cxx @@ -375,7 +375,6 @@ namespace bool _toUseThresholdForInternalFaces; double _sizeThreshold; - vector< TGeomID > _shapeIDs; // returned by Hexahedron::getSolids() SMESH_MesherHelper* _helper; size_t CellIndex( size_t i, size_t j, size_t k ) const @@ -867,7 +866,7 @@ namespace void init( size_t i ); void setIJK( size_t i ); bool compute( const Solid* solid, const IsInternalFlag intFlag ); - const vector< TGeomID >& getSolids(); + size_t getSolids( TGeomID ids[] ); bool isCutByInternalFace( IsInternalFlag & maxFlag ); void addEdges(SMESH_MesherHelper& helper, vector< Hexahedron* >& intersectedHex, @@ -2175,13 +2174,12 @@ namespace /*! * \brief Return IDs of SOLIDs interfering with this Hexahedron */ - const vector< TGeomID >& Hexahedron::getSolids() + size_t Hexahedron::getSolids( TGeomID ids[] ) { - _grid->_shapeIDs.clear(); if ( _grid->_geometry.IsOneSolid() ) { - _grid->_shapeIDs.push_back( _grid->GetSolid()->ID() ); - return _grid->_shapeIDs; + ids[0] = _grid->GetSolid()->ID(); + return 1; } // count intersection points belonging to each SOLID TID2Nb id2NbPoints; @@ -2231,12 +2229,12 @@ namespace insertAndIncrement( solidIDs[i], id2NbPoints ); } - _grid->_shapeIDs.reserve( id2NbPoints.size() ); + size_t nbSolids = 0; for ( TID2Nb::iterator id2nb = id2NbPoints.begin(); id2nb != id2NbPoints.end(); ++id2nb ) if ( id2nb->second >= 3 ) - _grid->_shapeIDs.push_back( id2nb->first ); + ids[ nbSolids++ ] = id2nb->first; - return _grid->_shapeIDs; + return nbSolids; } //================================================================================ @@ -2578,14 +2576,15 @@ namespace solid = _grid->GetSolid(); if ( !_grid->_geometry.IsOneSolid() ) { - const vector< TGeomID >& solidIDs = getSolids(); - if ( solidIDs.size() > 1 ) + TGeomID solidIDs[20]; + size_t nbSolids = getSolids( solidIDs ); + if ( nbSolids > 1 ) { - for ( size_t i = 0; i < solidIDs.size(); ++i ) + for ( size_t i = 0; i < nbSolids; ++i ) { solid = _grid->GetSolid( solidIDs[i] ); ComputeElements( solid, i ); - if ( !_volumeDefs._nodes.empty() && i < solidIDs.size() - 1 ) + if ( !_volumeDefs._nodes.empty() && i < nbSolids - 1 ) _volumeDefs.SetNext( new _volumeDef( _volumeDefs )); } return; @@ -3238,6 +3237,7 @@ namespace // add not split hexahedra to the mesh int nbAdded = 0; + TGeomID solidIDs[20]; vector< Hexahedron* > intHexa; intHexa.reserve( nbIntHex ); vector< const SMDS_MeshElement* > boundaryVolumes; boundaryVolumes.reserve( nbIntHex * 1.1 ); for ( size_t i = 0; i < allHexa.size(); ++i ) @@ -3273,7 +3273,8 @@ namespace } else { - solidID = getSolids()[0]; + getSolids( solidIDs ); + solidID = solidIDs[0]; } mesh->SetMeshElementOnShape( el, solidID ); ++nbAdded; From 0f4abfb68b7842023a67f4895ea05f1bd6651eb3 Mon Sep 17 00:00:00 2001 From: eap Date: Wed, 4 Mar 2020 18:08:41 +0300 Subject: [PATCH 57/60] Fix regression of smesh/3D_mesh_Extrusion_00/A3 --- src/StdMeshers/StdMeshers_Prism_3D.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/StdMeshers/StdMeshers_Prism_3D.cxx b/src/StdMeshers/StdMeshers_Prism_3D.cxx index 07666af9f..e4aa9d846 100644 --- a/src/StdMeshers/StdMeshers_Prism_3D.cxx +++ b/src/StdMeshers/StdMeshers_Prism_3D.cxx @@ -5222,10 +5222,10 @@ bool StdMeshers_Sweeper::ComputeNodesByTrsf( const double tol, for ( ++zS, --zT; zS < zTgt; ++zS, --zT ) // vertical loop on layers { // invert transformation - if ( !trsfOfLayer[ zS+1 ].Invert() ) - trsfOfLayer[ zS+1 ] = NSProjUtils::TrsfFinder3D(); // to recompute - if ( !trsfOfLayer[ zT-1 ].Invert() ) - trsfOfLayer[ zT-1 ] = NSProjUtils::TrsfFinder3D(); + //if ( !trsfOfLayer[ zS+1 ].Invert() ) + trsfOfLayer[ zS+1 ] = NSProjUtils::TrsfFinder3D(); // to recompute + //if ( !trsfOfLayer[ zT-1 ].Invert() ) + trsfOfLayer[ zT-1 ] = NSProjUtils::TrsfFinder3D(); // project internal nodes and compute bnd error for ( size_t iP = 0; iP < myBndColumns.size(); ++iP ) From 8fa9e8c05f714b51b683fa3e14a7588e96c61b35 Mon Sep 17 00:00:00 2001 From: Christian Van Wambeke Date: Fri, 28 Feb 2020 09:57:30 +0100 Subject: [PATCH 58/60] fix #7051 libmesh5.c/h DriverGMF_Read.cxx/hxx --- src/DriverGMF/DriverGMF_Read.cxx | 62 ++++++++++++++++++++++++++++---- src/DriverGMF/DriverGMF_Read.hxx | 6 ++++ src/DriverGMF/libmesh5.c | 6 ++-- src/DriverGMF/libmesh5.h | 6 ++-- 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/DriverGMF/DriverGMF_Read.cxx b/src/DriverGMF/DriverGMF_Read.cxx index d0a698fb4..fe036239c 100644 --- a/src/DriverGMF/DriverGMF_Read.cxx +++ b/src/DriverGMF/DriverGMF_Read.cxx @@ -43,7 +43,8 @@ extern "C" // -------------------------------------------------------------------------------- DriverGMF_Read::DriverGMF_Read(): Driver_SMESHDS_Mesh(), - _makeRequiredGroups( true ) + _makeRequiredGroups( true ), + _makeFaultGroups( true ) { } // -------------------------------------------------------------------------------- @@ -401,10 +402,9 @@ Driver_Mesh::Status DriverGMF_Read::Perform() } } - // Read required entities into groups + // Read some entities into groups + // see MeshGems/Docs/meshgems_formats_description.pdf - if ( _makeRequiredGroups ) - { // get ids of existing groups std::set< int > groupIDs; const std::set& groups = myMesh->GetGroups(); @@ -413,14 +413,20 @@ Driver_Mesh::Status DriverGMF_Read::Perform() groupIDs.insert( (*grIter)->GetID() ); if ( groupIDs.empty() ) groupIDs.insert( 0 ); + // Read required entities into groups + if ( _makeRequiredGroups ) + { + const int kes[4][3] = { { GmfRequiredVertices, SMDSAbs_Node, nodeIDShift }, { GmfRequiredEdges, SMDSAbs_Edge, edgeIDShift }, { GmfRequiredTriangles, SMDSAbs_Face, triaIDShift }, - { GmfRequiredQuadrilaterals,SMDSAbs_Face, quadIDShift }}; + { GmfRequiredQuadrilaterals,SMDSAbs_Face, quadIDShift } + }; const char* names[4] = { "_required_Vertices" , "_required_Edges" , "_required_Triangles" , - "_required_Quadrilaterals" }; + "_required_Quadrilaterals" + }; for ( int i = 0; i < 4; ++i ) { int gmfKwd = kes[i][0]; @@ -444,6 +450,50 @@ Driver_Mesh::Status DriverGMF_Read::Perform() } } + // Read fault entities into groups + if ( _makeFaultGroups ) + { + + const int kes[7][3] = { { GmfFault_SmallTri, SMDSAbs_Face, triaIDShift }, + { GmfFault_BadShape, SMDSAbs_Face, triaIDShift }, + { GmfFault_Overlap, SMDSAbs_Face, triaIDShift }, + { GmfFault_Inter, SMDSAbs_Face, triaIDShift }, + { GmfFault_NearTri, SMDSAbs_Face, triaIDShift }, + { GmfFault_FreeEdge, SMDSAbs_Face, triaIDShift }, + { GmfFault_MultipleEdge, SMDSAbs_Face, triaIDShift } + }; + const char* names[7] = { "Fault_SmallTri", + "Fault_BadShape", + "Fault_Overlap", + "Fault_Inter", + "Fault_NearTri", + "Fault_FreeEdge", + "Fault_MultipleEdge" + }; + for ( int i = 0; i < 7; ++i ) + { + int gmfKwd = kes[i][0]; + SMDSAbs_ElementType entity = (SMDSAbs_ElementType) kes[i][1]; + int shift = kes[i][2]; + if ( int nb = GmfStatKwd(meshID, gmfKwd)) + { + const int newID = *groupIDs.rbegin() + 1; + groupIDs.insert( newID ); + SMESHDS_Group* group = new SMESHDS_Group( newID, myMesh, entity ); + group->SetStoreName( names[i] ); + myMesh->AddGroup( group ); + + GmfGotoKwd(meshID, gmfKwd); + for ( int i = 0; i < nb; ++i ) + { + GmfGetLin(meshID, gmfKwd, &iN[0] ); + group->Add( shift + iN[0] ); + } + } + } + } + + myMesh->Modified(); myMesh->CompactMesh(); diff --git a/src/DriverGMF/DriverGMF_Read.hxx b/src/DriverGMF/DriverGMF_Read.hxx index 866f06fb4..7e7b64e96 100644 --- a/src/DriverGMF/DriverGMF_Read.hxx +++ b/src/DriverGMF/DriverGMF_Read.hxx @@ -50,6 +50,11 @@ public: _makeRequiredGroups = theMakeRequiredGroups; } + void SetMakeFaultGroups( bool theMakeFaultGroups ) + { + _makeFaultGroups = theMakeFaultGroups; + } + virtual Status Perform(); private: @@ -57,6 +62,7 @@ public: Status storeBadNodeIds(const char* gmfKwd, int elemNb, int nb, ...); bool _makeRequiredGroups; + bool _makeFaultGroups; }; diff --git a/src/DriverGMF/libmesh5.c b/src/DriverGMF/libmesh5.c index 889b69747..2c889ccc2 100644 --- a/src/DriverGMF/libmesh5.c +++ b/src/DriverGMF/libmesh5.c @@ -78,6 +78,7 @@ typedef struct static int GmfIniFlg=0; static GmfMshSct *GmfMshTab[ MaxMsh + 1 ]; +// see MeshGems/Docs/meshgems_formats_description.pdf static const char *GmfKwdFmt[ GmfMaxKwd + 1 ][4] = { {"Reserved", "", "", ""}, {"MeshVersionFormatted", "", "", "i"}, @@ -159,7 +160,8 @@ static const char *GmfKwdFmt[ GmfMaxKwd + 1 ][4] = {"Iterations", "","","i"}, {"Time", "","","r"}, {"Fault_SmallTri", "Fault_SmallTri","i","i"}, - {"CoarseHexahedra", "CoarseHexahedron", "i", "i"} + {"CoarseHexahedra", "CoarseHexahedron", "i", "i"}, + {"Fault_MultipleEdge", "Fault_MultipleEdge", "i", "i"} }; @@ -1062,7 +1064,7 @@ static int ScaKwdTab(GmfMshSct *msh) { /* Search which kwd code this string is associated with, then get its header and save the current position in file (just before the data) */ - + // printf("libmesh ScaKwdTab %s\n", str); for(KwdCod=1; KwdCod<= GmfMaxKwd; KwdCod++) if(!strcmp(str, GmfKwdFmt[ KwdCod ][0])) { diff --git a/src/DriverGMF/libmesh5.h b/src/DriverGMF/libmesh5.h index 066853f52..6345d1df3 100644 --- a/src/DriverGMF/libmesh5.h +++ b/src/DriverGMF/libmesh5.h @@ -22,7 +22,7 @@ #define GmfStrSiz 1024 #define GmfMaxTyp 1000 -#define GmfMaxKwd 80 +#define GmfMaxKwd 81 #define GmfMshVer 1 #define GmfRead 1 #define GmfWrite 2 @@ -33,6 +33,7 @@ #define GmfFloat 1 #define GmfDouble 2 +// see MeshGems/Docs/meshgems_formats_description.pdf enum GmfKwdCod { GmfReserved1, \ @@ -115,7 +116,8 @@ enum GmfKwdCod GmfIterations, \ GmfTime, \ GmfFault_SmallTri, \ - GmfCoarseHexahedra + GmfCoarseHexahedra, \ + GmfFault_MultipleEdge }; From deb6fe0827edd31f7c03d1e6b305f6d538e22120 Mon Sep 17 00:00:00 2001 From: Pascal Obry Date: Thu, 5 Mar 2020 10:03:37 +0100 Subject: [PATCH 59/60] Do not use Python 3.6 constructs. We must keep Python 3.5 compatibility as this is the latest version on Debian 9. --- bin/smesh_setenv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/smesh_setenv.py b/bin/smesh_setenv.py index 6826ab0ce..578a00e3b 100644 --- a/bin/smesh_setenv.py +++ b/bin/smesh_setenv.py @@ -51,7 +51,7 @@ def set_env(args): for resource_file in [i for i in os.listdir(resource_dir) \ if osp.isfile(os.path.join(resource_dir, i))]: # look for resource file (XML) to extract valid plugin name - if resource_file.lower() == f'{plugin_lname}.xml': + if resource_file.lower() == '{plugin_lname}.xml'.format(plugin_lname=plugin_lname): try: # get plugin name from 'resources' attribute of 'meshers-group' xml node # as name extracted from environment variable can be in wrong case From 54db132ba15eba64f6ea4a6f2b5644313e56ff1c Mon Sep 17 00:00:00 2001 From: eap Date: Tue, 10 Mar 2020 17:11:19 +0300 Subject: [PATCH 60/60] Fix SIGSEGV on imps_09/K0 Not sure about "continue;" --- src/SMESHUtils/SMESH_Slot.cxx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/SMESHUtils/SMESH_Slot.cxx b/src/SMESHUtils/SMESH_Slot.cxx index 00521c0b5..00c7e6ed2 100644 --- a/src/SMESHUtils/SMESH_Slot.cxx +++ b/src/SMESHUtils/SMESH_Slot.cxx @@ -836,20 +836,22 @@ SMESH_MeshAlgos::MakeSlot( SMDS_ElemIteratorPtr theSegmentIt, // 2) double minCutDist = theWidth; gp_XYZ projection, closestProj; - int iCut; - for ( size_t iC = 0; iC < closeSeg[iP]->myCuts.size(); ++iC ) + int iCut = -1; + for ( size_t iC2 = 0; iC2 < closeSeg[iP]->myCuts.size(); ++iC2 ) { - double cutDist = closeSeg[iP]->myCuts[iC].SquareDistance( intPnt[iP].myNode, + double cutDist = closeSeg[iP]->myCuts[iC2].SquareDistance( intPnt[iP].myNode, projection ); if ( cutDist < minCutDist ) { closestProj = projection; minCutDist = cutDist; - iCut = iC; + iCut = iC2; + if ( minCutDist < tol * tol ) + break; } - if ( minCutDist < tol * tol ) - break; } + if ( iCut < 0 ) + continue; // ??? double d1 = SMESH_MeshAlgos::GetDistance( neighborSeg->myEdge, closeSeg[iP]->myCuts[iCut][0].myNode ); double d2 = SMESH_MeshAlgos::GetDistance( neighborSeg->myEdge,