From: cconopoima Date: Sat, 25 May 2024 12:30:53 +0000 (+0100) Subject: Save porting status of presentation. X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fcce%2Fnetgen6.2.2X;p=plugins%2Fnetgenplugin.git Save porting status of presentation. --- diff --git a/src/NETGENPlugin/NETGENPlugin_Mesher.cxx b/src/NETGENPlugin/NETGENPlugin_Mesher.cxx index 7a49e1f..75db09f 100644 --- a/src/NETGENPlugin/NETGENPlugin_Mesher.cxx +++ b/src/NETGENPlugin/NETGENPlugin_Mesher.cxx @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -1019,7 +1020,7 @@ void NETGENPlugin_Mesher::PrepareOCCgeometry(netgen::OCCGeometry& occgeo, else if (meshedSM) { const int dim = SMESH_Gen::GetShapeDim( shape ); - meshedSM[ dim ].push_back( sm ); + meshedSM[ dim ].push_back( sm ); } } } @@ -1034,6 +1035,118 @@ void NETGENPlugin_Mesher::PrepareOCCgeometry(netgen::OCCGeometry& occgeo, occgeo.BuildFMapFromPrefilled(); } +//================================================================================ +/*! + * \brief Initialize netgen::OCCGeometry with OCCT shape + */ +//================================================================================ + +void NETGENPlugin_Mesher::PrepareOCCgeometry2(netgen::OCCGeometry& occgeo, + const TopoDS_Shape& shape, + SMESH_Mesh& mesh, + list< SMESH_subMesh* > * meshedSM, + NETGENPlugin_Internals* intern) +{ + updateTriangulation( shape ); + + Bnd_Box bb; + // BRepBndLib::Add (shape, bb); + GEOMUtils::PreciseBoundingBox(shape,bb); + double x1,y1,z1,x2,y2,z2; + bb.Get (x1,y1,z1,x2,y2,z2); + netgen::Point<3> p1 = netgen::Point<3> (x1,y1,z1); + netgen::Point<3> p2 = netgen::Point<3> (x2,y2,z2); + occgeo.boundingbox = netgen::Box<3> (p1,p2); + + occgeo.shape = shape; + occgeo.changed = 1; + + // fill maps of shapes of occgeo with not yet meshed subshapes + + // get root submeshes + list< SMESH_subMesh* > rootSM; + const int shapeID = mesh.GetMeshDS()->ShapeToIndex( shape ); + if ( shapeID > 0 ) { // SMESH_subMesh with ID 0 may exist, don't use it! + rootSM.push_back( mesh.GetSubMesh( shape )); + } + else { + for ( TopoDS_Iterator it( shape ); it.More(); it.Next() ) + rootSM.push_back( mesh.GetSubMesh( it.Value() )); + } + + int totNbFaces = 0; + + // add subshapes of empty submeshes + list< SMESH_subMesh* >::iterator rootIt = rootSM.begin(), rootEnd = rootSM.end(); + for ( ; rootIt != rootEnd; ++rootIt ) { + SMESH_subMesh * root = *rootIt; + SMESH_subMeshIteratorPtr smIt = root->getDependsOnIterator(/*includeSelf=*/true, + /*complexShapeFirst=*/true); + // to find a right orientation of subshapes (PAL20462) + TopTools_IndexedMapOfShape subShapes; + TopExp::MapShapes(root->GetSubShape(), subShapes); + while ( smIt->more() ) + { + SMESH_subMesh* sm = smIt->next(); + TopoDS_Shape shape = sm->GetSubShape(); + totNbFaces += ( shape.ShapeType() == TopAbs_FACE ); + if ( intern && intern->isShapeToPrecompute( shape )) + continue; + + if ( shape.ShapeType() != TopAbs_VERTEX ) + shape = subShapes( subShapes.FindIndex( shape ));// shape -> index -> oriented shape + if ( shape.Orientation() >= TopAbs_INTERNAL ) + shape.Orientation( TopAbs_FORWARD ); // issue 0020676 + + switch ( shape.ShapeType() ) { + case TopAbs_FACE : occgeo.fmap.Add( shape ); break; + case TopAbs_EDGE : occgeo.emap.Add( shape ); break; + case TopAbs_VERTEX: occgeo.vmap.Add( shape ); break; + case TopAbs_SOLID :occgeo.somap.Add( shape ); break; + default:; + } + + // collect submeshes of meshed shapes + if ( meshedSM && !sm->IsEmpty()) + { + const int dim = SMESH_Gen::GetShapeDim( shape ); + meshedSM[ dim ].push_back( sm ); + + TopoDS_Shape shape = sm->GetSubShape(); + switch ( shape.ShapeType() ) { + /* Fill map with vertex of premeshed elements*/ + case TopAbs_EDGE : { + /* Rules to create geometric elements see TopoDS_Builder.cxx */ + /* Vertex can be added to edges!*/ + SMDS_NodeIteratorPtr nodeIt = sm->GetSubMeshDS()->GetNodes(); + + while ( nodeIt->more() ) + { + printf("Including geom vertex associated to mesh nodes!\n\n"); + const SMDS_MeshNode* node = nodeIt->next(); + gp_Pnt point(node->X(),node->Y(),node->Z()); + TopoDS_Vertex v = BRepBuilderAPI_MakeVertex( point ); + /* Optionaly we could associate the edge to the edge itself too!*/ + occgeo.vmap.Add( v ); + } + break; + } + } + } + } + } + + occgeo.facemeshstatus.SetSize (totNbFaces); + occgeo.facemeshstatus = 0; + occgeo.face_maxh_modified.SetSize(totNbFaces); + occgeo.face_maxh_modified = 0; + occgeo.face_maxh.SetSize(totNbFaces); + occgeo.face_maxh = netgen::mparam.maxh; + + // + occgeo.BuildFMapFromPrefilled(); +} + //================================================================================ /*! * \brief Return a default min size value suitable for the given geometry. @@ -1163,7 +1276,7 @@ bool NETGENPlugin_Mesher::FillNgMesh(netgen::OCCGeometry& occgeom, switch ( sm->GetSubShape().ShapeType() ) { case TopAbs_EDGE: { // EDGE - + // ---------------------- TopoDS_Edge geomEdge = TopoDS::Edge( sm->GetSubShape() ); if ( geomEdge.Orientation() >= TopAbs_INTERNAL ) geomEdge.Orientation( TopAbs_FORWARD ); // issue 0020676 @@ -1187,7 +1300,7 @@ bool NETGENPlugin_Mesher::FillNgMesh(netgen::OCCGeometry& occgeom, // get all meshed EDGEs of the FACE connected to geomEdge (issue 0021140) helper.SetSubShape( face ); list< TopoDS_Edge > edges = getConnectedEdges( geomEdge, face, computedSM, helper, - visitedEdgeSM2Faces ); + visitedEdgeSM2Faces ); if ( edges.empty() ) continue; // wrong ancestor? @@ -1210,193 +1323,127 @@ bool NETGENPlugin_Mesher::FillNgMesh(netgen::OCCGeometry& occgeom, if ( points.empty() ) return false; // invalid node params? smIdType i, nbSeg = fSide.NbSegments(); - for ( i = 0; i < nbSeg; ++i ) - { - const SMDS_MeshNode * n0 = points[ i ].node; - const SMDS_MeshNode * n1 = points[ i+1 ].node; - double size = SMESH_TNodeXYZ( n0 ).Distance( n1 ); - netgen::Point3d p0(n0->X(), n0->Y(), n0->Z()); - netgen::Point3d p1(n1->X(), n1->Y(), n1->Z()); - ngMesh.RestrictLocalHLine( p0, p1, size, true ); - } - - } -/////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////// - { - /* - // ---------------------- - TopoDS_Edge geomEdge = TopoDS::Edge( sm->GetSubShape() ); - if ( geomEdge.Orientation() >= TopAbs_INTERNAL ) - geomEdge.Orientation( TopAbs_FORWARD ); // issue 0020676 - - // Add ng segments for each not meshed FACE the EDGE bounds - PShapeIteratorPtr fIt = helper.GetAncestors( geomEdge, *sm->GetFather(), TopAbs_FACE ); - while ( const TopoDS_Shape * anc = fIt->next() ) - { - faceNgID = occgeom.fmap.FindIndex( *anc ); - if ( faceNgID < 1 ) - continue; // meshed face - - int faceSMDSId = meshDS->ShapeToIndex( *anc ); - if ( visitedEdgeSM2Faces[ sm ].count( faceSMDSId )) - continue; // already treated EDGE - - TopoDS_Face face = TopoDS::Face( occgeom.fmap( faceNgID )); - if ( face.Orientation() >= TopAbs_INTERNAL ) - face.Orientation( TopAbs_FORWARD ); // issue 0020676 - - // get all meshed EDGEs of the FACE connected to geomEdge (issue 0021140) - helper.SetSubShape( face ); - list< TopoDS_Edge > edges = getConnectedEdges( geomEdge, face, computedSM, helper, - visitedEdgeSM2Faces ); - if ( edges.empty() ) - continue; // wrong ancestor? - - // find out orientation of within - TopoDS_Edge eNotSeam = edges.front(); - if ( helper.HasSeam() ) - { - list< TopoDS_Edge >::iterator eIt = edges.begin(); - while ( helper.IsRealSeam( *eIt )) ++eIt; - if ( eIt != edges.end() ) - eNotSeam = *eIt; - } - TopAbs_Orientation fOri = helper.GetSubShapeOri( face, eNotSeam ); - bool isForwad = ( fOri == eNotSeam.Orientation() || fOri >= TopAbs_INTERNAL ); - // get all nodes from connected - const bool skipMedium = netgen::mparam.secondorder;//smDS->IsQuadratic(); - StdMeshers_FaceSide fSide( face, edges, _mesh, isForwad, skipMedium, &helper ); - const vector& points = fSide.GetUVPtStruct(); - if ( points.empty() ) - return false; // invalid node params? - smIdType i, nbSeg = fSide.NbSegments(); + // remember EDGEs of fSide to treat only once + for ( int iE = 0; iE < fSide.NbEdges(); ++iE ) + visitedEdgeSM2Faces[ helper.GetMesh()->GetSubMesh( fSide.Edge(iE )) ].insert(faceSMDSId); - // remember EDGEs of fSide to treat only once - for ( int iE = 0; iE < fSide.NbEdges(); ++iE ) - visitedEdgeSM2Faces[ helper.GetMesh()->GetSubMesh( fSide.Edge(iE )) ].insert(faceSMDSId); + double otherSeamParam = 0; + bool isSeam = false; - double otherSeamParam = 0; - bool isSeam = false; + // add segments - // add segments + int prevNgId = ngNodeId( points[0].node, ngMesh, nodeNgIdMap ); - int prevNgId = ngNodeId( points[0].node, ngMesh, nodeNgIdMap ); + for ( i = 0; i < nbSeg; ++i ) + { + const UVPtStruct& p1 = points[ i ]; + const UVPtStruct& p2 = points[ i+1 ]; - for ( i = 0; i < nbSeg; ++i ) + if ( p1.node->GetPosition()->GetTypeOfPosition() == SMDS_TOP_VERTEX ) //an EDGE begins + { + isSeam = false; + if ( helper.IsRealSeam( p1.node->GetShapeID() )) { - const UVPtStruct& p1 = points[ i ]; - const UVPtStruct& p2 = points[ i+1 ]; - - if ( p1.node->GetPosition()->GetTypeOfPosition() == SMDS_TOP_VERTEX ) //an EDGE begins - { - isSeam = false; - if ( helper.IsRealSeam( p1.node->GetShapeID() )) - { - TopoDS_Edge e = fSide.Edge( fSide.EdgeIndex( 0.5 * ( p1.normParam + p2.normParam ))); - isSeam = helper.IsRealSeam( e ); - if ( isSeam ) - { - otherSeamParam = helper.GetOtherParam( helper.GetPeriodicIndex() & 1 ? p2.u : p2.v ); - } - } - } - netgen::Segment seg; - // ng node ids - seg[0] = prevNgId; - seg[1] = prevNgId = ngNodeId( p2.node, ngMesh, nodeNgIdMap ); - seg.edgenr = countEdgeId+1; - seg.si = countEdgeId+1; - seg.epgeominfo[0].edgenr = countEdgeId; - seg.epgeominfo[1].edgenr = countEdgeId; - // node param on curve - seg.epgeominfo[ 0 ].dist = p1.param; - seg.epgeominfo[ 1 ].dist = p2.param; - // uv on face - seg.epgeominfo[ 0 ].u = p1.u; - seg.epgeominfo[ 0 ].v = p1.v; - seg.epgeominfo[ 1 ].u = p2.u; - seg.epgeominfo[ 1 ].v = p2.v; - - //geomEdge = fSide.Edge( fSide.EdgeIndex( 0.5 * ( p1.normParam + p2.normParam ))); - //seg.epgeominfo[ 0 ].edgenr = seg.epgeominfo[ 1 ].edgenr = occgeom.emap.FindIndex( geomEdge ); - - //seg.epgeominfo[ iEnd ].edgenr = edgeID; // = geom.emap.FindIndex(edge); - // seg.si = faceNgID; // = geom.fmap.FindIndex (face); - // seg.edgenr = ngMesh.GetNSeg() + 1; // segment id - ngMesh.AddSegment (seg); - - SMESH_TNodeXYZ np1( p1.node ), np2( p2.node ); - RestrictLocalSize( ngMesh, 0.5*(np1+np2), (np1-np2).Modulus() ); - - #ifdef DUMP_SEGMENTS - cout << "Segment: " << seg.edgenr << " on SMESH face " << meshDS->ShapeToIndex( face ) << endl - << "\tface index: " << seg.si << endl - << "\tp1: " << seg[0] << endl - << "\tp2: " << seg[1] << endl - << "\tp0 param: " << seg.epgeominfo[ 0 ].dist << endl - << "\tp0 uv: " << seg.epgeominfo[ 0 ].u <<", "<< seg.epgeominfo[ 0 ].v << endl - //<< "\tp0 edge: " << seg.epgeominfo[ 0 ].edgenr << endl - << "\tp1 param: " << seg.epgeominfo[ 1 ].dist << endl - << "\tp1 uv: " << seg.epgeominfo[ 1 ].u <<", "<< seg.epgeominfo[ 1 ].v << endl; - //<< "\tp1 edge: " << seg.epgeominfo[ 1 ].edgenr << endl; - #endif + TopoDS_Edge e = fSide.Edge( fSide.EdgeIndex( 0.5 * ( p1.normParam + p2.normParam ))); + isSeam = helper.IsRealSeam( e ); if ( isSeam ) { - if ( helper.GetPeriodicIndex() && 1 ) { - seg.epgeominfo[ 0 ].u = otherSeamParam; - seg.epgeominfo[ 1 ].u = otherSeamParam; - swap (seg.epgeominfo[0].v, seg.epgeominfo[1].v); - } else { - seg.epgeominfo[ 0 ].v = otherSeamParam; - seg.epgeominfo[ 1 ].v = otherSeamParam; - swap (seg.epgeominfo[0].u, seg.epgeominfo[1].u); - } - swap( seg[0], seg[1] ); - swap( seg.epgeominfo[0].dist, seg.epgeominfo[1].dist ); - seg.edgenr = ngMesh.GetNSeg() + 1; // segment id - ngMesh.AddSegment( seg ); - #ifdef DUMP_SEGMENTS - cout << "Segment: " << seg.edgenr << endl - << "\t is SEAM (reverse) of the previous. " - << " Other " << (helper.GetPeriodicIndex() && 1 ? "U" : "V") - << " = " << otherSeamParam << endl; - #endif - } - else if ( fOri == TopAbs_INTERNAL ) - { - swap( seg[0], seg[1] ); - swap( seg.epgeominfo[0], seg.epgeominfo[1] ); - seg.edgenr = ngMesh.GetNSeg() + 1; // segment id - ngMesh.AddSegment( seg ); - #ifdef DUMP_SEGMENTS - cout << "Segment: " << seg.edgenr << endl << "\t is REVERSE of the previous" << endl; - #endif + otherSeamParam = helper.GetOtherParam( helper.GetPeriodicIndex() & 1 ? p2.u : p2.v ); } } - } // loop on geomEdge ancestors + } + netgen::Segment seg; + // ng node ids + seg[0] = prevNgId; + seg[1] = prevNgId = ngNodeId( p2.node, ngMesh, nodeNgIdMap ); + // node param on curve + seg.epgeominfo[ 0 ].dist = p1.param; + seg.epgeominfo[ 1 ].dist = p2.param; + // uv on face + seg.epgeominfo[ 0 ].u = p1.u; + seg.epgeominfo[ 0 ].v = p1.v; + seg.epgeominfo[ 1 ].u = p2.u; + seg.epgeominfo[ 1 ].v = p2.v; + + geomEdge = fSide.Edge( fSide.EdgeIndex( 0.5 * ( p1.normParam + p2.normParam ))); + int edgeID = occgeom.GetEdge(geomEdge).nr; + + seg.epgeominfo[ 0 ].edgenr = edgeID; + seg.epgeominfo[ 1 ].edgenr = edgeID; + + //seg.epgeominfo[ iEnd ].edgenr = edgeID; // = geom.emap.FindIndex(edge); + // seg.si = faceNgID; // = geom.fmap.FindIndex (face); + // seg.edgenr = ngMesh.GetNSeg() + 1; // segment id + + seg.si = edgeID+1; // = geom.fmap.FindIndex (face); + seg.edgenr = edgeID+1; // segment id - if ( quadHelper ) // remember medium nodes of sub-meshes + ngMesh.AddSegment (seg); + + SMESH_TNodeXYZ np1( p1.node ), np2( p2.node ); + RestrictLocalSize( ngMesh, 0.5*(np1+np2), (np1-np2).Modulus() ); + +#ifdef DUMP_SEGMENTS + cout << "Segment: " << seg.edgenr << " on SMESH face " << meshDS->ShapeToIndex( face ) << endl + << "\tface index: " << seg.si << endl + << "\tp1: " << seg[0] << endl + << "\tp2: " << seg[1] << endl + << "\tp0 param: " << seg.epgeominfo[ 0 ].dist << endl + << "\tp0 uv: " << seg.epgeominfo[ 0 ].u <<", "<< seg.epgeominfo[ 0 ].v << endl + //<< "\tp0 edge: " << seg.epgeominfo[ 0 ].edgenr << endl + << "\tp1 param: " << seg.epgeominfo[ 1 ].dist << endl + << "\tp1 uv: " << seg.epgeominfo[ 1 ].u <<", "<< seg.epgeominfo[ 1 ].v << endl; + //<< "\tp1 edge: " << seg.epgeominfo[ 1 ].edgenr << endl; +#endif + if ( isSeam ) { - SMDS_ElemIteratorPtr edges = smDS->GetElements(); - while ( edges->more() ) - { - const SMDS_MeshElement* e = edges->next(); - if ( !quadHelper->AddTLinks( static_cast< const SMDS_MeshEdge*>( e ))) - break; + if ( helper.GetPeriodicIndex() && 1 ) { + seg.epgeominfo[ 0 ].u = otherSeamParam; + seg.epgeominfo[ 1 ].u = otherSeamParam; + swap (seg.epgeominfo[0].v, seg.epgeominfo[1].v); + } else { + seg.epgeominfo[ 0 ].v = otherSeamParam; + seg.epgeominfo[ 1 ].v = otherSeamParam; + swap (seg.epgeominfo[0].u, seg.epgeominfo[1].u); } + swap( seg[0], seg[1] ); + swap( seg.epgeominfo[0].dist, seg.epgeominfo[1].dist ); + seg.edgenr = ngMesh.GetNSeg() + 1; // segment id + ngMesh.AddSegment( seg ); +#ifdef DUMP_SEGMENTS + cout << "Segment: " << seg.edgenr << endl + << "\t is SEAM (reverse) of the previous. " + << " Other " << (helper.GetPeriodicIndex() && 1 ? "U" : "V") + << " = " << otherSeamParam << endl; +#endif } - - countEdgeId++; // Assume edge is iterate incrementally as done by the emap! - break; - - */ + else if ( fOri == TopAbs_INTERNAL ) + { + swap( seg[0], seg[1] ); + swap( seg.epgeominfo[0], seg.epgeominfo[1] ); + seg.edgenr = edgeID+1;//ngMesh.GetNSeg() + 1; // segment id + ngMesh.AddSegment( seg ); +#ifdef DUMP_SEGMENTS + cout << "Segment: " << seg.edgenr << endl << "\t is REVERSE of the previous" << endl; +#endif + } + } + } // loop on geomEdge ancestors - } /* scope of original code*/ - } // case TopAbs_EDGE + if ( quadHelper ) // remember medium nodes of sub-meshes + { + SMDS_ElemIteratorPtr edges = smDS->GetElements(); + while ( edges->more() ) + { + const SMDS_MeshElement* e = edges->next(); + if ( !quadHelper->AddTLinks( static_cast< const SMDS_MeshEdge*>( e ))) + break; + } + } + break; + } // case TopAbs_EDGE case TopAbs_FACE: { // FACE // ---------------------- const TopoDS_Face& geomFace = TopoDS::Face( sm->GetSubShape() ); @@ -2300,6 +2347,117 @@ void NETGENPlugin_Mesher::AddIntVerticesInSolids(const netgen::OCCGeometry& } +// Basicatly try to reproduce what is done in MeshFace method in basegeom.cpp class of netgen +// where Meshing2 class is instantiated with the face, paramters, bounding box +// then the mesh points are added to netgen and then the connectivity of the clossed chaing of nodes is feed into Meshing2 class +SMESH_ComputeErrorPtr +NETGENPlugin_Mesher::AddSegmentsToMesh3(netgen::Mesh& ngMesh, + netgen::OCCGeometry& geom, + const TSideVector& wires, + SMESH_MesherHelper& helper, + vector< const SMDS_MeshNode* > & nodeVec, + netgen::MeshingParameters & mparams, + const bool overrideMinH ) +{ + // ---------------------------- + // Check wires and count nodes + // ---------------------------- + smIdType nbNodes = 0; + for ( size_t iW = 0; iW < wires.size(); ++iW ) + { + StdMeshers_FaceSidePtr wire = wires[ iW ]; + const vector& uvPtVec = wire->GetUVPtStruct(); + if ((int) uvPtVec.size() != wire->NbPoints() ) + return SMESH_ComputeError::New(COMPERR_BAD_INPUT_MESH, + SMESH_Comment("Unexpected nb of points on wire ") << iW + << ": " << uvPtVec.size()<<" != "<NbPoints()); + nbNodes += wire->NbPoints(); + } + nodeVec.reserve( nodeVec.size() + nbNodes + 1 ); + if ( nodeVec.empty() ) + nodeVec.push_back( 0 ); + + const int predefinedNodes = ngMesh.GetNP(); + const bool wasNgMeshEmpty = ( ngMesh.GetNP() < 1 ); + + map node2ngID; + // was empty part is ignored for now! + netgen::Box<3> bb = geom.GetBoundingBox(); + bb.Increase(bb.Diam()/10); + netgen::Meshing2 meshing(geom, mparams, bb); + for ( size_t iW = 0; iW < wires.size(); ++iW ) + { + StdMeshers_FaceSidePtr wire = wires[ iW ]; + const vector& uvPtVec = wire->GetUVPtStruct(); + const smIdType nbSegments = wire->NbPoints() - 1; + + // assure the 1st node to be in node2ngID, which is needed to correctly + // "close chain of segments" (see below) in case if the 1st node is not + // onVertex because it is on a Viscous layer + node2ngID.insert( make_pair( uvPtVec[ 0 ].node, ngMesh.GetNP() + 1 - predefinedNodes )); + // MANDATORY TO ADD FACE DESCRIPTO. OTHERWISE THE Meshing2 fails + ngMesh.AddFaceDescriptor(netgen::FaceDescriptor(1, 1, 1, 1)); + // ngMesh.SetBCName(0, "Myname"); + printf("wire id, nbSegments: %d, %d\n", iW, nbSegments ); + for ( int i = 0; i < nbSegments; ++i ) // loop on segments + { + // Add the first point of a segment + + const SMDS_MeshNode * n = uvPtVec[ i ].node; + const int posShapeID = n->GetShapeID(); + bool onVertex = ( n->GetPosition()->GetTypeOfPosition() == SMDS_TOP_VERTEX ); + bool onEdge = ( n->GetPosition()->GetTypeOfPosition() == SMDS_TOP_EDGE ); + + // skip nodes on degenerated edges + if ( helper.IsDegenShape( posShapeID ) && + helper.IsDegenShape( uvPtVec[ i+1 ].node->GetShapeID() )) + continue; + + int ngID1 = ngMesh.GetNP() + 1 - predefinedNodes, ngID2 = ngID1+1; + + if ( onVertex || ( !wasNgMeshEmpty && onEdge ) || helper.IsRealSeam( posShapeID ) ) + ngID1 = node2ngID.insert( make_pair( n, ngID1 )).first->second; + + ngID1 = node2ngID.insert( make_pair( uvPtVec[ i ].node, ngID1 )).first->second; + + if ( ngID1 > ngMesh.GetNP() - predefinedNodes ) + { + netgen::MeshPoint mp( netgen::Point<3> (n->X(), n->Y(), n->Z()) ); + auto pIndex = ngMesh.AddPoint ( mp, 1, netgen::EDGEPOINT ); + // add point to mesh2 class + int pointId = meshing.AddPoint( ngMesh.Point(pIndex), ngID1 /*id of the added point*/ ); + // printf("Point id of added point:%d %d\n", pIndex, pointId); + nodeVec.push_back( n ); + } + } + + for ( int i = 0; i < nbSegments; ++i ) // loop on segments + { + const UVPtStruct& pnt0 = uvPtVec[ i ]; + const UVPtStruct& pnt1 = uvPtVec[ i + 1 ]; + netgen::PointGeomInfo gi0, gi1; + gi0.trignum = gi1.trignum = 1; /*face index + 1*/ + gi0.u = pnt0.u; + gi0.v = pnt0.v; + gi1.u = pnt1.u; + gi1.v = pnt1.v; + meshing.AddBoundaryElement(node2ngID[uvPtVec[ i ].node],node2ngID[uvPtVec[i+1].node], gi0, gi1); + } + + } + + auto noldsurfels = ngMesh.GetNSE(); + // printf( "noldsurfels %d\n", noldsurfels ); + // printf("Before Generate the mesh: %d,%d,%d \n", ngMesh.GetNP(), ngMesh.GetNSeg(), ngMesh.GetNSE() ); + // printf("Maxh %0.1lf\n", mparams.maxh ); + netgen::MESHING2_RESULT result = meshing.GenerateMesh(ngMesh,mparams,mparams.maxh,1); + printf("Mesh Generated: %d,%d,%d \n", ngMesh.GetNP(), ngMesh.GetNSeg(), ngMesh.GetNSE() ); + ngMesh.CalcSurfacesOfNode(); + + return TError(); +} + + // Iterate in all edges of the geometry, and use RestrictLocalHLine function to set the value of allow edges in 1D mesh SMESH_ComputeErrorPtr NETGENPlugin_Mesher::AddSegmentsToMesh2(netgen::Mesh& ngMesh, @@ -2321,6 +2479,9 @@ NETGENPlugin_Mesher::AddSegmentsToMesh2(netgen::Mesh& ngMesh, { const SMDS_MeshNode * n0 = uvPtVec[ i ].node; const SMDS_MeshNode * n1 = uvPtVec[ i + 1 ].node; + + bool onEdge = n0->GetPosition()->GetTypeOfPosition() == SMDS_TOP_EDGE && n1->GetPosition()->GetTypeOfPosition() == SMDS_TOP_EDGE; + const int posShapeID = n0->GetShapeID(); // skip nodes on degenerated edges if ( helper.IsDegenShape( posShapeID ) && helper.IsDegenShape( n1->GetShapeID() )) @@ -2330,24 +2491,12 @@ NETGENPlugin_Mesher::AddSegmentsToMesh2(netgen::Mesh& ngMesh, netgen::Point3d p0(n0->X(), n0->Y(), n0->Z()); netgen::Point3d p1(n1->X(), n1->Y(), n1->Z()); - // ngMesh.RestrictLocalHLine( p0, p1, size, overrideMinH ); - ngMesh.RestrictLocalH( p0, size, overrideMinH ); - - // if ( node2ngID.count(n0) == 0 ) - // { - // netgen::MeshPoint mp0( p0 ); - // ngMesh.AddPoint ( mp0, 1, netgen::FIXEDPOINT ); - // node2ngID.insert( make_pair( n0, ngMesh.GetNP() + 1 ) ); - // nodeVec.push_back( n0 ); - // } - // if ( node2ngID.count(n1) == 0 ) - // { - // netgen::MeshPoint mp1( p1 ); - // ngMesh.AddPoint ( mp1, 1, netgen::FIXEDPOINT ); - // node2ngID.insert( make_pair( n1, ngMesh.GetNP() + 1 ) ); - // nodeVec.push_back( n1 ); - // } - + if ( onEdge && size > 1e-6 ) + { + ngMesh.RestrictLocalHLine( p0, p1, size, overrideMinH ); + // ngMesh.RestrictLocalH( p0, size, overrideMinH ); /*similar behavior is observed for this case*/ + + } } } @@ -2485,14 +2634,7 @@ NETGENPlugin_Mesher::AddSegmentsToMesh(netgen::Mesh& ngMesh, if ( onVertex || ( !wasNgMeshEmpty && onEdge ) || helper.IsRealSeam( posShapeID ) ) { - ngID1 = node2ngID.insert( make_pair( n, ngID1 )).first->second; - - // printf("OnVertex: %d \n", ngID1 ); - // if ( node2ngID.count( n ) == 0 ) - // node2ngID.insert( make_pair( n, ngID1 )); - // ngID1 = node2ngID[ n ]; - - // printf("OnVertex: %d, %0.1lf, %0.1lf, %0.1lf\n", ngID1, n->X(), n->Y(), n->Z() ); + ngID1 = node2ngID.insert( make_pair( n, ngID1 )).first->second; } if ( ngID1 > ngMesh.GetNP() ) @@ -2760,7 +2902,9 @@ int NETGENPlugin_Mesher::FillSMesh(const netgen::OCCGeometry& occgeo, { if (aEdge.IsNull()) { - int aGeomEdgeInd = seg.epgeominfo[j].edgenr; + // int aGeomEdgeInd = seg.epgeominfo[j].edgenr; + int aGeomEdgeInd = seg.epgeominfo[j].edgenr + 1; /*+1 is needed for netgen6!!!!!*/ + if (aGeomEdgeInd > 0 && aGeomEdgeInd <= occgeo.emap.Extent()) aEdge = TopoDS::Edge(occgeo.emap(aGeomEdgeInd)); } @@ -2822,12 +2966,17 @@ int NETGENPlugin_Mesher::FillSMesh(const netgen::OCCGeometry& occgeo, // add a faces descriptor to exclude qudrangle elements generated by NETGEN // from computation of 3D mesh ngMesh.AddFaceDescriptor (netgen::FaceDescriptor(quadFaceID, /*solid1=*/0, /*solid2=*/0, 0)); + + printf("nbInitFac and nbFac %d,%d:\n", nbInitFac, nbFac ); vector nodes; for ( int i = nbInitFac+1; i <= nbFac; ++i ) { const netgen::Element2d& elem = ngMesh.SurfaceElement(i); const int aGeomFaceInd = elem.GetIndex(); + + printf("Face index %d:\n", aGeomFaceInd ); + TopoDS_Face aFace; if (aGeomFaceInd > 0 && aGeomFaceInd <= occgeo.fmap.Extent()) aFace = TopoDS::Face(occgeo.fmap(aGeomFaceInd)); @@ -3107,7 +3256,8 @@ int NETGENPlugin_Mesher::FillInternalElements( NETGENPlugin_NetgenLibWrapper& ng } bool NETGENPlugin_Mesher::Fill2DViscousLayer( netgen::OCCGeometry& occgeo, vector< const SMDS_MeshNode* >& nodeVec, - NETGENPlugin_Internals* internals, NETGENPlugin_ngMeshInfo& initState ) + NETGENPlugin_Internals* internals, NETGENPlugin_ngMeshInfo& initState, + netgen::MeshingParameters &mparams, bool & wasAlreadyComputed ) { SMESH_ComputeErrorPtr error = SMESH_ComputeError::New(); SMESH_Comment comment; @@ -3122,6 +3272,10 @@ bool NETGENPlugin_Mesher::Fill2DViscousLayer( netgen::OCCGeometry& occgeo, vecto FillSMesh( occgeo, *_ngMesh, initState, *_mesh, nodeVec, comment ); initState = NETGENPlugin_ngMeshInfo(_ngMesh); } + + // Move init state here BEFORE calling AddSegmentsToMesh3 + initState = NETGENPlugin_ngMeshInfo(_ngMesh); + SMESH_ProxyMesh::Ptr viscousMesh; SMESH_MesherHelper helper( *_mesh ); for ( int faceID = 1; faceID <= occgeo.fmap.Extent(); ++faceID ) @@ -3142,11 +3296,15 @@ bool NETGENPlugin_Mesher::Fill2DViscousLayer( netgen::OCCGeometry& occgeo, vecto // add new segments to _ngMesh instead of excluded ones helper.SetSubShape( F ); TSideVector wires = StdMeshers_FaceSide::GetFaceWires( F, *_mesh, /*skipMediumNodes=*/true, error, &helper, viscousMesh ); - error = AddSegmentsToMesh( *_ngMesh, occgeo, wires, helper, nodeVec ); + error = AddSegmentsToMesh3( *_ngMesh, occgeo, wires, helper, nodeVec, mparams ); + // error = AddSegmentsToMesh( *_ngMesh, occgeo, wires, helper, nodeVec ); if ( !error ) error = SMESH_ComputeError::New(); + + wasAlreadyComputed = toCompute; } - initState = NETGENPlugin_ngMeshInfo(_ngMesh); + + // initState = NETGENPlugin_ngMeshInfo(_ngMesh); } return true; } @@ -3557,6 +3715,7 @@ int NETGENPlugin_Mesher::Fill0D1DElements( netgen::OCCGeometry& occgeo, vector< return err; } + void NETGENPlugin_Mesher::InitialSetup( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo, list< SMESH_subMesh* >* meshedSM, NETGENPlugin_Internals* internals, SMESH_MesherHelper &quadHelper, NETGENPlugin_ngMeshInfo& initState, @@ -3577,6 +3736,27 @@ void NETGENPlugin_Mesher::InitialSetup( NETGENPlugin_NetgenLibWrapper& ngLib, ne } + +void NETGENPlugin_Mesher::InitialSetup2( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo, + list< SMESH_subMesh* >* meshedSM, NETGENPlugin_Internals* internals, + SMESH_MesherHelper &quadHelper, NETGENPlugin_ngMeshInfo& initState, + netgen::MeshingParameters &mparams ) +{ + // Init occ geometry maps for non meshed object and fill meshedSM with premeshed objects + PrepareOCCgeometry2( occgeo, _shape, *_mesh, meshedSM, internals ); + _occgeom = &occgeo; + _ngMesh = NULL; + + // Fill basic mesh param and + // define _ngMesh + SetBasicMeshParameters( ngLib, mparams, occgeo ); + + // Mesh internal faces and edges with netgen and then fill smesh with those entries! + // if ( internals->hasInternalEdges() ) + // FillInternalElements( ngLib, *internals, occgeo, initState, quadHelper, meshedSM ); + +} + void NETGENPlugin_Mesher::InitialSetupSA( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo, list< SMESH_subMesh* >* meshedSM, NETGENPlugin_Internals* internals, SMESH_MesherHelper &quadHelper, NETGENPlugin_ngMeshInfo& initState, @@ -3726,11 +3906,15 @@ bool NETGENPlugin_Mesher::Compute2D( NETGENPlugin_NetgenLibWrapper& ngLib, netge vector< const SMDS_MeshNode* >& nodeVec, SMESH_Comment& comment, DIM dim ) { SetBasicMeshParametersFor2D(occgeo, nodeVec, mparams, internals, initState ); - if ( !Fill2DViscousLayer(occgeo, nodeVec, internals, initState ) ) + bool wasAlreadyComputed = false; + if ( !Fill2DViscousLayer(occgeo, nodeVec, internals, initState, mparams, wasAlreadyComputed ) ) return false; mparams.uselocalh = true; // restore as it is used at surface optimization - int err = CallNetgenMeshFaces( ngLib, occgeo, comment ); + + int err = 0; + if ( !wasAlreadyComputed ) + err = CallNetgenMeshFaces( ngLib, occgeo, comment ); if ( !err && dim == DIM::D2 /* if mesh is 3D then second order is defined after volumens are computed*/ ) MakeSecondOrder( mparams, occgeo, meshedSM, initState, comment ); @@ -3798,44 +3982,50 @@ bool NETGENPlugin_Mesher::Compute() // Generate the mesh // ------------------------- + /* Original version InitialSetup( ngLib, occgeo, meshedSM, &internals, quadHelper, initState, mparams ); err = Fill0D1DElements( occgeo, nodeVec, meshedSM, quadHelper ); + */ + + /* Alternative version where premeshed 1D elements are passed as geometrical vertex of the geometry */ + InitialSetup2( ngLib, occgeo, meshedSM, &internals, quadHelper, initState, mparams ); + // err = Fill0D1DElements( occgeo, nodeVec, meshedSM, quadHelper ); + initState = NETGENPlugin_ngMeshInfo(_ngMesh); err = CallNetgenMeshEdges( ngLib, occgeo ); SetBasicMeshParametersFor2D(occgeo, nodeVec, mparams, &internals, initState ); - if ( !Fill2DViscousLayer(occgeo, nodeVec, &internals, initState ) ) + bool wasAlreadyComputed = false; + if ( !Fill2DViscousLayer(occgeo, nodeVec, &internals, initState, mparams, wasAlreadyComputed ) ) return false; SMESH_Comment comment; - { - - if ( _isVolume ) - _ticTime = ( doneTime += edgeMeshingTime ) / _totalTime / _progressTic; + if ( _isVolume ) + _ticTime = ( doneTime += edgeMeshingTime ) / _totalTime / _progressTic; - mparams.uselocalh = true; // restore as it is used at surface optimization + mparams.uselocalh = true; // restore as it is used at surface optimization + if ( !wasAlreadyComputed ) err = CallNetgenMeshFaces( ngLib, occgeo, comment ); - - if ( _isVolume ) - { - doneTime += faceMeshingTime + ( _optimize ? faceOptimizTime : 0 ); - _ticTime = doneTime / _totalTime / _progressTic; - } - - if ( !err ) - if ( !Fill3DViscousLayerAndQuadAdaptor( occgeo, nodeVec, mparams, initState, meshedSM, quadHelper, err ) ) - return false; + + if ( _isVolume ) + { + doneTime += faceMeshingTime + ( _optimize ? faceOptimizTime : 0 ); + _ticTime = doneTime / _totalTime / _progressTic; + } - if (!err && _isVolume) - { - SetBasicMeshParametersFor3D( ngLib, occgeo, nodeVec, mparams, &internals, initState, quadHelper, comment ); - err = CallNetgenMeshVolumens( ngLib, occgeo, comment ); - } + if ( !err ) + if ( !Fill3DViscousLayerAndQuadAdaptor( occgeo, nodeVec, mparams, initState, meshedSM, quadHelper, err ) ) + return false; - if (!err ) - MakeSecondOrder( mparams, occgeo, meshedSM, initState, comment ); + if (!err && _isVolume) + { + SetBasicMeshParametersFor3D( ngLib, occgeo, nodeVec, mparams, &internals, initState, quadHelper, comment ); + err = CallNetgenMeshVolumens( ngLib, occgeo, comment ); } + if (!err ) + MakeSecondOrder( mparams, occgeo, meshedSM, initState, comment ); + _ticTime = 0.98 / _progressTic; //int nbNod = _ngMesh->GetNP(); @@ -4941,25 +5131,15 @@ int NETGENPlugin_NetgenLibWrapper::GenerateMesh( netgen::OCCGeometry& occgeo, // netgen::mparam.Print(std::cerr); #ifdef NETGEN_V6 - ngMesh->SetGeometry( shared_ptr( &occgeo, &NOOP_Deleter )); netgen::mparam.perfstepsstart = startWith; netgen::mparam.perfstepsend = endWith; std::shared_ptr meshPtr( ngMesh, &NOOP_Deleter ); err = occgeo.GenerateMesh( meshPtr, netgen::mparam ); - #else - #ifdef NETGEN_V5 - - err = netgen::OCCGenerateMesh(occgeo, ngMesh, netgen::mparam, startWith, endWith); - - #else - char *optstr = 0; err = netgen::OCCGenerateMesh(occgeo, ngMesh, startWith, endWith, optstr); - - #endif #endif return err; } diff --git a/src/NETGENPlugin/NETGENPlugin_Mesher.hxx b/src/NETGENPlugin/NETGENPlugin_Mesher.hxx index b56a988..a5245d8 100644 --- a/src/NETGENPlugin/NETGENPlugin_Mesher.hxx +++ b/src/NETGENPlugin/NETGENPlugin_Mesher.hxx @@ -67,6 +67,7 @@ class TopoDS_Shape; namespace netgen { class OCCGeometry; class Mesh; + class Meshing2; NETGENPLUGIN_DLL_HEADER extern MeshingParameters mparam; } @@ -191,6 +192,10 @@ class NETGENPLUGIN_EXPORT NETGENPlugin_Mesher list< SMESH_subMesh* >* meshedSM, NETGENPlugin_Internals* internals, SMESH_MesherHelper &quadHelper, NETGENPlugin_ngMeshInfo& initState, netgen::MeshingParameters &mparams ); + void InitialSetup2( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo, + list< SMESH_subMesh* >* meshedSM, NETGENPlugin_Internals* internals, + SMESH_MesherHelper &quadHelper, NETGENPlugin_ngMeshInfo& initState, netgen::MeshingParameters &mparams ); + void InitialSetupSA( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo, list< SMESH_subMesh* >* meshedSM, NETGENPlugin_Internals* internals, SMESH_MesherHelper &quadHelper, NETGENPlugin_ngMeshInfo& initState, @@ -207,13 +212,16 @@ class NETGENPLUGIN_EXPORT NETGENPlugin_Mesher void CallNetgenConstAnalysis( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::MeshingParameters &mparams, netgen::OCCGeometry& occgeo ); int CallNetgenMeshEdges( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo ); int CallNetgenMeshFaces( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo, SMESH_Comment& comment ); + + // Iterate the geometry by face, int CallNetgenMeshVolumens( NETGENPlugin_NetgenLibWrapper& ngLib, netgen::OCCGeometry& occgeo, SMESH_Comment& comment ); void MakeSecondOrder( netgen::MeshingParameters &mparams, netgen::OCCGeometry& occgeo, list< SMESH_subMesh* >* meshedSM, NETGENPlugin_ngMeshInfo& initState, SMESH_Comment& comment ); int FillInternalElements( NETGENPlugin_NetgenLibWrapper& ngLib, NETGENPlugin_Internals& internals, netgen::OCCGeometry& occgeo, NETGENPlugin_ngMeshInfo& initState, SMESH_MesherHelper &quadHelper, list< SMESH_subMesh* >* meshedSM ); bool Fill2DViscousLayer( netgen::OCCGeometry& occgeo, vector< const SMDS_MeshNode* >& nodeVec, - NETGENPlugin_Internals* internals, NETGENPlugin_ngMeshInfo& initState ); + NETGENPlugin_Internals* internals, NETGENPlugin_ngMeshInfo& initState, + netgen::MeshingParameters &mparams, bool & wasAlreadyComputed ); bool Fill3DViscousLayerAndQuadAdaptor( netgen::OCCGeometry& occgeo, vector< const SMDS_MeshNode* >& nodeVec, netgen::MeshingParameters &mparams, NETGENPlugin_ngMeshInfo& initState, @@ -243,6 +251,12 @@ class NETGENPLUGIN_EXPORT NETGENPlugin_Mesher SMESH_Mesh& mesh, std::list< SMESH_subMesh* > * meshedSM=0, NETGENPlugin_Internals* internalShapes=0); + /*get submesh and define geometrical vertex on premeshed nodes*/ + static void PrepareOCCgeometry2(netgen::OCCGeometry& occgeom, + const TopoDS_Shape& shape, + SMESH_Mesh& mesh, + std::list< SMESH_subMesh* > * meshedSM=0, + NETGENPlugin_Internals* internalShapes=0); static double GetDefaultMinSize(const TopoDS_Shape& shape, const double maxSize); @@ -299,6 +313,14 @@ class NETGENPLUGIN_EXPORT NETGENPlugin_Mesher SMESH_MesherHelper& helper, std::vector< const SMDS_MeshNode* > & nodeVec, const bool overrideMinH=true); + + static SMESH_ComputeErrorPtr AddSegmentsToMesh3(netgen::Mesh& ngMesh, + netgen::OCCGeometry& geom, + const TSideVector& wires, + SMESH_MesherHelper& helper, + std::vector< const SMDS_MeshNode* > & nodeVec, + netgen::MeshingParameters & mparams, + const bool overrideMinH=true); void SetDefaultParameters(); diff --git a/src/NETGENPlugin/NETGENPlugin_NETGEN_2D_ONLY.cxx b/src/NETGENPlugin/NETGENPlugin_NETGEN_2D_ONLY.cxx index 1ea1a83..d39401f 100644 --- a/src/NETGENPlugin/NETGENPlugin_NETGEN_2D_ONLY.cxx +++ b/src/NETGENPlugin/NETGENPlugin_NETGEN_2D_ONLY.cxx @@ -1065,8 +1065,9 @@ bool NETGENPlugin_NETGEN_2D_ONLY::Compute(SMESH_Mesh& aMesh, netgen::OCCGeometry occgeom; occgeom.shape = F; + occgeom.fmap.Add( F ); occgeom.BuildFMapFace(); - + // ------------------------- // Fill netgen mesh // ------------------------- @@ -1101,11 +1102,22 @@ bool NETGENPlugin_NETGEN_2D_ONLY::Compute(SMESH_Mesh& aMesh, } nodeVec.clear(); + + // legacy version feeding netgen with netgen segments which demands mapping to submeshed edges // faceErr = aMesher.AddSegmentsToMesh( *ngMesh, occgeom, wires, helper, nodeVec, // /*overrideMinH=*/!_hypParameters); - faceErr = aMesher.AddSegmentsToMesh2( *ngMesh, occgeom, wires, helper, nodeVec, - /*overrideMinH=*/!_hypParameters); + // using RestrictLocalHLine function from netgen + // Most of the commont geometries are meshed but it is not stable + // for medium number of edges it fails to mesh the face + // faceErr = aMesher.AddSegmentsToMesh2( *ngMesh, occgeom, wires, helper, nodeVec, + // /*overrideMinH=*/!_hypParameters); + + // using directly Mesh2 class from netgen + // When using this version. Does not call ngLib.GenerateMesh below + // Most of the commont geometries are meshed but it is not stable + // for medium number of edges it fails to mesh the face + faceErr = aMesher.AddSegmentsToMesh3( *ngMesh, occgeom, wires, helper, nodeVec, mparam, !_hypParameters); if ( faceErr && !faceErr->IsOK() ) break; @@ -1116,8 +1128,10 @@ bool NETGENPlugin_NETGEN_2D_ONLY::Compute(SMESH_Mesh& aMesh, // ------------------------- // Generate surface mesh // ------------------------- - const int startWith = netgen::MESHCONST_MESHEDGES; - const int endWith = toOptimize ? MESHCONST_OPTSURFACE : MESHCONST_MESHSURFACE; + // const int startWith = netgen::MESHCONST_MESHEDGES; + const int startWith = netgen::MESHCONST_MESHSURFACE; + const int endWith = toOptimize ? netgen::MESHCONST_OPTSURFACE : netgen::MESHCONST_MESHSURFACE; + // const int startWith = MESHCONST_MESHSURFACE; // const int endWith = toOptimize ? MESHCONST_OPTSURFACE : MESHCONST_MESHSURFACE; @@ -1127,7 +1141,7 @@ bool NETGENPlugin_NETGEN_2D_ONLY::Compute(SMESH_Mesh& aMesh, try { OCC_CATCH_SIGNALS; printf("Going to generate mesh\n"); - err = ngLib.GenerateMesh(occgeom, startWith, endWith, ngMesh); + // err = ngLib.GenerateMesh(occgeom, startWith, endWith, ngMesh); printf("Mesh Generated: %d,%d,%d \n", ngMesh->GetNP(), ngMesh->GetNSeg(), ngMesh->GetNSE() ); if ( netgen::multithread.terminate )