--- /dev/null
+// Copyright (C) 2007-2011 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.
+//
+// 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 : DriverCGNS_Read.cxx
+// Created : Thu Jun 30 10:33:31 2011
+// Author : Edward AGAPOV (eap)
+
+#include "DriverCGNS_Read.hxx"
+
+#include "SMDS_MeshNode.hxx"
+#include "SMESHDS_Group.hxx"
+#include "SMESHDS_Mesh.hxx"
+#include "SMESH_Comment.hxx"
+
+#include <gp_XYZ.hxx>
+
+#include <cgnslib.h>
+
+#include <map>
+
+#if CGNS_VERSION < 3100
+# define cgsize_t int
+#endif
+
+#define NB_ZONE_SIZE_VAL 9
+#define CGNS_NAME_SIZE 33
+#define CGNS_STRUCT_RANGE_SZ 6
+
+using namespace std;
+
+namespace
+{
+ //================================================================================
+ /*!
+ * \brief Data of a zone
+ */
+ struct TZoneData
+ {
+ int _id;
+ int _nodeIdShift; // nb nodes in previously read zones
+ int _elemIdShift; // nb faces in previously read zones
+ int _nbNodes, _nbElems;
+ int _meshDim;
+ int _sizeX, _sizeY, _sizeZ, _nbCells; // structured
+ cgsize_t _sizes[NB_ZONE_SIZE_VAL];
+ CGNS_ENUMT(ZoneType_t) _type;
+ map< int, int > _nodeReplacementMap;/* key: id of node to replace (in this zone),
+ value: id of node to replace by (in another zone)
+ id values include _nodeIdShift of the zones */
+ void SetSizeAndDim( cgsize_t* sizes, int meshDim )
+ {
+ _meshDim = meshDim;
+ memcpy( _sizes, sizes, NB_ZONE_SIZE_VAL*sizeof(cgsize_t));
+ _sizeX = _sizes[0];
+ _sizeY = _meshDim > 1 ? _sizes[1] : 0;
+ _sizeZ = _meshDim > 2 ? _sizes[2] : 0;
+ _nbCells = (_sizeX - 1) * ( _meshDim > 1 ? _sizeY : 1 ) * ( _meshDim > 2 ? _sizeZ : 1 );
+ }
+ bool IsStructured() const { return ( _type == CGNS_ENUMV( Structured )); }
+ int IndexSize() const { return IsStructured() ? _meshDim : 1; }
+ string ReadZonesConnection(int file, int base, const map< string, TZoneData >& zonesByName);
+ void ReplaceNodes( int* ids, int nbIds, int idShift = 0 ) const;
+
+ // Methods for a structured zone
+
+ int NodeID( int i, int j, int k = 1 ) const
+ {
+ return _nodeIdShift + (k-1)*_sizeX*_sizeY + (j-1)*_sizeX + i;
+ }
+ int NodeID( const gp_XYZ& ijk ) const
+ {
+ return NodeID( int(ijk.X()), int(ijk.Y()), int(ijk.Z()));
+ }
+ void CellNodes( int i, int j, int k, cgsize_t* ids ) const
+ {
+ ids[0] = NodeID( i , j , k );
+ ids[1] = NodeID( i , j+1, k );
+ ids[2] = NodeID( i+1, j+1, k );
+ ids[3] = NodeID( i+1, j , k );
+ ids[4] = NodeID( i , j , k+1);
+ ids[5] = NodeID( i , j+1, k+1);
+ ids[6] = NodeID( i+1, j+1, k+1);
+ ids[7] = NodeID( i+1, j , k+1);
+ }
+ void CellNodes( int i, int j, cgsize_t* ids ) const
+ {
+ ids[0] = NodeID( i , j );
+ ids[1] = NodeID( i , j+1 );
+ ids[2] = NodeID( i+1, j+1 );
+ ids[3] = NodeID( i+1, j );
+ }
+ void IFaceNodes( int i, int j, int k, cgsize_t* ids ) const // face perpendiculaire to X (3D)
+ {
+ ids[0] = NodeID( i, j, k );
+ ids[1] = ids[0] + _sizeX*( i==_sizeX ? 1 : _sizeY );
+ ids[2] = ids[0] + _sizeX*( _sizeY + 1 );
+ ids[3] = ids[0] + _sizeX*( i==_sizeX ? _sizeY : 1 );
+ }
+ void JFaceNodes( int i, int j, int k, cgsize_t* ids ) const
+ {
+ ids[0] = NodeID( i, j, k );
+ ids[1] = ids[0] + ( j==_sizeY ? _sizeX*_sizeY : 1);
+ ids[2] = ids[0] + _sizeX*_sizeY + 1;
+ ids[3] = ids[0] + ( j==_sizeY ? 1 : _sizeX*_sizeY);
+ }
+ void KFaceNodes( int i, int j, int k, cgsize_t* ids ) const
+ {
+ ids[0] = NodeID( i, j, k );
+ ids[1] = ids[0] + ( k==_sizeZ ? 1 : _sizeX);
+ ids[2] = ids[0] + _sizeX + 1;
+ ids[3] = ids[0] + ( k==_sizeZ ? _sizeX : 1);
+ }
+ void IEdgeNodes( int i, int j, int k, cgsize_t* ids ) const // edge perpendiculaire to X (2D)
+ {
+ ids[0] = NodeID( i, j, 0 );
+ ids[1] = ids[0] + _sizeX;
+ }
+ void JEdgeNodes( int i, int j, int k, cgsize_t* ids ) const
+ {
+ ids[0] = NodeID( i, j, 0 );
+ ids[1] = ids[0] + 1;
+ }
+#define gpXYZ2IJK(METHOD) \
+ void METHOD( const gp_XYZ& ijk, cgsize_t* ids ) const { \
+ METHOD( int(ijk.X()), int(ijk.Y()), int(ijk.Z()), ids); \
+ }
+ gpXYZ2IJK( IFaceNodes )
+ gpXYZ2IJK( JFaceNodes )
+ gpXYZ2IJK( KFaceNodes )
+ gpXYZ2IJK( IEdgeNodes )
+ gpXYZ2IJK( JEdgeNodes )
+ };
+
+ //================================================================================
+ /*!
+ * \brief Iterator over nodes of the structired grid using FORTRAN multidimensional
+ * array ordering.
+ */
+ class TPointRangeIterator
+ {
+ int _beg[3], _end[3], _cur[3], _dir[3], _dim;
+ bool _more;
+ public:
+ TPointRangeIterator( const cgsize_t* range, int dim ):_dim(dim)
+ {
+ _more = false;
+ for ( int i = 0; i < dim; ++i )
+ {
+ _beg[i] = range[i];
+ _end[i] = range[i+dim];
+ _dir[i] = _end[i] < _beg[i] ? -1 : 1;
+ _end[i] += _dir[i];
+ _cur[i] = _beg[i];
+ if ( _end[i] - _beg[i] )
+ _more = true;
+ }
+// for ( int i = dim; i < 3; ++i )
+// _cur[i] = _beg[i] = _end[i] = _dir[i] = 0;
+ }
+ bool More() const
+ {
+ return _more;
+ }
+ gp_XYZ Next()
+ {
+ gp_XYZ res( _cur[0], _cur[1], _cur[2] );
+ for ( int i = 0; i < _dim; ++i )
+ {
+ _cur[i] += _dir[i];
+ if ( _cur[i]*_dir[i] < _end[i]*_dir[i] )
+ break;
+ if ( i+1 < _dim )
+ _cur[i] = _beg[i];
+ else
+ _more = false;
+ }
+ return res;
+ }
+ size_t Size() const
+ {
+ size_t size = 1;
+ for ( int i = 0; i < _dim; ++i )
+ size *= _dir[i]*(_end[i]-_beg[i]);
+ return size;
+ }
+ gp_XYZ Begin() const { return gp_XYZ( _beg[0], _beg[1], _beg[2] ); }
+ //gp_XYZ End() const { return gp_XYZ( _end[0]-1, _end[1]-1, _end[2]-1 ); }
+ };
+
+ //================================================================================
+ /*!
+ * \brief Reads zone interface connectivity
+ * \param file - file to read
+ * \param base - base to read
+ * \param zone - zone to replace nodes in
+ * \param zonesByName - TZoneData by name
+ * \retval string - warning message
+ *
+ * see // http://www.grc.nasa.gov/WWW/cgns/CGNS_docs_current/sids/cnct.html
+ */
+ //================================================================================
+
+ string TZoneData::ReadZonesConnection( int file,
+ int base,
+ const map< string, TZoneData >& zonesByName)
+ {
+ string error;
+
+ char connectName[ CGNS_NAME_SIZE ], donorName [ CGNS_NAME_SIZE ];
+
+ // ----------------------------
+ // read zone 1 to 1 interfaces
+ // ----------------------------
+ if ( IsStructured() )
+ {
+ int nb1to1 = 0;
+ if ( cg_n1to1 ( file, base, _id, &nb1to1) == CG_OK )
+ {
+ cgsize_t range[CGNS_STRUCT_RANGE_SZ], donorRange[CGNS_STRUCT_RANGE_SZ];
+ int transform[3] = {0,0,0};
+
+ for ( int I = 1; I <= nb1to1; ++I )
+ {
+ if ( cg_1to1_read(file, base, _id, I, connectName,
+ donorName, range, donorRange, transform) == CG_OK )
+ {
+ map< string, TZoneData>::const_iterator n_z = zonesByName.find( donorName );
+ if ( n_z == zonesByName.end() )
+ continue; // donor zone not yet read
+ const TZoneData& zone2 = n_z->second;
+
+ // set up matrix to transform ijk of the zone to ijk of the zone2
+ gp_Mat T;
+ for ( int i = 0; i < _meshDim; ++i )
+ if ( transform[i] )
+ {
+ int row = Abs(transform[i]);
+ int col = i+1;
+ int val = transform[i] > 0 ? +1 : -1;
+ T( row, col ) = val;
+ }
+
+ // fill nodeReplacementMap
+ TPointRangeIterator rangeIt1( range, _meshDim );
+ TPointRangeIterator rangeIt2( donorRange, _meshDim );
+ gp_XYZ begin1 = rangeIt1.Begin(), begin2 = rangeIt2.Begin(), index1, index2;
+ if ( &zone2 == this )
+ {
+ // not to read twice the same interface with self
+ TPointRangeIterator rangeIt1bis( range, _meshDim );
+ if ( rangeIt1bis.More() )
+ {
+ index1 = rangeIt1bis.Next();
+ index2 = T * ( index1 - begin1 ) + begin2;
+ int node1 = NodeID( index1 );
+ int node2 = zone2.NodeID( index2 );
+ if ( _nodeReplacementMap.count( node2 ) &&
+ _nodeReplacementMap[ node2 ] == node1 )
+ continue; // this interface already read
+ }
+ }
+ while ( rangeIt1.More() )
+ {
+ index1 = rangeIt1.Next();
+ index2 = T * ( index1 - begin1 ) + begin2;
+ int node1 = NodeID( index1 );
+ int node2 = zone2.NodeID( index2 );
+ _nodeReplacementMap.insert( make_pair( node1, node2 ));
+ }
+ }
+ else
+ {
+ error = cg_get_error();
+ }
+ }
+ }
+ else
+ {
+ error = cg_get_error();
+ }
+ }
+
+ // ---------------------------------
+ // read general zone connectivities
+ // ---------------------------------
+ int nbConn = 0;
+ if ( cg_nconns( file, base, _id, &nbConn) == CG_OK )
+ {
+ cgsize_t nb, donorNb;
+ CGNS_ENUMT(GridLocation_t) location;
+ CGNS_ENUMT(GridConnectivityType_t) connectType;
+ CGNS_ENUMT(PointSetType_t) ptype, donorPtype;
+ CGNS_ENUMT(ZoneType_t) donorZonetype;
+ CGNS_ENUMT(DataType_t) donorDatatype;
+
+ for ( int I = 1; I <= nbConn; ++I )
+ {
+ if ( cg_conn_info(file, base, _id, I, connectName, &location, &connectType,
+ &ptype, &nb, donorName, &donorZonetype, &donorPtype,
+ &donorDatatype, &donorNb ) == CG_OK )
+ {
+ if ( location != CGNS_ENUMV( Vertex ))
+ continue; // we do not support cell-to-cell connectivity
+ if ( ptype != CGNS_ENUMV( PointList ) &&
+ ptype != CGNS_ENUMV( PointRange ))
+ continue;
+ if ( donorPtype != CGNS_ENUMV( PointList ) &&
+ donorPtype != CGNS_ENUMV( PointRange ))
+ continue;
+
+ map< string, TZoneData>::const_iterator n_z = zonesByName.find( donorName );
+ if ( n_z == zonesByName.end() )
+ continue; // donor zone not yet read
+ const TZoneData& zone2 = n_z->second;
+
+ vector< int > ids( nb * IndexSize() );
+ vector< int > donorIds( donorNb * zone2.IndexSize() );
+ if (cg_conn_read ( file, base, _id, I,
+ &ids[0], CGNS_ENUMV(Integer), &donorIds[0]) == CG_OK )
+ {
+ for ( int isThisZone = 0; isThisZone < 2; ++isThisZone )
+ {
+ const TZoneData& zone = isThisZone ? *this : zone2;
+ CGNS_ENUMT(PointSetType_t) type = isThisZone ? ptype : donorPtype;
+ vector< int >& points = isThisZone ? ids : donorIds;
+ if ( type == CGNS_ENUMV( PointRange ))
+ {
+ TPointRangeIterator rangeIt( &points[0], zone._meshDim );
+ points.clear();
+ while ( rangeIt.More() )
+ points.push_back ( NodeID( rangeIt.Next() ));
+ }
+ else if ( zone.IsStructured() )
+ {
+ vector< int > resIDs; resIDs.reserve( points.size() / IndexSize() );
+ for ( size_t i = 0; i < points.size(); i += IndexSize() )
+ resIDs.push_back( zone.NodeID( points[i+0], points[i+1], points[i+2] ));
+ resIDs.swap( points );
+ }
+ else if ( zone._nodeIdShift > 0 )
+ {
+ for ( size_t i = 0; i < points.size(); ++i )
+ points[i] += zone._nodeIdShift;
+ }
+ }
+ for ( size_t i = 0; i < ids.size() && i < donorIds.size(); ++i )
+ _nodeReplacementMap.insert( make_pair( ids[i], donorIds[i] ));
+ }
+ else
+ {
+ error = cg_get_error();
+ }
+ }
+ else
+ {
+ error = cg_get_error();
+ }
+ }
+ }
+ else
+ {
+ error = cg_get_error();
+ }
+ return error;
+ }
+
+ //================================================================================
+ /*!
+ * \brief Replaces node ids according to nodeReplacementMap to take into account
+ * connection of zones
+ */
+ //================================================================================
+
+ void TZoneData::ReplaceNodes( cgsize_t* ids, int nbIds, int idShift/* = 0*/ ) const
+ {
+ if ( !_nodeReplacementMap.empty() )
+ {
+ map< int, int >::const_iterator it, end = _nodeReplacementMap.end();
+ for ( size_t i = 0; i < nbIds; ++i )
+ if (( it = _nodeReplacementMap.find( ids[i] + idShift)) != end )
+ ids[i] = it->second;
+ else
+ ids[i] += idShift;
+ }
+ else if ( idShift )
+ {
+ for ( size_t i = 0; i < nbIds; ++i )
+ ids[i] += idShift;
+ }
+ }
+ //================================================================================
+ /*!
+ * \brief functions adding an element of a particular type
+ */
+ SMDS_MeshElement* add_0D(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->Add0DElementWithID( ids[0], ID );
+ }
+ SMDS_MeshElement* add_BAR_2(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddEdgeWithID( ids[0], ids[1], ID );
+ }
+ SMDS_MeshElement* add_BAR_3(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddEdgeWithID( ids[0], ids[1], ids[2], ID );
+ }
+ SMDS_MeshElement* add_TRI_3(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddFaceWithID( ids[0], ids[2], ids[1], ID );
+ }
+ SMDS_MeshElement* add_TRI_6(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddFaceWithID( ids[0], ids[2], ids[1], ids[5], ids[4], ids[3], ID );
+ }
+ SMDS_MeshElement* add_QUAD_4(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddFaceWithID( ids[0], ids[3], ids[2], ids[1], ID );
+ }
+ SMDS_MeshElement* add_QUAD_8(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddFaceWithID( ids[0],ids[3],ids[2],ids[1],ids[7],ids[6],ids[5],ids[4], ID );
+ }
+ SMDS_MeshElement* add_TETRA_4(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0], ids[2], ids[1], ids[3], ID );
+ }
+ SMDS_MeshElement* add_TETRA_10(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0],ids[2],ids[1],ids[3],ids[6],
+ ids[5],ids[4],ids[7],ids[9],ids[8], ID );
+ }
+ SMDS_MeshElement* add_PYRA_5(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0],ids[3],ids[2],ids[1],ids[4],ID );
+ }
+ SMDS_MeshElement* add_PYRA_13(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0],ids[3],ids[2],ids[1],ids[4],ids[8],ids[7],
+ ids[6],ids[5],ids[9],ids[12],ids[11],ids[10], ID );
+ }
+ SMDS_MeshElement* add_PENTA_6(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0],ids[2],ids[1],ids[3],ids[5],ids[4], ID );
+ }
+ SMDS_MeshElement* add_PENTA_15(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0],ids[2],ids[1],ids[3],ids[5],ids[4],ids[8],ids[7],
+ ids[6],ids[9],ids[11],ids[10],ids[14],ids[13],ids[12], ID );
+ }
+ SMDS_MeshElement* add_HEXA_8(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0],ids[3],ids[2],ids[1],ids[4],ids[7],ids[6],ids[5], ID );
+ }
+ SMDS_MeshElement* add_HEXA_20(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ return mesh->AddVolumeWithID( ids[0],ids[3],ids[2],ids[1],ids[4],ids[7],ids[6],
+ ids[5],ids[11],ids[10],ids[9],ids[8],ids[12],ids[15],
+ ids[14],ids[13],ids[19],ids[18],ids[17],ids[16], ID );
+ }
+ SMDS_MeshElement* add_NGON(cgsize_t* ids, SMESHDS_Mesh* mesh, int ID)
+ {
+ vector<int> idVec( ids[0] );
+ for ( int i = 0; i < ids[0]; ++i )
+ idVec[ i ] = (int) ids[ i + 1];
+ return mesh->AddPolygonalFaceWithID( idVec, ID );
+ }
+
+ typedef SMDS_MeshElement* (* PAddElemFun) (cgsize_t* ids, SMESHDS_Mesh* mesh, int ID);
+
+ //================================================================================
+ /*!
+ * \brief Return an array of functions each adding an element of a particular type
+ */
+ //================================================================================
+
+ PAddElemFun* getAddElemFunTable()
+ {
+ static vector< PAddElemFun > funVec;
+ if ( funVec.empty() )
+ {
+ funVec.resize( NofValidElementTypes, (PAddElemFun)0 );
+ funVec[ CGNS_ENUMV( NODE )] = add_0D ;
+ funVec[ CGNS_ENUMV( BAR_2 )] = add_BAR_2 ;
+ funVec[ CGNS_ENUMV( BAR_3 )] = add_BAR_3 ;
+ funVec[ CGNS_ENUMV( TRI_3 )] = add_TRI_3 ;
+ funVec[ CGNS_ENUMV( TRI_6 )] = add_TRI_6 ;
+ funVec[ CGNS_ENUMV( QUAD_4 )] = add_QUAD_4 ;
+ funVec[ CGNS_ENUMV( QUAD_8 )] = add_QUAD_8 ;
+ funVec[ CGNS_ENUMV( QUAD_9 )] = add_QUAD_8 ;
+ funVec[ CGNS_ENUMV( TETRA_4 )] = add_TETRA_4 ;
+ funVec[ CGNS_ENUMV( TETRA_10 )] = add_TETRA_10;
+ funVec[ CGNS_ENUMV( PYRA_5 )] = add_PYRA_5 ;
+ funVec[ CGNS_ENUMV( PYRA_13 )] = add_PYRA_13 ;
+ funVec[ CGNS_ENUMV( PYRA_14 )] = add_PYRA_13 ;
+ funVec[ CGNS_ENUMV( PENTA_6 )] = add_PENTA_6 ;
+ funVec[ CGNS_ENUMV( PENTA_15 )] = add_PENTA_15;
+ funVec[ CGNS_ENUMV( PENTA_18 )] = add_PENTA_15;
+ funVec[ CGNS_ENUMV( HEXA_8 )] = add_HEXA_8 ;
+ funVec[ CGNS_ENUMV( HEXA_20 )] = add_HEXA_20 ;
+ funVec[ CGNS_ENUMV( HEXA_27 )] = add_HEXA_20 ;
+ funVec[ CGNS_ENUMV( NGON_n )] = add_NGON ;
+ }
+ return &funVec[0];
+ }
+
+ //================================================================================
+ /*!
+ * \brief Finds an existing boundary element
+ */
+ //================================================================================
+
+ const SMDS_MeshElement* findElement(const cgsize_t* nodeIDs,
+ const int nbNodes,
+ const SMESHDS_Mesh* mesh)
+ {
+ const SMDS_MeshNode* nn[4]; // look for quad4 or seg2
+ if (( nn[0] = mesh->FindNode( nodeIDs[0] )))
+ {
+ SMDSAbs_ElementType eType = nbNodes==4 ? SMDSAbs_Face : SMDSAbs_Edge;
+ SMDS_ElemIteratorPtr eIt = nn[0]->GetInverseElementIterator( eType );
+ if ( eIt->more() )
+ for ( int i = 1; i < nbNodes; ++i )
+ nn[i] = mesh->FindNode( nodeIDs[i] );
+ while ( eIt->more() )
+ {
+ const SMDS_MeshElement* e = eIt->next();
+ if ( e->NbNodes() == nbNodes )
+ {
+ bool elemOK = true;
+ for ( int i = 1; i < nbNodes && elemOK; ++i )
+ elemOK = ( e->GetNodeIndex( nn[i] ) >= 0 );
+ if ( elemOK )
+ return e;
+ }
+ }
+ }
+ return 0;
+ }
+
+} // namespace
+
+//================================================================================
+/*!
+ * \brief Perform reading a myMeshId-th mesh
+ */
+//================================================================================
+
+Driver_Mesh::Status DriverCGNS_Read::Perform()
+{
+ myErrorMessages.clear();
+
+ Status aResult;
+ if (( aResult = open() ) != DRS_OK )
+ return aResult;
+
+ // read nb of meshes (CGNSBase_t)
+ if ( myMeshId < 0 || myMeshId >= GetNbMeshes(aResult))
+ return addMessage( SMESH_Comment("Invalid mesh index :") << myMeshId );
+
+ // read a name and a dimension of the mesh
+ const int cgnsBase = myMeshId + 1;
+ char meshName[CGNS_NAME_SIZE];
+ int meshDim, spaceDim;
+ if ( cg_base_read( _fn, cgnsBase, meshName, &meshDim, &spaceDim) != CG_OK )
+ return addMessage( cg_get_error() );
+
+ if ( spaceDim < 1 || spaceDim > 3 )
+ return addMessage( SMESH_Comment("Invalid space dimension: ") << spaceDim
+ << " in mesh '" << meshName << "'");
+
+ myMeshName = meshName;
+
+ // read nb of domains (Zone_t) in the mesh
+ int nbZones = 0;
+ if ( cg_nzones (_fn, cgnsBase, &nbZones) != CG_OK )
+ return addMessage( cg_get_error() );
+
+ if ( nbZones < 1 )
+ return addMessage( SMESH_Comment("Empty mesh: '") << meshName << "'");
+
+ // read the domains (zones)
+ // ------------------------
+ map< string, TZoneData > zonesByName;
+ char name[CGNS_NAME_SIZE];
+ cgsize_t sizes[NB_ZONE_SIZE_VAL];
+ memset(sizes, 0, NB_ZONE_SIZE_VAL * sizeof(cgsize_t));
+
+ const SMDS_MeshInfo& meshInfo = myMesh->GetMeshInfo();
+ int groupID = myMesh->GetGroups().size();
+
+ for ( int iZone = 1; iZone <= nbZones; ++iZone )
+ {
+ // size and name of a zone
+ if ( cg_zone_read( _fn, cgnsBase, iZone, name, sizes) != CG_OK) {
+ addMessage( cg_get_error() );
+ continue;
+ }
+ TZoneData& zone = zonesByName[ name ];
+ zone._id = iZone;
+ zone._nodeIdShift = meshInfo.NbNodes();
+ zone._elemIdShift = meshInfo.NbElements();
+ zone.SetSizeAndDim( sizes, meshDim );
+
+ // mesh type of the zone
+ if ( cg_zone_type ( _fn, cgnsBase, iZone, &zone._type) != CG_OK) {
+ addMessage( cg_get_error() );
+ continue;
+ }
+
+ switch ( zone._type )
+ {
+ case CGNS_ENUMV( Unstructured ):
+ case CGNS_ENUMV( Structured ):
+ break;
+ case CGNS_ENUMV( ZoneTypeNull ):
+ addMessage( "Meshes with ZoneTypeNull are not supported");
+ continue;
+ case CGNS_ENUMV( ZoneTypeUserDefined ):
+ addMessage( "Meshes with ZoneTypeUserDefined are not supported");
+ continue;
+ default:
+ addMessage( "Unknown ZoneType_t");
+ continue;
+ }
+
+ // -----------
+ // Read nodes
+ // -----------
+
+ if ( cg_ncoords( _fn, cgnsBase, iZone, &spaceDim) != CG_OK ) {
+ addMessage( cg_get_error() );
+ continue;
+ }
+ if ( spaceDim < 1 ) {
+ addMessage( SMESH_Comment("No coordinates defined in zone ")
+ << iZone << " of Mesh " << myMeshId );
+ continue;
+ }
+ // read coordinates
+
+ int rmin[3] = {1,1,1}; // range of nodes to read
+ int rmax[3] = {1,1,1};
+ int nbNodes = rmax[0] = zone._sizes[0];
+ if ( zone.IsStructured())
+ for ( int i = 1; i < meshDim; ++i )
+ nbNodes *= rmax[i] = zone._sizes[i];
+
+ vector<double> coords[3];
+ for ( int c = 1; c <= spaceDim; ++c)
+ {
+ coords[c-1].resize( nbNodes );
+
+ CGNS_ENUMV( DataType_t ) type;
+ if ( cg_coord_info( _fn, cgnsBase, iZone, c, &type, name) != CG_OK ||
+ cg_coord_read( _fn, cgnsBase, iZone, name, CGNS_ENUMV(RealDouble),
+ rmin, rmax, (void*)&(coords[c-1][0])) != CG_OK)
+ {
+ addMessage( cg_get_error() );
+ coords[c-1].clear();
+ break;
+ }
+ }
+ if ( coords[ spaceDim-1 ].empty() )
+ continue; // there was an error while reading coordinates
+
+ // fill coords with zero if spaceDim < 3
+ for ( int c = 2; c <= 3; ++c)
+ if ( coords[ c-1 ].empty() )
+ coords[ c-1 ].resize( nbNodes, 0.0 );
+
+ // create nodes
+ try {
+ for ( int i = 0; i < nbNodes; ++i )
+ myMesh->AddNodeWithID( coords[0][i], coords[1][i], coords[2][i], i+1+zone._nodeIdShift );
+ }
+ catch ( std::exception& exc ) // expect std::bad_alloc
+ {
+ addMessage( exc.what() );
+ break;
+ }
+
+ // Read connectivity between zones. Nodes of the zone interface will be
+ // replaced withing the zones read later
+ string err = zone.ReadZonesConnection( _fn, cgnsBase, zonesByName );
+ if ( !err.empty() )
+ addMessage( err );
+
+ // --------------
+ // Read elements
+ // --------------
+ if ( zone.IsStructured())
+ {
+ int nbI = zone._sizeX - 1, nbJ = zone._sizeY - 1, nbK = zone._sizeZ - 1;
+ cgsize_t nID[8];
+ if ( meshDim > 2 && nbK > 0 )
+ {
+ for ( int k = 1; k <= nbK; ++k )
+ for ( int j = 1; j <= nbJ; ++j )
+ for ( int i = 1; i <= nbI; ++i )
+ {
+ zone.CellNodes( i, j, k, nID );
+ zone.ReplaceNodes( nID, 8 );
+ myMesh->AddVolumeWithID(nID[0],nID[1],nID[2],nID[3],nID[4],nID[5],nID[6],nID[7],
+ meshInfo.NbElements()+1);
+ }
+ }
+ else if ( meshDim > 1 && nbJ > 0 )
+ {
+ for ( int j = 1; j <= nbJ; ++j )
+ for ( int i = 1; i <= nbI; ++i )
+ {
+ zone.CellNodes( i, j, nID );
+ zone.ReplaceNodes( nID, 4 );
+ myMesh->AddFaceWithID(nID[0],nID[1],nID[2],nID[3], meshInfo.NbElements()+1);
+ }
+ }
+ else if ( meshDim > 0 && nbI > 0 )
+ {
+ nID[0] = zone.NodeID( 1, 0, 0 );
+ for ( int i = 1; i <= nbI; ++i, ++nID[0] )
+ {
+ nID[1] = nID[0]+1;
+ zone.ReplaceNodes( nID, 2 );
+ myMesh->AddEdgeWithID(nID[0],nID[1], meshInfo.NbElements()+1);
+ }
+ }
+ }
+ else
+ {
+ // elements can be stored in different sections each dedicated to one element type
+ int nbSections = 0;
+ if ( cg_nsections( _fn, cgnsBase, iZone, &nbSections) != CG_OK)
+ {
+ addMessage( cg_get_error() );
+ continue;
+ }
+ PAddElemFun* addElemFuns = getAddElemFunTable(), curAddElemFun = 0;
+ int nbNotSuppElem = 0; // nb elements of not supported types
+ bool polyhedError = false; // error at polyhedron creation
+
+ // read element data
+
+ CGNS_ENUMT( ElementType_t ) elemType;
+ cgsize_t start, end; // range of ids of elements of a zone
+ int nbBnd, parent_flag, eDataSize = 0;
+ for ( int iSec = 1; iSec <= nbSections; ++iSec )
+ {
+ if ( cg_section_read( _fn, cgnsBase, iZone, iSec, name, &elemType,
+ &start, &end, &nbBnd, &parent_flag) != CG_OK ||
+ cg_ElementDataSize( _fn, cgnsBase, iZone, iSec, &eDataSize ) != CG_OK )
+ {
+ addMessage( cg_get_error() );
+ continue;
+ }
+ vector< cgsize_t > elemData( eDataSize );
+ if ( cg_elements_read( _fn, cgnsBase, iZone, iSec, &elemData[0], NULL ) != CG_OK )
+ {
+ addMessage( cg_get_error() );
+ continue;
+ }
+ // store elements
+
+ int pos = 0, cgnsNbNodes = 0, elemID = start + zone._elemIdShift;
+ cg_npe( elemType, &cgnsNbNodes ); // get nb nodes by element type
+ curAddElemFun = addElemFuns[ elemType ];
+ SMDS_MeshElement* newElem = 0;
+ const SMDS_MeshElement* face;
+
+ while ( pos < eDataSize )
+ {
+ CGNS_ENUMT( ElementType_t ) currentType = elemType;
+ if ( currentType == CGNS_ENUMV( MIXED )) {
+ //ElementConnectivity = Etype1, Node11, Node21, ... NodeN1,
+ // Etype2, Node12, Node22, ... NodeN2,
+ // ...
+ // EtypeM, Node1M, Node2M, ... NodeNM
+ currentType = (CGNS_ENUMT(ElementType_t)) elemData[ pos++ ];
+ cg_npe( currentType, &cgnsNbNodes );
+ curAddElemFun = addElemFuns[ currentType ];
+ }
+ if ( cgnsNbNodes < 1 ) // poly elements
+ {
+ if ( currentType == CGNS_ENUMV( NFACE_n )) // polyhedron
+ {
+ //ElementConnectivity = Nfaces1, Face11, Face21, ... FaceN1,
+ // Nfaces2, Face12, Face22, ... FaceN2,
+ // ...
+ // NfacesM, Face1M, Face2M, ... FaceNM
+ const int nbFaces = elemData[ pos++ ];
+ vector<int> quantities( nbFaces );
+ vector<const SMDS_MeshNode*> nodes, faceNodes;
+ nodes.reserve( nbFaces * 4 );
+ for ( int iF = 0; iF < nbFaces; ++iF )
+ {
+ const int faceID = Abs( elemData[ pos++ ]) + zone._elemIdShift;
+ if (( face = myMesh->FindElement( faceID )) && face->GetType() == SMDSAbs_Face )
+ {
+ const bool reverse = ( elemData[ pos-1 ] < 0 );
+ const int iQuad = face->IsQuadratic() ? 1 : 0;
+ SMDS_ElemIteratorPtr nIter = face->interlacedNodesElemIterator();
+ faceNodes.assign( SMDS_MeshElement::iterator( nIter ),
+ SMDS_MeshElement::iterator());
+ if ( iQuad && reverse )
+ nodes.push_back( faceNodes[0] );
+ if ( reverse )
+ nodes.insert( nodes.end(), faceNodes.rbegin(), faceNodes.rend() - iQuad );
+ else
+ nodes.insert( nodes.end(), faceNodes.begin(), faceNodes.end() );
+
+ quantities[ iF ] = face->NbNodes();
+ }
+ else {
+ polyhedError = true;
+ break;
+ }
+ }
+ if ( quantities.back() )
+ {
+ myMesh->AddPolyhedralVolumeWithID( nodes, quantities, elemID );
+ }
+ }
+ else if ( currentType == CGNS_ENUMV( NGON_n )) // polygon
+ {
+ // ElementConnectivity = Nnodes1, Node11, Node21, ... NodeN1,
+ // Nnodes2, Node12, Node22, ... NodeN2,
+ // ...
+ // NnodesM, Node1M, Node2M, ... NodeNM
+ cgnsNbNodes = elemData[ pos ];
+ zone.ReplaceNodes( &elemData[pos+1], cgnsNbNodes, zone._nodeIdShift );
+ newElem = add_NGON( &elemData[pos ], myMesh, elemID );
+ pos += cgnsNbNodes + 1;
+ cgnsNbNodes = 0; // as mark of poly elements
+ }
+ }
+ else // standard elements
+ {
+ zone.ReplaceNodes( &elemData[pos], cgnsNbNodes, zone._nodeIdShift );
+ newElem = curAddElemFun( &elemData[pos], myMesh, elemID );
+ pos += cgnsNbNodes;
+ nbNotSuppElem += int( newElem && newElem->NbNodes() != cgnsNbNodes );
+ }
+ elemID++;
+
+ } // loop on elemData
+ } // loop on cgns sections
+
+ if ( nbNotSuppElem > 0 )
+ addMessage( SMESH_Comment(nbNotSuppElem) << " elements of not supported types"
+ << " have beem converted to close types");
+ if ( polyhedError )
+ addMessage( "Some polyhedral elements have been skipped due to internal(?) errors" );
+
+ } // reading unstructured elements
+
+ zone._nbNodes = meshInfo.NbNodes() - zone._nodeIdShift;
+ zone._nbElems = meshInfo.NbElements() - zone._elemIdShift;
+
+ // -------------------------------------------
+ // Read Boundary Conditions into SMESH groups
+ // -------------------------------------------
+ int nbBC = 0;
+ if ( cg_nbocos( _fn, cgnsBase, iZone, &nbBC) == CG_OK )
+ {
+ CGNS_ENUMT( BCType_t ) bcType;
+ CGNS_ENUMT( PointSetType_t ) psType;
+ CGNS_ENUMT( DataType_t ) normDataType;
+ cgsize_t nbPnt, normFlag;
+ int normIndex[3], nbDS;
+ for ( int iBC = 1; iBC <= nbBC; ++iBC )
+ {
+ if ( cg_boco_info( _fn, cgnsBase, iZone, iBC, name, &bcType, &psType,
+ &nbPnt, normIndex, &normFlag, &normDataType, &nbDS ) != CG_OK )
+ {
+ addMessage( cg_get_error() );
+ continue;
+ }
+ vector< cgsize_t > ids( nbPnt * zone.IndexSize() );
+ CGNS_ENUMT( GridLocation_t ) location;
+ if ( cg_boco_read( _fn, cgnsBase, iZone, iBC, &ids[0], NULL ) != CG_OK ||
+ cg_boco_gridlocation_read( _fn, cgnsBase, iZone, iBC, &location) != CG_OK )
+ {
+ addMessage( cg_get_error() );
+ continue;
+ }
+ SMDSAbs_ElementType elemType = SMDSAbs_All;
+ switch ( location ) {
+ case CGNS_ENUMV( Vertex ): elemType = SMDSAbs_Node; break;
+ case CGNS_ENUMV( FaceCenter ): elemType = SMDSAbs_Face; break;
+ case CGNS_ENUMV( IFaceCenter ): elemType = SMDSAbs_Face; break;
+ case CGNS_ENUMV( JFaceCenter ): elemType = SMDSAbs_Face; break;
+ case CGNS_ENUMV( KFaceCenter ): elemType = SMDSAbs_Face; break;
+ case CGNS_ENUMV( EdgeCenter ): elemType = SMDSAbs_Edge; break;
+ default:;
+ }
+ SMESHDS_Group* group = new SMESHDS_Group ( groupID++, myMesh, elemType );
+ myMesh->AddGroup( group );
+ SMESH_Comment groupName( name ); groupName << " " << cg_BCTypeName( bcType );
+ group->SetStoreName( groupName.c_str() );
+ SMDS_MeshGroup& groupDS = group->SMDSGroup();
+
+ if ( elemType == SMDSAbs_Node )
+ {
+ if ( zone.IsStructured() )
+ {
+ vector< cgsize_t > nodeIds;
+ if ( psType == CGNS_ENUMV( PointRange ))
+ {
+ // nodes are given as (ijkMin, ijkMax)
+ TPointRangeIterator idIt( & ids[0], meshDim );
+ nodeIds.reserve( idIt.Size() );
+ while ( idIt.More() )
+ nodeIds.push_back( zone.NodeID( idIt.Next() ));
+ }
+ else
+ {
+ // nodes are given as (ijk1, ijk2, ..., ijkN)
+ nodeIds.reserve( ids.size() / meshDim );
+ for ( size_t i = 0; i < ids.size(); i += meshDim )
+ nodeIds.push_back( zone.NodeID( ids[i], ids[i+1], ids[i+2] ));
+ }
+ ids.swap( nodeIds );
+ }
+ else if ( zone._nodeIdShift )
+ {
+ for ( size_t i = 0; i < ids.size(); ++i )
+ ids[i] += zone._nodeIdShift;
+ }
+ zone.ReplaceNodes( &ids[0], ids.size() );
+
+ for ( size_t i = 0; i < ids.size(); ++i )
+ if ( const SMDS_MeshNode* n = myMesh->FindNode( ids[i] ))
+ groupDS.Add( n );
+ }
+ else // BC applied to elements
+ {
+ if ( zone.IsStructured() )
+ {
+ int axis = 0; // axis perpendiculaire to which boundary elements are oriented
+ if ( ids.size() >= meshDim * 2 )
+ {
+ for ( ; axis < meshDim; ++axis )
+ if ( ids[axis] - ids[axis+meshDim] == 0 )
+ break;
+ }
+ else
+ {
+ for ( ; axis < meshDim; ++axis )
+ if ( normIndex[axis] != 0 )
+ break;
+ }
+ if ( axis == meshDim )
+ {
+ addMessage( SMESH_Comment("Invalid NormalIndex in BC ") << name );
+ continue;
+ }
+ const int nbElemNodesByDim[] = { 1, 2, 4, 8 };
+ const int nbElemNodes = nbElemNodesByDim[ meshDim ];
+
+ if ( psType == CGNS_ENUMV( PointRange ) ||
+ psType == CGNS_ENUMV( ElementRange ))
+ {
+ // elements are given as (ijkMin, ijkMax)
+ typedef void (TZoneData::*PGetNodesFun)( const gp_XYZ& ijk, cgsize_t* ids ) const;
+ PGetNodesFun getNodesFun = 0;
+ if ( elemType == SMDSAbs_Face && meshDim == 3 )
+ switch ( axis ) {
+ case 0: getNodesFun = & TZoneData::IFaceNodes;
+ case 1: getNodesFun = & TZoneData::JFaceNodes;
+ case 2: getNodesFun = & TZoneData::KFaceNodes;
+ }
+ else if ( elemType == SMDSAbs_Edge && meshDim == 2 )
+ switch ( axis ) {
+ case 0: getNodesFun = & TZoneData::IEdgeNodes;
+ case 1: getNodesFun = & TZoneData::JEdgeNodes;
+ }
+ if ( !getNodesFun )
+ {
+ addMessage( SMESH_Comment("Unsupported BC location in BC ") << name
+ << " " << cg_GridLocationName( location )
+ << " in " << meshDim << " mesh");
+ continue;
+ }
+ TPointRangeIterator rangeIt( & ids[0], meshDim );
+ vector< cgsize_t > elemNodeIds( rangeIt.Size() * nbElemNodes );
+ for ( int i = 0; rangeIt.More(); i+= nbElemNodes )
+ (zone.*getNodesFun)( rangeIt.Next(), &elemNodeIds[i] );
+
+ ids.swap( elemNodeIds );
+ }
+ else
+ {
+ // elements are given as (ijk1, ijk2, ..., ijkN)
+ typedef void (TZoneData::*PGetNodesFun)( int i, int j, int k, cgsize_t* ids ) const;
+ PGetNodesFun getNodesFun = 0;
+ if ( elemType == SMDSAbs_Face )
+ switch ( axis ) {
+ case 0: getNodesFun = & TZoneData::IFaceNodes;
+ case 1: getNodesFun = & TZoneData::JFaceNodes;
+ case 2: getNodesFun = & TZoneData::KFaceNodes;
+ }
+ else if ( elemType == SMDSAbs_Edge && meshDim == 2 )
+ switch ( axis ) {
+ case 0: getNodesFun = & TZoneData::IEdgeNodes;
+ case 1: getNodesFun = & TZoneData::JEdgeNodes;
+ }
+ if ( !getNodesFun )
+ {
+ addMessage( SMESH_Comment("Unsupported BC location in BC ") << name
+ << " " << cg_GridLocationName( location )
+ << " in " << meshDim << " mesh");
+ continue;
+ }
+ vector< cgsize_t > elemNodeIds( ids.size()/meshDim * nbElemNodes );
+ for ( size_t i = 0, j = 0; i < ids.size(); i += meshDim, j += nbElemNodes )
+ (zone.*getNodesFun)( ids[i], ids[i+1], ids[i+2], &elemNodeIds[j] );
+
+ ids.swap( elemNodeIds );
+ }
+ zone.ReplaceNodes( &ids[0], ids.size() );
+
+ PAddElemFun addElemFun = 0;
+ switch ( meshDim ) {
+ case 1: addElemFun = & add_BAR_2;
+ case 2: addElemFun = & add_QUAD_4;
+ case 3: addElemFun = & add_HEXA_8;
+ }
+ int elemID = meshInfo.NbElements();
+ const SMDS_MeshElement* elem = 0;
+ for ( size_t i = 0; i < ids.size(); i += nbElemNodes )
+ {
+ if ( iZone == 1 || !( elem = findElement( &ids[i], nbElemNodes, myMesh )))
+ elem = addElemFun( &ids[i], myMesh, ++elemID );
+ groupDS.Add( elem );
+ }
+ }
+ else // unstructured zone
+ {
+ if ( zone._elemIdShift )
+ for ( size_t i = 0; i < ids.size(); ++i )
+ ids[i] += zone._elemIdShift;
+
+ if ( psType == CGNS_ENUMV( PointRange ) && ids.size() == 2 )
+ {
+ for ( size_t i = ids[0]; i <= ids[1]; ++i )
+ if ( const SMDS_MeshElement* e = myMesh->FindElement( i ))
+ groupDS.Add( e );
+ }
+ else
+ {
+ for ( size_t i = 0; i < ids.size(); ++i )
+ if ( const SMDS_MeshElement* e = myMesh->FindElement( ids[i] ))
+ groupDS.Add( e );
+ }
+ }
+ } // end "BC applied to elements"
+
+ // to have group type according to a real elem type
+ group->SetType( groupDS.GetType() );
+
+ } // loop on BCs of the zone
+ }
+ else
+ {
+ addMessage( cg_get_error() );
+ }
+ } // loop on the zones of a mesh
+
+
+ // ------------------------------------------------------------------------
+ // Make groups for multiple zones and remove free nodes at zone interfaces
+ // ------------------------------------------------------------------------
+ map< string, TZoneData >::iterator nameZoneIt = zonesByName.begin();
+ for ( ; nameZoneIt != zonesByName.end(); ++nameZoneIt )
+ {
+ TZoneData& zone = nameZoneIt->second;
+ if ( zone._nbElems == 0 ) continue;
+ if ( zone._nbElems == meshInfo.NbElements() ) break; // there is only one non-empty zone
+
+ // make a group
+ SMDSAbs_ElementType elemType = myMesh->GetElementType( zone._elemIdShift + 1,
+ /*iselem=*/true );
+ SMESHDS_Group* group = new SMESHDS_Group ( groupID++, myMesh, elemType );
+ myMesh->AddGroup( group );
+ group->SetStoreName( nameZoneIt->first.c_str() );
+ SMDS_MeshGroup& groupDS = group->SMDSGroup();
+
+ for ( int i = 1; i <= zone._nbElems; ++i )
+ if ( const SMDS_MeshElement* e = myMesh->FindElement( i + zone._elemIdShift ))
+ groupDS.Add( e );
+
+ // remove free nodes
+ map< int, int >::iterator nnRmKeepIt = zone._nodeReplacementMap.begin();
+ for ( ; nnRmKeepIt != zone._nodeReplacementMap.end(); ++nnRmKeepIt )
+ if ( const SMDS_MeshNode* n = myMesh->FindNode( nnRmKeepIt->first ))
+ if ( n->NbInverseElements() == 0 )
+ myMesh->RemoveFreeNode( n, (SMESHDS_SubMesh *)0, /*fromGroups=*/false );
+ }
+
+ aResult = myErrorMessages.empty() ? DRS_OK : DRS_WARN_SKIP_ELEM;
+
+ return aResult;
+}
+
+//================================================================================
+/*!
+ * \brief Constructor
+ */
+//================================================================================
+
+DriverCGNS_Read::DriverCGNS_Read()
+{
+ _fn = -1;
+}
+//================================================================================
+/*!
+ * \brief Close the cgns file at destruction
+ */
+//================================================================================
+
+DriverCGNS_Read::~DriverCGNS_Read()
+{
+ if ( _fn > 0 )
+ cg_close( _fn );
+}
+
+//================================================================================
+/*!
+ * \brief Opens myFile
+ */
+//================================================================================
+
+Driver_Mesh::Status DriverCGNS_Read::open()
+{
+ if ( _fn < 0 )
+ {
+
+#ifdef CG_MODE_READ
+ int res = cg_open(myFile.c_str(), CG_MODE_READ, &_fn);
+#else
+ int res = cg_open(myFile.c_str(), MODE_READ, &_fn);
+#endif
+ if ( res != CG_OK)
+ {
+ addMessage( cg_get_error(), /*fatal = */true );
+ }
+ }
+ return _fn >= 0 ? DRS_OK : DRS_FAIL;
+}
+
+//================================================================================
+/*!
+ * \brief Reads nb of meshes in myFile
+ */
+//================================================================================
+
+int DriverCGNS_Read::GetNbMeshes(Status& theStatus)
+{
+ if (( theStatus = open()) != DRS_OK )
+ return 0;
+
+ int nbases = 0;
+ if(cg_nbases( _fn, &nbases) != CG_OK)
+ theStatus = addMessage( cg_get_error(), /*fatal = */true );
+
+ return nbases;
+}
--- /dev/null
+// Copyright (C) 2007-2011 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.
+//
+// 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 : DriverCGNS_Write.cxx
+// Created : Fri Aug 5 17:43:54 2011
+// Author : Edward AGAPOV (eap)
+
+#include "DriverCGNS_Write.hxx"
+
+#include "SMDS_MeshNode.hxx"
+#include "SMDS_VolumeTool.hxx"
+#include "SMESHDS_GroupBase.hxx"
+#include "SMESHDS_Mesh.hxx"
+#include "SMESH_Comment.hxx"
+
+#include <limits>
+#include <cgnslib.h>
+
+#if CGNS_VERSION < 3100
+# define cgsize_t int
+#endif
+
+using namespace std;
+
+namespace
+{
+ //================================================================================
+ /*!
+ * \brief Return interlace and type of CGNS element for the given SMDSAbs_EntityType
+ */
+ //================================================================================
+
+ const int* getInterlaceAndType( const SMDSAbs_EntityType smType,
+ CGNS_ENUMT( ElementType_t ) & cgType )
+ {
+ static vector< const int* > interlaces;
+ static vector< CGNS_ENUMT( ElementType_t )> cgTypes;
+ if ( interlaces.empty() )
+ {
+ interlaces.resize( SMDSEntity_Last, 0 );
+ cgTypes.resize( SMDSEntity_Last, CGNS_ENUMV( ElementTypeNull ));
+ {
+ static int ids[] = {0};
+ interlaces[SMDSEntity_0D] = ids;
+ cgTypes [SMDSEntity_0D] = CGNS_ENUMV( NODE );
+ }
+ {
+ static int ids[] = { 0, 1 };
+ interlaces[SMDSEntity_Edge] = ids;
+ cgTypes [SMDSEntity_Edge] = CGNS_ENUMV( BAR_2 );
+ }
+ {
+ static int ids[] = { 0, 1, 2 };
+ interlaces[SMDSEntity_Quad_Edge] = ids;
+ cgTypes [SMDSEntity_Quad_Edge] = CGNS_ENUMV( BAR_3 );
+ }
+ {
+ static int ids[] = { 0, 2, 1 };
+ interlaces[SMDSEntity_Triangle] = ids;
+ cgTypes [SMDSEntity_Triangle] = CGNS_ENUMV( TRI_3 );
+ }
+ {
+ static int ids[] = { 0, 2, 1, 5, 4, 3 };
+ interlaces[SMDSEntity_Quad_Triangle] = ids;
+ cgTypes [SMDSEntity_Quad_Triangle] = CGNS_ENUMV( TRI_6 );
+ }
+ {
+ static int ids[] = { 0, 3, 2, 1 };
+ interlaces[SMDSEntity_Quadrangle] = ids;
+ cgTypes [SMDSEntity_Quadrangle] = CGNS_ENUMV( QUAD_4 );
+ }
+ {
+ static int ids[] = { 0,3,2,1,7,6,5,4 };
+ interlaces[SMDSEntity_Quad_Quadrangle] = ids;
+ cgTypes [SMDSEntity_Quad_Quadrangle] = CGNS_ENUMV( QUAD_8 );
+ }
+ {
+ static int ids[] = { 0, 2, 1, 3 };
+ interlaces[SMDSEntity_Tetra] = ids;
+ cgTypes [SMDSEntity_Tetra] = CGNS_ENUMV( TETRA_4 );
+ }
+ {
+ static int ids[] = { 0,2,1,3,6,5,4,7,9,8 };
+ interlaces[SMDSEntity_Quad_Tetra] = ids;
+ cgTypes [SMDSEntity_Quad_Tetra] = CGNS_ENUMV( TETRA_10 );
+ }
+ {
+ static int ids[] = { 0,3,2,1,4 };
+ interlaces[SMDSEntity_Pyramid] = ids;
+ cgTypes [SMDSEntity_Pyramid] = CGNS_ENUMV( PYRA_5 );
+ }
+ {
+ static int ids[] = { 0,3,2,1,4,8,7,6,5,9,12,11,10 };
+ interlaces[SMDSEntity_Quad_Pyramid] = ids;
+ cgTypes [SMDSEntity_Quad_Pyramid] = CGNS_ENUMV( PYRA_13 );
+ }
+ {
+ static int ids[] = { 0,2,1,3,5,4 };
+ interlaces[SMDSEntity_Penta] = ids;
+ cgTypes [SMDSEntity_Penta] = CGNS_ENUMV( PENTA_6 );
+ }
+ {
+ static int ids[] = { 0,2,1,3,5,4,8,7,6,9,11,10,14,13,12 };
+ interlaces[SMDSEntity_Quad_Penta] = ids;
+ cgTypes [SMDSEntity_Quad_Penta] = CGNS_ENUMV( PENTA_15 );
+ }
+ {
+ static int ids[] = { 0,3,2,1,4,7,6,5 };
+ interlaces[SMDSEntity_Hexa] = ids;
+ cgTypes [SMDSEntity_Hexa] = CGNS_ENUMV( HEXA_8 );
+ }
+ {
+ static int ids[] = { 0,3,2,1,4,7,6,5,11,10,9,8,12,15,14,13,19,18,17,16 };
+ interlaces[SMDSEntity_Quad_Hexa] = ids;
+ cgTypes [SMDSEntity_Quad_Hexa] = CGNS_ENUMV( HEXA_20 );
+ }
+ {
+ cgTypes[SMDSEntity_Polygon] = CGNS_ENUMV( NGON_n );
+ cgTypes[SMDSEntity_Polyhedra] = CGNS_ENUMV( NFACE_n );
+ }
+ }
+ cgType = cgTypes[ smType ];
+ return interlaces[ smType ];
+ }
+
+ //================================================================================
+ /*!
+ * \brief Cut off type of boundary condition from the group name
+ */
+ //================================================================================
+
+ CGNS_ENUMT( BCType_t ) getBCType( string& groupName )
+ {
+ CGNS_ENUMT( BCType_t ) bcType = CGNS_ENUMV( BCGeneral ); // default type
+
+ // boundary condition type starts from "BC"
+ size_t bcBeg = groupName.find("BC");
+ if ( bcBeg != string::npos )
+ {
+ for ( int t = 0; t < NofValidBCTypes; ++t )
+ {
+ CGNS_ENUMT( BCType_t ) type = CGNS_ENUMT( BCType_t)( t );
+ string typeName = cg_BCTypeName( type );
+ if ( typeName == &groupName[0] + bcBeg )
+ {
+ bcType = type;
+ while ( bcBeg > 0 && isspace( bcBeg-1 ))
+ --bcBeg;
+ if ( bcBeg == 0 )
+ groupName = "Group";
+ else
+ groupName = groupName.substr( 0, bcBeg-1 );
+ }
+ }
+ }
+ return bcType;
+ }
+
+ //================================================================================
+ /*!
+ * \brief Sortable face of a polyhedron
+ */
+ struct TPolyhedFace
+ {
+ int _id; // id of NGON_n
+ vector< int > _nodes; // lowest node IDs used for sorting
+
+ TPolyhedFace( const SMDS_MeshNode** nodes, const int nbNodes, int ID):_id(ID)
+ {
+ set< int > ids;
+ for ( int i = 0; i < nbNodes; ++i )
+ ids.insert( nodes[i]->GetID() );
+
+ _nodes.resize( 3 ); // std::min( nbNodes, 4 )); hope 3 nodes is enough
+ set< int >::iterator idIt = ids.begin();
+ for ( size_t j = 0; j < _nodes.size(); ++j, ++idIt )
+ _nodes[j] = *idIt;
+ }
+ bool operator< (const TPolyhedFace& o ) const
+ {
+ return _nodes < o._nodes;
+ }
+ };
+ //================================================================================
+ /*!
+ * \brief Return CGNS id of an element
+ */
+ //================================================================================
+
+ cgsize_t cgnsID( const SMDS_MeshElement* elem,
+ const map< const SMDS_MeshElement*, cgsize_t >& elem2cgID )
+ {
+ map< const SMDS_MeshElement*, cgsize_t >::const_iterator e2id = elem2cgID.find( elem );
+ return ( e2id == elem2cgID.end() ? elem->GetID() : e2id->second );
+ }
+
+} // namespace
+
+//================================================================================
+/*!
+ * \brief Write the mesh into the CGNS file
+ */
+//================================================================================
+
+Driver_Mesh::Status DriverCGNS_Write::Perform()
+{
+ myErrorMessages.clear();
+
+ if ( !myMesh || myMesh->GetMeshInfo().NbElements() < 1 )
+ return addMessage( !myMesh ? "NULL mesh" : "Empty mesh (no elements)", /*fatal = */true );
+
+ // open the file
+ if ( cg_open(myFile.c_str(), CG_MODE_MODIFY, &_fn) != CG_OK &&
+ cg_open(myFile.c_str(), CG_MODE_WRITE, &_fn) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+
+ // create a Base
+ // --------------
+
+ const int spaceDim = 3;
+ int meshDim = 1;
+ if ( myMesh->NbFaces() > 0 ) meshDim = 2;
+ if ( myMesh->NbVolumes() > 0 ) meshDim = 3;
+
+ if ( myMeshName.empty() )
+ {
+ int nbases = 0;
+ if ( cg_nbases( _fn, &nbases) == CG_OK)
+ myMeshName = ( SMESH_Comment("Base_") << nbases+1 );
+ else
+ myMeshName = "Base_0";
+ }
+ int iBase;
+ if ( cg_base_write( _fn, myMeshName.c_str(), meshDim, spaceDim, &iBase))
+ return addMessage( cg_get_error(), /*fatal = */true );
+
+ // create a Zone
+ // --------------
+
+ int nbCells = myMesh->NbEdges();
+ if ( meshDim == 3 )
+ nbCells = myMesh->NbVolumes();
+ else if ( meshDim == 2 )
+ nbCells = myMesh->NbFaces();
+
+ cgsize_t size[9] = { myMesh->NbNodes(), nbCells, /*NBoundVertex=*/0, 0,0,0,0,0,0 };
+ int iZone;
+ if ( cg_zone_write( _fn, iBase, "SMESH_Mesh", size,
+ CGNS_ENUMV( Unstructured ), &iZone) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+
+ // Map to store only elements whose an SMDS ID differs from a CGNS one
+ typedef map< const SMDS_MeshElement*, cgsize_t > TElem2cgIDMap;
+ vector< TElem2cgIDMap > elem2cgIDByEntity( SMDSEntity_Last );
+ TElem2cgIDMap::iterator elem2cgIDIter;
+
+ TElem2cgIDMap & n2cgID = elem2cgIDByEntity[ SMDSEntity_Node ];
+
+ // Write nodes
+ // ------------
+ {
+ vector< double > coords( myMesh->NbNodes() );
+ int iC;
+ // X
+ SMDS_NodeIteratorPtr nIt = myMesh->nodesIterator( /*idInceasingOrder=*/true );
+ for ( int i = 0; nIt->more(); ++i ) coords[i] = nIt->next()->X();
+ if ( cg_coord_write( _fn, iBase, iZone, CGNS_ENUMV(RealDouble),
+ "CoordinateX", &coords[0], &iC) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+ // Y
+ nIt = myMesh->nodesIterator( /*idInceasingOrder=*/true );
+ for ( int i = 0; nIt->more(); ++i ) coords[i] = nIt->next()->Y();
+ if ( cg_coord_write( _fn, iBase, iZone, CGNS_ENUMV(RealDouble),
+ "CoordinateY", &coords[0], &iC) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+ // Z
+ nIt = myMesh->nodesIterator( /*idInceasingOrder=*/true );
+ for ( int i = 0; nIt->more(); ++i ) coords[i] = nIt->next()->Z();
+ if ( cg_coord_write( _fn, iBase, iZone, CGNS_ENUMV(RealDouble),
+ "CoordinateZ", &coords[0], &iC) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+
+ // store CGNS ids of nodes
+ nIt = myMesh->nodesIterator( /*idInceasingOrder=*/true );
+ for ( int i = 0; nIt->more(); ++i )
+ {
+ const SMDS_MeshElement* n = nIt->next();
+ if ( n->GetID() != i+1 )
+ n2cgID.insert( n2cgID.end(), make_pair( n, i+1 ));
+ }
+ }
+ // Write elements
+ // ---------------
+
+ cgsize_t cgID = 1, startID;
+
+ // write into a section all successive elements of one geom type
+ int iSec;
+ vector< cgsize_t > elemData;
+ SMDS_ElemIteratorPtr elemIt = myMesh->elementsIterator();
+ const SMDS_MeshElement* elem = elemIt->next();
+ while ( elem )
+ {
+ const SMDSAbs_EntityType elemType = elem->GetEntityType();
+ CGNS_ENUMT( ElementType_t ) cgType;
+ const int* interlace = getInterlaceAndType( elemType, cgType );
+
+ TElem2cgIDMap & elem2cgID = elem2cgIDByEntity[ elemType ];
+
+ elemData.clear();
+ startID = cgID;
+
+ if ( interlace ) // STANDARD elements
+ do
+ {
+ for ( int i = 0, nb = elem->NbNodes(); i < nb; ++i )
+ elemData.push_back( cgnsID( elem->GetNode( interlace[i] ), n2cgID ));
+ if ( elem->GetID() != cgID )
+ elem2cgID.insert( elem2cgID.end(), make_pair( elem, cgID ));
+ ++cgID;
+ elem = elemIt->more() ? elemIt->next() : 0;
+ }
+ while ( elem && elem->GetEntityType() == elemType );
+
+ else if ( elemType == SMDSEntity_Polygon ) // POLYGONS
+ do
+ {
+ elemData.push_back( elem->NbNodes() );
+ for ( int i = 0, nb = elem->NbNodes(); i < nb; ++i )
+ elemData.push_back( cgnsID( elem->GetNode(i), n2cgID ));
+ if ( elem->GetID() != cgID )
+ elem2cgID.insert( elem2cgID.end(), make_pair( elem, cgID ));
+ ++cgID;
+ elem = elemIt->more() ? elemIt->next() : 0;
+ }
+ while ( elem && elem->GetEntityType() == elemType );
+
+ else if ( elemType == SMDSEntity_Polyhedra ) // POLYHEDRA
+ {
+ // to save polyhedrons after all
+ const SMDS_MeshInfo& meshInfo = myMesh->GetMeshInfo();
+ if ( meshInfo.NbPolyhedrons() == meshInfo.NbElements() - cgID + 1 )
+ break; // only polyhedrons remain
+ while ( elem && elem->GetEntityType() == elemType )
+ elem = elemIt->more() ? elemIt->next() : 0;
+ continue;
+ }
+
+ SMESH_Comment sectionName( cg_ElementTypeName( cgType ));
+ sectionName << " " << startID << " - " << cgID-1;
+
+ if ( cg_section_write(_fn, iBase, iZone, sectionName.c_str(), cgType, startID,
+ cgID-1, /*nbndry=*/0, &elemData[0], &iSec) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+ }
+ // Write polyhedral volumes
+ // -------------------------
+
+ if ( myMesh->GetMeshInfo().NbPolyhedrons() > 0 )
+ {
+ // the polyhedron (NFACE_n) is described as a set of signed face IDs,
+ // so first we are to write all polygones (NGON_n) bounding polyhedrons
+
+ vector< cgsize_t > faceData;
+ set< TPolyhedFace > faces;
+ set< TPolyhedFace >::iterator faceInSet;
+ vector<const SMDS_MeshNode *> faceNodesVec;
+ int nbPolygones = 0, faceID;
+
+ SMDS_VolumeTool vol;
+
+ elemData.clear();
+
+ int nbPolyhTreated = 0;
+
+ TElem2cgIDMap * elem2cgID = 0;
+ TElem2cgIDMap & n2cgID = elem2cgIDByEntity[ SMDSEntity_Node ];
+
+ SMDS_ElemIteratorPtr elemIt = myMesh->elementsIterator();
+ while ( elemIt->more() )
+ {
+ elem = elemIt->next();
+ if ( elem->GetEntityType() == SMDSEntity_Polyhedra )
+ {
+ ++nbPolyhTreated;
+ vol.Set( elem );
+ vol.SetExternalNormal();
+ const int nbFaces = vol.NbFaces();
+ elemData.push_back( nbFaces );
+ for ( int iF = 0; iF < nbFaces; ++iF )
+ {
+ const int nbNodes = vol.NbFaceNodes( iF );
+ const SMDS_MeshNode** faceNodes = vol.GetFaceNodes( iF );
+ faceNodesVec.assign( faceNodes, faceNodes + nbNodes );
+ if (( elem = myMesh->FindElement( faceNodesVec, SMDSAbs_Face, /*noMedium=*/false)))
+ {
+ // a face of the polyhedron is present in the mesh
+ faceID = cgnsID( elem, elem2cgIDByEntity[ elem->GetEntityType() ]);
+ }
+ else if ( vol.IsFreeFace( iF ))
+ {
+ // the face is not shared by volumes
+ faceID = cgID++;
+ ++nbPolygones;
+ faceData.push_back( nbNodes );
+ for ( int i = 0; i < nbNodes; ++i )
+ faceData.push_back( cgnsID( faceNodes[i], n2cgID ));
+ }
+ else
+ {
+ TPolyhedFace face( faceNodes, nbNodes, cgID );
+ faceInSet = faces.insert( faces.end(), face );
+ if ( faceInSet->_id == cgID ) // the face encounters for the 1st time
+ {
+ faceID = cgID++;
+ ++nbPolygones;
+ faceData.push_back( nbNodes );
+ for ( int i = 0; i < nbNodes; ++i )
+ faceData.push_back( cgnsID( faceNodes[i], n2cgID ));
+ }
+ else
+ {
+ // the face encounters for the 2nd time; we hope it won't encounter once more,
+ // for that we can erase it from the set of faces
+ faceID = -faceInSet->_id;
+ faces.erase( faceInSet );
+ }
+ }
+ elemData.push_back( faceID );
+ }
+ }
+ }
+
+ if ( nbPolygones > 0 )
+ {
+ if ( cg_section_write(_fn, iBase, iZone, "Faces of Polyhedrons",
+ CGNS_ENUMV( NGON_n ), cgID - nbPolygones, cgID-1,
+ /*nbndry=*/0, &faceData[0], &iSec) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+ }
+
+ if ( cg_section_write(_fn, iBase, iZone, "Polyhedrons",
+ CGNS_ENUMV( NFACE_n ), cgID, cgID+nbPolyhTreated-1,
+ /*nbndry=*/0, &elemData[0], &iSec) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true );
+
+ if ( !myMesh->GetGroups().empty() )
+ {
+ // store CGNS ids of polyhedrons
+ elem2cgID = &elem2cgIDByEntity[ SMDSEntity_Polyhedra ];
+ elemIt = myMesh->elementsIterator();
+ while ( elemIt->more() )
+ {
+ elem = elemIt->next();
+ if ( elem->GetEntityType() == SMDSEntity_Polyhedra )
+ {
+ if ( elem->GetID() != cgID )
+ elem2cgID->insert( elem2cgID->end(), make_pair( elem, cgID ));
+ ++cgID;
+ }
+ }
+ }
+ } // write polyhedral volumes
+
+
+ // Write groups as boundary conditions
+ // ------------------------------------
+
+ const set<SMESHDS_GroupBase*>& groups = myMesh->GetGroups();
+ set<SMESHDS_GroupBase*>::const_iterator grpIt = groups.begin();
+ set< string > groupNames; groupNames.insert(""); // to avoid duplicated and empty names
+ for ( ; grpIt != groups.end(); ++grpIt )
+ {
+ const SMESHDS_GroupBase* group = *grpIt;
+
+ // write BC location (default is Vertex)
+ CGNS_ENUMT( GridLocation_t ) location = CGNS_ENUMV( Vertex );
+ if ( group->GetType() != SMDSAbs_Node )
+ {
+ switch ( meshDim ) {
+ case 3:
+ switch ( group->GetType() ) {
+ case SMDSAbs_Volume: location = CGNS_ENUMV( FaceCenter ); break; // !!!
+ case SMDSAbs_Face: location = CGNS_ENUMV( FaceCenter ); break; // OK
+ case SMDSAbs_Edge: location = CGNS_ENUMV( EdgeCenter ); break; // OK
+ default:;
+ }
+ break;
+ case 2:
+ switch ( group->GetType() ) {
+ case SMDSAbs_Face: location = CGNS_ENUMV( FaceCenter ); break; // ???
+ case SMDSAbs_Edge: location = CGNS_ENUMV( EdgeCenter ); break; // OK
+ default:;
+ }
+ break;
+ case 1:
+ location = CGNS_ENUMV( EdgeCenter ); break; // ???
+ break;
+ }
+ }
+
+ // try to extract type of boundary condition from the group name
+ string name = group->GetStoreName();
+ CGNS_ENUMT( BCType_t ) bcType = getBCType( name );
+ while ( !groupNames.insert( name ).second )
+ name = (SMESH_Comment( "Group_") << groupNames.size());
+
+ // write IDs of elements
+ vector< cgsize_t > pnts;
+ pnts.reserve( group->Extent() );
+ SMDS_ElemIteratorPtr elemIt = group->GetElements();
+ while ( elemIt->more() )
+ {
+ const SMDS_MeshElement* elem = elemIt->next();
+ pnts.push_back( cgnsID( elem, elem2cgIDByEntity[ elem->GetEntityType() ]));
+ }
+ int iBC;
+ if ( cg_boco_write( _fn, iBase, iZone, name.c_str(), bcType,
+ CGNS_ENUMV( PointList ), pnts.size(), &pnts[0], &iBC) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */true);
+
+ // write BC location
+ if ( location != CGNS_ENUMV( Vertex ))
+ {
+ if ( cg_boco_gridlocation_write( _fn, iBase, iZone, iBC, location) != CG_OK )
+ return addMessage( cg_get_error(), /*fatal = */false);
+ }
+ }
+ return DRS_OK;
+}
+
+//================================================================================
+/*!
+ * \brief Constructor
+ */
+//================================================================================
+
+DriverCGNS_Write::DriverCGNS_Write(): _fn(0)
+{
+}
+
+//================================================================================
+/*!
+ * \brief Close the cgns file at destruction
+ */
+//================================================================================
+
+DriverCGNS_Write::~DriverCGNS_Write()
+{
+ if ( _fn > 0 )
+ cg_close( _fn );
+}