$extrastylesheet
00001 // The libMesh Finite Element Library. 00002 // Copyright (C) 2002-2014 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner 00003 00004 // This library is free software; you can redistribute it and/or 00005 // modify it under the terms of the GNU Lesser General Public 00006 // License as published by the Free Software Foundation; either 00007 // version 2.1 of the License, or (at your option) any later version. 00008 00009 // This library is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 // Lesser General Public License for more details. 00013 00014 // You should have received a copy of the GNU Lesser General Public 00015 // License along with this library; if not, write to the Free Software 00016 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00017 00018 // C++ includes 00019 #include <string> 00020 #include <cstdlib> // std::strtol 00021 #include <sstream> 00022 #include <ctype.h> // isspace 00023 00024 // Local includes 00025 #include "libmesh/abaqus_io.h" 00026 #include "libmesh/point.h" 00027 #include "libmesh/elem.h" 00028 #include "libmesh/string_to_enum.h" 00029 #include "libmesh/boundary_info.h" 00030 #include "libmesh/utility.h" 00031 00032 // Anonymous namespace to hold mapping Data for Abaqus/libMesh element types 00033 namespace 00034 { 00035 using namespace libMesh; 00036 00041 struct ElementDefinition 00042 { 00043 // Maps (zero-based!) Abaqus local node numbers to libmesh local node numbers 00044 std::vector<unsigned> abaqus_zero_based_node_id_to_libmesh_node_id; 00045 00046 // Maps (zero-based!) Abaqus side numbers to libmesh side numbers 00047 std::vector<unsigned short> abaqus_zero_based_side_id_to_libmesh_side_id; 00048 }; 00049 00053 std::map<ElemType, ElementDefinition> eletypes; 00054 00058 void add_eletype_entry(ElemType libmesh_elem_type, 00059 const unsigned* node_map, 00060 unsigned node_map_size, 00061 const unsigned short* side_map, 00062 unsigned side_map_size) 00063 { 00064 // If map entry does not exist, this will create it 00065 ElementDefinition& map_entry = eletypes[libmesh_elem_type]; 00066 00067 00068 // Use the "swap trick" from Scott Meyer's "Effective STL" to swap 00069 // an unnamed temporary vector into the map_entry's vector. Note: 00070 // the vector(iter, iter) constructor is used. 00071 std::vector<unsigned> 00072 (node_map, node_map+node_map_size).swap 00073 (map_entry.abaqus_zero_based_node_id_to_libmesh_node_id); 00074 00075 std::vector<unsigned short> 00076 (side_map, side_map+side_map_size).swap 00077 (map_entry.abaqus_zero_based_side_id_to_libmesh_side_id); 00078 } 00079 00080 00084 void init_eletypes () 00085 { 00086 // This should happen only once. The first time this method is 00087 // called the eletypes data struture will be empty, and we will 00088 // fill it. Any subsequent calls will find an initialized 00089 // eletypes map and will do nothing. 00090 if (eletypes.empty()) 00091 { 00092 { 00093 // EDGE2 00094 const unsigned int node_map[] = {0,1}; // identity 00095 const unsigned short side_map[] = {0,1}; // identity 00096 add_eletype_entry(EDGE2, node_map, 2, side_map, 2); 00097 } 00098 00099 { 00100 // TRI3 00101 const unsigned int node_map[] = {0,1,2}; // identity 00102 const unsigned short side_map[] = {0,1,2}; // identity 00103 add_eletype_entry(TRI3, node_map, 3, side_map, 3); 00104 } 00105 00106 { 00107 // QUAD4 00108 const unsigned int node_map[] = {0,1,2,3}; // identity 00109 const unsigned short side_map[] = {0,1,2,3}; // identity 00110 add_eletype_entry(QUAD4, node_map, 4, side_map, 4); 00111 } 00112 00113 { 00114 // TET4 00115 const unsigned int node_map[] = {0,1,2,3}; // identity 00116 const unsigned short side_map[] = {0,1,2,3}; // identity 00117 add_eletype_entry(TET4, node_map, 4, side_map, 4); 00118 } 00119 00120 { 00121 // TET10 00122 const unsigned int node_map[] = {0,1,2,3,4,5,6,7,8,9}; // identity 00123 const unsigned short side_map[] = {0,1,2,3}; // identity 00124 add_eletype_entry(TET10, node_map, 10, side_map, 4); 00125 } 00126 00127 { 00128 // HEX8 00129 const unsigned int node_map[] = {0,1,2,3,4,5,6,7}; // identity 00130 const unsigned short side_map[] = {0,5,1,2,3,4}; // inverse = 0,2,3,4,5,1 00131 add_eletype_entry(HEX8, node_map, 8, side_map, 6); 00132 } 00133 00134 { 00135 // HEX20 00136 const unsigned int node_map[] = // map is its own inverse 00137 {0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19,12,13,14,15}; 00138 const unsigned short side_map[] = // inverse = 0,2,3,4,5,1 00139 {0,5,1,2,3,4}; 00140 add_eletype_entry(HEX20, node_map, 20, side_map, 6); 00141 } 00142 00143 { 00144 // HEX27 00145 const unsigned int node_map[] = // inverse = ...,21,23,24,25,26,22,20 00146 {0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19,12,13,14,15,26,20,25,21,22,23,24}; 00147 const unsigned short side_map[] = // inverse = 0,2,3,4,5,1 00148 {0,5,1,2,3,4}; 00149 add_eletype_entry(HEX27, node_map, 27, side_map, 6); 00150 } 00151 00152 { 00153 // PRISM6 00154 const unsigned int node_map[] = {0,1,2,3,4,5}; // identity 00155 const unsigned short side_map[] = {0,4,1,2,3}; // inverse = 0,2,3,4,1 00156 add_eletype_entry(PRISM6, node_map, 6, side_map, 5); 00157 } 00158 00159 { 00160 // PRISM15 00161 const unsigned int node_map[] = // map is its own inverse 00162 {0,1,2,3,4,5,6,7,8,12,13,14,9,10,11}; 00163 const unsigned short side_map[] = // inverse = 0,2,3,4,1 00164 {0,4,1,2,3}; 00165 add_eletype_entry(PRISM15, node_map, 15, side_map, 5); 00166 } 00167 00168 { 00169 // PRISM18 00170 const unsigned int node_map[] = // map is its own inverse 00171 {0,1,2,3,4,5,6,7,8,12,13,14,9,10,11,15,16,17}; 00172 const unsigned short side_map[] = // inverse = 0,2,3,4,1 00173 {0,4,1,2,3}; 00174 add_eletype_entry(PRISM18, node_map, 18, side_map, 5); 00175 } 00176 } // if (eletypes.empty()) 00177 } 00178 } // anonymous namespace 00179 00180 00181 00182 00183 00184 namespace libMesh 00185 { 00186 00187 AbaqusIO::AbaqusIO (MeshBase& mesh_in) : 00188 MeshInput<MeshBase> (mesh_in), 00189 build_sidesets_from_nodesets(false), 00190 _already_seen_part(false) 00191 { 00192 } 00193 00194 00195 00196 00197 AbaqusIO::~AbaqusIO () 00198 { 00199 } 00200 00201 00202 00203 00204 void AbaqusIO::read (const std::string& fname) 00205 { 00206 // Get a reference to the mesh we are reading 00207 MeshBase& the_mesh = MeshInput<MeshBase>::mesh(); 00208 00209 // Clear any existing mesh data 00210 the_mesh.clear(); 00211 00212 // Open stream for reading 00213 _in.open(fname.c_str()); 00214 libmesh_assert(_in.good()); 00215 00216 // Initialize the elems_of_dimension array. We will use this in a 00217 // "1-based" manner so that elems_of_dimension[d]==true means 00218 // elements of dimension d have been seen. 00219 elems_of_dimension.resize(4, false); 00220 00221 // Read file line-by-line... this is based on a set of different 00222 // test input files. I have not looked at the full input file 00223 // specs for Abaqus. 00224 std::string s; 00225 while (true) 00226 { 00227 // Try to read something. This may set EOF! 00228 std::getline(_in, s); 00229 00230 if (_in) 00231 { 00232 // Process s... 00233 // 00234 // There are many sections in Abaqus files, we read some 00235 // but others are just ignored... Some sections may occur 00236 // more than once. For example for a hybrid grid, you 00237 // will have multiple *Element sections... 00238 00239 // Some Abaqus files use all upper-case for section names, 00240 // so we will just convert s to uppercase 00241 std::string upper(s); 00242 std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper); 00243 00244 // 0.) Look for the "*Part" section 00245 if (upper.find("*PART") == static_cast<std::string::size_type>(0)) 00246 { 00247 if (_already_seen_part) 00248 libmesh_error_msg("We currently don't support reading Abaqus files with multiple PART sections"); 00249 00250 _already_seen_part = true; 00251 } 00252 00253 // 1.) Look for the "*Nodes" section 00254 if (upper.find("*NODE") == static_cast<std::string::size_type>(0)) 00255 { 00256 // Some sections that begin with *NODE are actually 00257 // "*NODE OUTPUT" sections which we want to skip. I 00258 // have only seen this with a single space, but it would 00259 // probably be more robust to remove whitespace before 00260 // making this check. 00261 if (upper.find("*NODE OUTPUT") == static_cast<std::string::size_type>(0)) 00262 continue; 00263 00264 // Some *Node sections also specify an Nset name on the same line. 00265 // Look for one here. 00266 std::string nset_name = this->parse_label(s, "nset"); 00267 00268 // Process any lines of comments that may be present 00269 this->process_and_discard_comments(); 00270 00271 // Read a block of nodes 00272 this->read_nodes(nset_name); 00273 } 00274 00275 00276 00277 // 2.) Look for the "*Element" section 00278 else if (upper.find("*ELEMENT,") == static_cast<std::string::size_type>(0)) 00279 { 00280 // Some sections that begin with *ELEMENT are actually 00281 // "*ELEMENT OUTPUT" sections which we want to skip. I 00282 // have only seen this with a single space, but it would 00283 // probably be more robust to remove whitespace before 00284 // making this check. 00285 if (upper.find("*ELEMENT OUTPUT") == static_cast<std::string::size_type>(0)) 00286 continue; 00287 00288 // Some *Element sections also specify an Elset name on the same line. 00289 // Look for one here. 00290 std::string elset_name = this->parse_label(s, "elset"); 00291 00292 // Process any lines of comments that may be present 00293 this->process_and_discard_comments(); 00294 00295 // Read a block of elements 00296 this->read_elements(upper, elset_name); 00297 } 00298 00299 00300 00301 // 3.) Look for a Nodeset section 00302 else if (upper.find("*NSET") == static_cast<std::string::size_type>(0)) 00303 { 00304 std::string nset_name = this->parse_label(s, "nset"); 00305 00306 // I haven't seen an unnamed nset yet, but let's detect it 00307 // just in case... 00308 if (nset_name == "") 00309 libmesh_error_msg("Unnamed nset encountered!"); 00310 00311 // Process any lines of comments that may be present 00312 this->process_and_discard_comments(); 00313 00314 // Read the IDs, storing them in _nodeset_ids 00315 this->read_ids(nset_name, _nodeset_ids); 00316 } // *Nodeset 00317 00318 00319 00320 // 4.) Look for an Elset section 00321 else if (upper.find("*ELSET") == static_cast<std::string::size_type>(0)) 00322 { 00323 std::string elset_name = this->parse_label(s, "elset"); 00324 00325 // I haven't seen an unnamed elset yet, but let's detect it 00326 // just in case... 00327 if (elset_name == "") 00328 libmesh_error_msg("Unnamed elset encountered!"); 00329 00330 // Process any lines of comments that may be present 00331 this->process_and_discard_comments(); 00332 00333 // Read the IDs, storing them in _elemset_ids 00334 this->read_ids(elset_name, _elemset_ids); 00335 } // *Elset 00336 00337 00338 00339 // 5.) Look for a Surface section. Need to be a little 00340 // careful, since there are also "surface interaction" 00341 // sections we don't want to read here. 00342 else if (upper.find("*SURFACE,") == static_cast<std::string::size_type>(0)) 00343 { 00344 // Get the name from the Name=Foo label. This will be the map key. 00345 std::string sideset_name = this->parse_label(s, "name"); 00346 00347 // Process any lines of comments that may be present 00348 this->process_and_discard_comments(); 00349 00350 // Read the sideset IDs 00351 this->read_sideset(sideset_name, _sideset_ids); 00352 } 00353 00354 continue; 00355 } // if (_in) 00356 00357 // If !file, check to see if EOF was set. If so, break out 00358 // of while loop. 00359 if (_in.eof()) 00360 break; 00361 00362 // If !in and !in.eof(), stream is in a bad state! 00363 libmesh_error_msg("Stream is bad! Perhaps the file: " << fname << " does not exist?"); 00364 } // while 00365 00366 // Set the Mesh dimension based on the highest dimension element seen. 00367 the_mesh.set_mesh_dimension(this->max_elem_dimension_seen()); 00368 00369 // Set element IDs based on the element sets. 00370 this->assign_subdomain_ids(); 00371 00372 // Assign nodeset values to the BoundaryInfo object 00373 this->assign_boundary_node_ids(); 00374 00375 // Assign sideset values in the BoundaryInfo object 00376 this->assign_sideset_ids(); 00377 00378 // Abaqus files only contain nodesets by default. To be useful in 00379 // applying most types of BCs in libmesh, we will definitely need 00380 // sidesets. So we can call the new BoundaryInfo function which 00381 // generates sidesets from nodesets. 00382 if (build_sidesets_from_nodesets) 00383 the_mesh.get_boundary_info().build_side_list_from_node_list(); 00384 00385 // Delete lower-dimensional elements from the Mesh. We assume these 00386 // were only used for setting BCs, and aren't part of the actual 00387 // Mesh. 00388 { 00389 unsigned char max_dim = this->max_elem_dimension_seen(); 00390 00391 MeshBase::element_iterator el = the_mesh.elements_begin(); 00392 const MeshBase::element_iterator end_el = the_mesh.elements_end(); 00393 00394 for (; el != end_el; ++el) 00395 { 00396 Elem* elem = *el; 00397 00398 if (elem->dim() < max_dim) 00399 the_mesh.delete_elem(elem); 00400 } 00401 } 00402 } 00403 00404 00405 00406 00407 00408 00409 00410 void AbaqusIO::read_nodes(std::string nset_name) 00411 { 00412 // Get a reference to the mesh we are reading 00413 MeshBase& the_mesh = MeshInput<MeshBase>::mesh(); 00414 00415 // In the input files I have, Abaqus neither tells what 00416 // the mesh dimension is nor how many nodes it has... 00417 // 00418 // The node line format is: 00419 // id, x, y, z 00420 // and you do have to parse out the commas. 00421 // The z-coordinate will only be present for 3D meshes 00422 00423 // Temporary variables for parsing lines of text 00424 char c; 00425 std::string dummy; 00426 00427 // Defines the sequential node numbering used by libmesh. Since 00428 // there can be multiple *NODE sections in an Abaqus file, we always 00429 // start our numbering with the number of nodes currently in the 00430 // Mesh. 00431 dof_id_type libmesh_node_id = the_mesh.n_nodes(); 00432 00433 // We need to duplicate some of the read_ids code if this *NODE 00434 // section also defines an NSET. We'll set up the id_storage 00435 // pointer and push back IDs into this vector in the loop below... 00436 std::vector<dof_id_type>* id_storage = NULL; 00437 if (nset_name != "") 00438 id_storage = &(_nodeset_ids[nset_name]); 00439 00440 // We will read nodes until the next line begins with *, since that will be the 00441 // next section. 00442 // TODO: Is Abaqus guaranteed to start the line with '*' or can there be leading white space? 00443 while (_in.peek() != '*' && _in.peek() != EOF) 00444 { 00445 // Re-Initialize variables to be read in from file 00446 dof_id_type abaqus_node_id=0; 00447 Real x=0, y=0, z=0; 00448 00449 // Note: we assume *at least* 2D points here, should we worry about 00450 // trying to read 1D Abaqus meshes? 00451 _in >> abaqus_node_id >> c >> x >> c >> y; 00452 00453 // Peek at the next character. If it is a comma, then there is another 00454 // value to read! 00455 if (_in.peek() == ',') 00456 _in >> c >> z; 00457 00458 // Read (and discard) the rest of the line, including the newline. 00459 // This is required so that our 'peek()' at the beginning of this 00460 // loop doesn't read the newline character, for example. 00461 std::getline(_in, dummy); 00462 00463 // If this *NODE section defines an NSET, also store the abaqus ID in id_storage 00464 if (id_storage) 00465 id_storage->push_back(abaqus_node_id); 00466 00467 // Set up the abaqus -> libmesh node mapping. This is usually just the 00468 // "off-by-one" map. 00469 _abaqus_to_libmesh_node_mapping[abaqus_node_id] = libmesh_node_id; 00470 00471 // Add the point to the mesh using libmesh's numbering, 00472 // and post-increment the libmesh node counter. 00473 the_mesh.add_point(Point(x,y,z), libmesh_node_id++); 00474 } // while 00475 } 00476 00477 00478 00479 00480 00481 void AbaqusIO::read_elements(std::string upper, std::string elset_name) 00482 { 00483 // Get a reference to the mesh we are reading 00484 MeshBase& the_mesh = MeshInput<MeshBase>::mesh(); 00485 00486 // initialize the eletypes map (eletypes is a file-global variable) 00487 init_eletypes(); 00488 00489 ElemType elem_type = INVALID_ELEM; 00490 unsigned n_nodes_per_elem = 0; 00491 00492 // Within s, we should have "type=XXXX" 00493 if (upper.find("T3D2") != std::string::npos) 00494 { 00495 elem_type = EDGE2; 00496 n_nodes_per_elem = 2; 00497 elems_of_dimension[1] = true; 00498 } 00499 else if (upper.find("CPE4") != std::string::npos || 00500 upper.find("CPS4") != std::string::npos) 00501 { 00502 elem_type = QUAD4; 00503 n_nodes_per_elem = 4; 00504 elems_of_dimension[2] = true; 00505 } 00506 else if (upper.find("CPS3") != std::string::npos || 00507 upper.find("S3R") != std::string::npos) 00508 { 00509 elem_type = TRI3; 00510 n_nodes_per_elem = 3; 00511 elems_of_dimension[2] = true; 00512 } 00513 else if (upper.find("C3D8") != std::string::npos) 00514 { 00515 elem_type = HEX8; 00516 n_nodes_per_elem = 8; 00517 elems_of_dimension[3] = true; 00518 } 00519 else if (upper.find("C3D4") != std::string::npos) 00520 { 00521 elem_type = TET4; 00522 n_nodes_per_elem = 4; 00523 elems_of_dimension[3] = true; 00524 } 00525 else if (upper.find("C3D20") != std::string::npos) 00526 { 00527 elem_type = HEX20; 00528 n_nodes_per_elem = 20; 00529 elems_of_dimension[3] = true; 00530 } 00531 else if (upper.find("C3D6") != std::string::npos) 00532 { 00533 elem_type = PRISM6; 00534 n_nodes_per_elem = 6; 00535 elems_of_dimension[3] = true; 00536 } 00537 else if (upper.find("C3D15") != std::string::npos) 00538 { 00539 elem_type = PRISM15; 00540 n_nodes_per_elem = 15; 00541 elems_of_dimension[3] = true; 00542 } 00543 else if (upper.find("C3D10") != std::string::npos) 00544 { 00545 elem_type = TET10; 00546 n_nodes_per_elem = 10; 00547 elems_of_dimension[3] = true; 00548 } 00549 else 00550 libmesh_error_msg("Unrecognized element type: " << upper); 00551 00552 // Insert the elem type we detected into the set of all elem types for this mesh 00553 _elem_types.insert(elem_type); 00554 00555 // Grab a reference to the element definition for this element type 00556 const ElementDefinition& eledef = eletypes[elem_type]; 00557 00558 // If the element definition was not found, the call above would have 00559 // created one with an uninitialized struct. Check for that here... 00560 if (eledef.abaqus_zero_based_node_id_to_libmesh_node_id.size() == 0) 00561 libmesh_error_msg("No Abaqus->LibMesh mapping information for ElemType " \ 00562 << Utility::enum_to_string(elem_type) \ 00563 << "!"); 00564 00565 // We will read elements until the next line begins with *, since that will be the 00566 // next section. 00567 while (_in.peek() != '*' && _in.peek() != EOF) 00568 { 00569 // Read the element ID, it is the first number on each line. It is 00570 // followed by a comma, so read that also. We will need this ID later 00571 // when we try to assign subdomain IDs 00572 dof_id_type abaqus_elem_id = 0; 00573 char c; 00574 _in >> abaqus_elem_id >> c; 00575 00576 // Add an element of the appropriate type to the Mesh. 00577 Elem* elem = the_mesh.add_elem(Elem::build(elem_type).release()); 00578 00579 // Associate the ID returned from libmesh with the abaqus element ID 00580 //_libmesh_to_abaqus_elem_mapping[elem->id()] = abaqus_elem_id; 00581 _abaqus_to_libmesh_elem_mapping[abaqus_elem_id] = elem->id(); 00582 00583 // The count of the total number of IDs read for the current element. 00584 unsigned id_count=0; 00585 00586 // Continue reading line-by-line until we have read enough nodes for this element 00587 while (id_count < n_nodes_per_elem) 00588 { 00589 // Read entire line (up to carriage return) of comma-separated values 00590 std::string csv_line; 00591 std::getline(_in, csv_line); 00592 00593 // Create a stream object out of the current line 00594 std::stringstream line_stream(csv_line); 00595 00596 // Process the comma-separated values 00597 std::string cell; 00598 while (std::getline(line_stream, cell, ',')) 00599 { 00600 // FIXME: factor out this strtol stuff into a utility function. 00601 char* endptr; 00602 dof_id_type abaqus_global_node_id = cast_int<dof_id_type> 00603 (std::strtol(cell.c_str(), &endptr, /*base=*/10)); 00604 00605 if (abaqus_global_node_id!=0 || cell.c_str() != endptr) 00606 { 00607 // Use the global node number mapping to determine the corresponding libmesh global node id 00608 dof_id_type libmesh_global_node_id = _abaqus_to_libmesh_node_mapping[abaqus_global_node_id]; 00609 00610 // Grab the node pointer from the mesh for this ID 00611 Node* node = the_mesh.node_ptr(libmesh_global_node_id); 00612 00613 // If node_ptr() returns NULL, it may mean we have not yet read the 00614 // *Nodes section, though I assumed that always came before the *Elements section... 00615 if (node == NULL) 00616 libmesh_error_msg("Error! Mesh returned NULL Node pointer. Either no node exists with ID " \ 00617 << libmesh_global_node_id \ 00618 << " or perhaps this input file has *Elements defined before *Nodes?"); 00619 00620 // Note: id_count is the zero-based abaqus (elem local) node index. We therefore map 00621 // it to a libmesh elem local node index using the element definition map 00622 unsigned libmesh_elem_local_node_id = 00623 eledef.abaqus_zero_based_node_id_to_libmesh_node_id[id_count]; 00624 00625 // Set this node pointer within the element. 00626 elem->set_node(libmesh_elem_local_node_id) = node; 00627 00628 // Increment the count of IDs read for this element 00629 id_count++; 00630 } // end if strtol success 00631 } // end while getline(',') 00632 } // end while (id_count) 00633 00634 // Ensure that we read *exactly* as many nodes as we were expecting to, no more. 00635 if (id_count != n_nodes_per_elem) 00636 libmesh_error_msg("Error: Needed to read " \ 00637 << n_nodes_per_elem \ 00638 << " nodes, but read " \ 00639 << id_count \ 00640 << " instead!"); 00641 00642 // If we are recording Elset IDs, add this element to the correct set for later processing. 00643 // Make sure to add it with the Abaqus ID, not the libmesh one! 00644 if (elset_name != "") 00645 _elemset_ids[elset_name].push_back(abaqus_elem_id); 00646 } // end while (peek) 00647 } 00648 00649 00650 00651 00652 std::string AbaqusIO::parse_label(std::string line, std::string label_name) 00653 { 00654 // Handle files which have weird line endings from e.g. windows. 00655 // You can check what kind of line endings you have with 'cat -vet'. 00656 // For example, some files may have two kinds of line endings like: 00657 // 00658 // 4997,^I496,^I532,^I487,^I948^M$ 00659 // 00660 // and we don't want to deal with this when extracting a label, so 00661 // just remove all the space characters, which should include all 00662 // kinds of remaining newlines. (I don't think Abaqus allows 00663 // whitespace in label names.) 00664 line.erase(std::remove_if(line.begin(), line.end(), isspace), line.end()); 00665 00666 // Do all string comparisons in upper-case 00667 std::string 00668 upper_line(line), 00669 upper_label_name(label_name); 00670 std::transform(upper_line.begin(), upper_line.end(), upper_line.begin(), ::toupper); 00671 std::transform(upper_label_name.begin(), upper_label_name.end(), upper_label_name.begin(), ::toupper); 00672 00673 // Get index of start of "label=" 00674 size_t label_index = upper_line.find(upper_label_name + "="); 00675 00676 if (label_index != std::string::npos) 00677 { 00678 // Location of the first comma following "label=" 00679 size_t comma_index = upper_line.find(",", label_index); 00680 00681 // Construct iterators from which to build the sub-string. 00682 // Note: The +1 while initializing beg is to skip past the "=" which follows the label name 00683 std::string::iterator 00684 beg = line.begin() + label_name.size() + 1 + label_index, 00685 end = (comma_index == std::string::npos) ? line.end() : line.begin() + comma_index; 00686 00687 return std::string(beg, end); 00688 } 00689 00690 // The label index was not found, return the empty string 00691 return std::string(""); 00692 } 00693 00694 00695 00696 00697 void AbaqusIO::read_ids(std::string set_name, container_t& container) 00698 { 00699 // Grab a reference to a vector that will hold all the IDs 00700 std::vector<dof_id_type>& id_storage = container[set_name]; 00701 00702 // Read until the start of another section is detected, or EOF is encountered 00703 while (_in.peek() != '*' && _in.peek() != EOF) 00704 { 00705 // Read entire comma-separated line into a string 00706 std::string csv_line; 00707 std::getline(_in, csv_line); 00708 00709 // On that line, use std::getline again to parse each 00710 // comma-separated entry. 00711 std::string cell; 00712 std::stringstream line_stream(csv_line); 00713 while (std::getline(line_stream, cell, ',')) 00714 { 00715 // If no conversion can be performed by strtol, 0 is returned. 00716 // 00717 // If endptr is not NULL, strtol() stores the address of the 00718 // first invalid character in *endptr. If there were no 00719 // digits at all, however, strtol() stores the original 00720 // value of str in *endptr. 00721 char* endptr; 00722 00723 // FIXME - this needs to be updated for 64-bit inputs 00724 dof_id_type id = cast_int<dof_id_type> 00725 (std::strtol(cell.c_str(), &endptr, /*base=*/10)); 00726 00727 // Note that lists of comma-separated values in abaqus also 00728 // *end* with a comma, so the last call to getline on a given 00729 // line will get an empty string, which we must detect. 00730 if (id != 0 || cell.c_str() != endptr) 00731 { 00732 // 'cell' is now a string with an integer id in it 00733 id_storage.push_back( id ); 00734 } 00735 } 00736 } 00737 } 00738 00739 00740 00741 00742 void AbaqusIO::read_sideset(std::string sideset_name, sideset_container_t& container) 00743 { 00744 // Grab a reference to a vector that will hold all the IDs 00745 std::vector<std::pair<dof_id_type, unsigned> >& id_storage = container[sideset_name]; 00746 00747 // Variables for storing values read in from file 00748 dof_id_type elem_id=0; 00749 unsigned side_id=0; 00750 char c; 00751 std::string dummy; 00752 00753 // Read until the start of another section is detected, or EOF is encountered 00754 while (_in.peek() != '*' && _in.peek() != EOF) 00755 { 00756 // The strings are of the form: "391, S2" 00757 00758 // Read the element ID and the leading comma 00759 _in >> elem_id >> c; 00760 00761 // Read another character (the 'S') and finally the side ID 00762 _in >> c >> side_id; 00763 00764 // Store this pair of data in the vector 00765 id_storage.push_back( std::make_pair(elem_id, side_id) ); 00766 00767 // Extract remaining characters on line including newline 00768 std::getline(_in, dummy); 00769 } // while 00770 } 00771 00772 00773 00774 00775 void AbaqusIO::assign_subdomain_ids() 00776 { 00777 // Get a reference to the mesh we are reading 00778 MeshBase& the_mesh = MeshInput<MeshBase>::mesh(); 00779 00780 // The number of elemsets we've found while reading 00781 std::size_t n_elemsets = _elemset_ids.size(); 00782 00783 // Fill in a temporary map with (ElemType, index) pairs based on the _elem_types set. This 00784 // will allow us to easily look up this index in the loop below. 00785 std::map<ElemType, unsigned> elem_types_map; 00786 { 00787 unsigned ctr=0; 00788 for (std::set<ElemType>::iterator it=_elem_types.begin(); it!=_elem_types.end(); ++it) 00789 elem_types_map[*it] = ctr++; 00790 } 00791 00792 // Loop over each Elemset and assign subdomain IDs to Mesh elements 00793 { 00794 // The maximum element dimension seen while reading the Mesh 00795 unsigned char max_dim = this->max_elem_dimension_seen(); 00796 00797 // The elemset_id counter assigns a logical numbering to the _elemset_ids keys 00798 container_t::iterator it = _elemset_ids.begin(); 00799 for (unsigned elemset_id=0; it != _elemset_ids.end(); ++it, ++elemset_id) 00800 { 00801 // Grab a reference to the vector of IDs 00802 std::vector<dof_id_type>& id_vector = it->second; 00803 00804 // Loop over this vector 00805 for (std::size_t i=0; i<id_vector.size(); ++i) 00806 { 00807 // Map the id_vector[i]'th element ID (Abaqus numbering) to LibMesh numbering 00808 dof_id_type libmesh_elem_id = _abaqus_to_libmesh_elem_mapping[ id_vector[i] ]; 00809 00810 // Get pointer to that element 00811 Elem* elem = the_mesh.elem(libmesh_elem_id); 00812 00813 if (elem == NULL) 00814 libmesh_error_msg("Mesh returned NULL pointer for Elem " << libmesh_elem_id); 00815 00816 // We won't assign subdomain ids to lower-dimensional 00817 // elements, as they are assumed to represent boundary 00818 // conditions. Since lower-dimensional elements can 00819 // appear in multiple sidesets, it doesn't make sense to 00820 // assign them a single subdomain id... only the last one 00821 // assigned would actually "stick". 00822 if (elem->dim() < max_dim) 00823 break; 00824 00825 // Compute the proper subdomain ID, based on the formula in the 00826 // documentation for this function. 00827 subdomain_id_type computed_id = cast_int<subdomain_id_type> 00828 (elemset_id + (elem_types_map[elem->type()] * n_elemsets)); 00829 00830 // Assign this ID to the element in question 00831 elem->subdomain_id() = computed_id; 00832 00833 // We will also assign a unique name to the computed_id, 00834 // which is created by appending the geometric element 00835 // name to the elset name provided by the user in the 00836 // Abaqus file. 00837 std::string computed_name = it->first + "_" + Utility::enum_to_string(elem->type()); 00838 the_mesh.subdomain_name(computed_id) = computed_name; 00839 } 00840 } 00841 } 00842 } 00843 00844 00845 00846 00847 void AbaqusIO::assign_boundary_node_ids() 00848 { 00849 // Get a reference to the mesh we are reading 00850 MeshBase& the_mesh = MeshInput<MeshBase>::mesh(); 00851 00852 // Iterate over the container of nodesets 00853 container_t::iterator it = _nodeset_ids.begin(); 00854 for (unsigned short current_id=0; it != _nodeset_ids.end(); ++it, ++current_id) 00855 { 00856 // Associate current_id with the name we determined earlier 00857 the_mesh.get_boundary_info().nodeset_name(current_id) = it->first; 00858 00859 // Get a reference to the current vector of nodeset ID values 00860 std::vector<dof_id_type>& nodeset_ids = it->second; 00861 00862 for (std::size_t i=0; i<nodeset_ids.size(); ++i) 00863 { 00864 // Map the Abaqus global node ID to the libmesh node ID 00865 dof_id_type libmesh_global_node_id = _abaqus_to_libmesh_node_mapping[nodeset_ids[i]]; 00866 00867 // Get node pointer from the mesh 00868 Node* node = the_mesh.node_ptr(libmesh_global_node_id); 00869 00870 if (node == NULL) 00871 libmesh_error_msg("Error! Mesh returned NULL node pointer!"); 00872 00873 // Add this node with the current_id (which is determined by the 00874 // alphabetical ordering of the map) to the BoundaryInfo object 00875 the_mesh.get_boundary_info().add_node(node, current_id); 00876 } 00877 } 00878 } 00879 00880 00881 00882 00883 void AbaqusIO::assign_sideset_ids() 00884 { 00885 // Get a reference to the mesh we are reading 00886 MeshBase& the_mesh = MeshInput<MeshBase>::mesh(); 00887 00888 // initialize the eletypes map (eletypes is a file-global variable) 00889 init_eletypes(); 00890 00891 // Iterate over the container of sidesets 00892 { 00893 sideset_container_t::iterator it = _sideset_ids.begin(); 00894 for (unsigned short current_id=0; it != _sideset_ids.end(); ++it, ++current_id) 00895 { 00896 // Associate current_id with the name we determined earlier 00897 the_mesh.get_boundary_info().sideset_name(current_id) = it->first; 00898 00899 // Get a reference to the current vector of nodeset ID values 00900 std::vector<std::pair<dof_id_type,unsigned> >& sideset_ids = it->second; 00901 00902 for (std::size_t i=0; i<sideset_ids.size(); ++i) 00903 { 00904 // sideset_ids is a vector of pairs (elem id, side id). Pull them out 00905 // now to make the code below more readable. 00906 dof_id_type abaqus_elem_id = sideset_ids[i].first; 00907 unsigned abaqus_side_number = sideset_ids[i].second; 00908 00909 // Map the Abaqus element ID to LibMesh numbering 00910 dof_id_type libmesh_elem_id = _abaqus_to_libmesh_elem_mapping[ abaqus_elem_id ]; 00911 00912 // Get pointer to that element 00913 Elem* elem = the_mesh.elem(libmesh_elem_id); 00914 00915 // Check that the pointer returned from the Mesh is non-NULL 00916 if (elem == NULL) 00917 libmesh_error_msg("Mesh returned NULL pointer for Elem " << libmesh_elem_id); 00918 00919 // Grab a reference to the element definition for this element type 00920 const ElementDefinition& eledef = eletypes[elem->type()]; 00921 00922 // If the element definition was not found, the call above would have 00923 // created one with an uninitialized struct. Check for that here... 00924 if (eledef.abaqus_zero_based_side_id_to_libmesh_side_id.size() == 0) 00925 libmesh_error_msg("No Abaqus->LibMesh mapping information for ElemType " \ 00926 << Utility::enum_to_string(elem->type()) \ 00927 << "!"); 00928 00929 // Add this node with the current_id (which is determined by the 00930 // alphabetical ordering of the map). Side numbers in Abaqus are 1-based, 00931 // so we subtract 1 here before passing the abaqus side number to the 00932 // mapping array 00933 the_mesh.get_boundary_info().add_side 00934 (elem, 00935 eledef.abaqus_zero_based_side_id_to_libmesh_side_id[abaqus_side_number-1], 00936 current_id); 00937 } 00938 } 00939 } 00940 00941 00942 // Some elsets (if they contain lower-dimensional elements) also 00943 // define sidesets. So loop over them and build a searchable data 00944 // structure we can use to assign sidesets. 00945 { 00946 unsigned char max_dim = this->max_elem_dimension_seen(); 00947 00948 // multimap from "vector-of-lower-dimensional-element-node-ids" to subdomain ID which should be applied. 00949 // We use a multimap because the lower-dimensional elements can belong to more than 1 sideset. 00950 typedef std::multimap<std::vector<dof_id_type>, unsigned> provide_bcs_t; 00951 provide_bcs_t provide_bcs; 00952 00953 // The elemset_id counter assigns a logical numbering to the _elemset_ids keys 00954 container_t::iterator it = _elemset_ids.begin(); 00955 for (unsigned short elemset_id=0; it != _elemset_ids.end(); ++it, ++elemset_id) 00956 { 00957 // Grab a reference to the vector of IDs 00958 std::vector<dof_id_type>& id_vector = it->second; 00959 00960 // Loop over this vector 00961 for (std::size_t i=0; i<id_vector.size(); ++i) 00962 { 00963 // Map the id_vector[i]'th element ID (Abaqus numbering) to LibMesh numbering 00964 dof_id_type libmesh_elem_id = _abaqus_to_libmesh_elem_mapping[ id_vector[i] ]; 00965 00966 // Get pointer to that element 00967 Elem* elem = the_mesh.elem(libmesh_elem_id); 00968 00969 if (elem == NULL) 00970 libmesh_error_msg("Mesh returned NULL pointer for Elem " << libmesh_elem_id); 00971 00972 // If the element dimension is equal to the maximum 00973 // dimension seen, we can break out of this for loop -- 00974 // this elset does not contain sideset information. 00975 if (elem->dim() == max_dim) 00976 break; 00977 00978 // We can only handle elements that are *exactly* 00979 // one dimension lower than the max element 00980 // dimension. Not sure if "edge" BCs in 3D 00981 // actually make sense/are required... 00982 if (elem->dim()+1 != max_dim) 00983 libmesh_error_msg("ERROR: Expected boundary element of dimension " << max_dim-1 << " but got " << elem->dim()); 00984 00985 // To be pushed into the provide_bcs data container 00986 std::vector<dof_id_type> elem_node_ids(elem->n_nodes()); 00987 00988 // Save node IDs in a local vector which will be used as a key for the map. 00989 for (unsigned n=0; n<elem->n_nodes(); n++) 00990 elem_node_ids[n] = elem->node(n); 00991 00992 // Sort before putting into the map 00993 std::sort(elem_node_ids.begin(), elem_node_ids.end()); 00994 00995 // Insert the (key, id) pair into the multimap 00996 provide_bcs.insert(std::make_pair(elem_node_ids, elemset_id)); 00997 00998 // Associate the name of this sideset with the ID we've 00999 // chosen. It's not necessary to do this for every 01000 // element in the set, but it's convenient to do it here 01001 // since we have all the necessary information... 01002 the_mesh.get_boundary_info().sideset_name(elemset_id) = it->first; 01003 } 01004 } 01005 01006 // Loop over elements and try to assign boundary information 01007 { 01008 MeshBase::element_iterator e_it = the_mesh.active_elements_begin(); 01009 const MeshBase::element_iterator end = the_mesh.active_elements_end(); 01010 for ( ; e_it != end; ++e_it) 01011 { 01012 Elem* elem = *e_it; 01013 01014 if (elem->dim() == max_dim) 01015 { 01016 // This is a max-dimension element that may require BCs. 01017 // For each of its sides, including internal sides, we'll 01018 // see if a lower-dimensional element provides boundary 01019 // information for it. Note that we have not yet called 01020 // find_neighbors(), so we can't use elem->neighbor(sn) in 01021 // this algorithm... 01022 for (unsigned short sn=0; sn<elem->n_sides(); sn++) 01023 { 01024 UniquePtr<Elem> side (elem->build_side(sn)); 01025 01026 // Build up a node_ids vector, which is the key 01027 std::vector<dof_id_type> node_ids(side->n_nodes()); 01028 for (unsigned n=0; n<side->n_nodes(); n++) 01029 node_ids[n] = side->node(n); 01030 01031 // Sort the vector before using it as a key 01032 std::sort(node_ids.begin(), node_ids.end()); 01033 01034 // Look for this key in the provide_bcs multimap 01035 std::pair<provide_bcs_t::const_iterator, provide_bcs_t::const_iterator> 01036 range = provide_bcs.equal_range (node_ids); 01037 01038 // Add boundary information for each side in the range. 01039 for (provide_bcs_t::const_iterator s_it = range.first; 01040 s_it != range.second; ++s_it) 01041 the_mesh.get_boundary_info().add_side 01042 (elem, sn, cast_int<unsigned short>(s_it->second)); 01043 } 01044 } 01045 } 01046 } 01047 } 01048 } 01049 01050 01051 01052 void AbaqusIO::process_and_discard_comments() 01053 { 01054 std::string dummy; 01055 while (true) 01056 { 01057 // We assume we are at the beginning of a line that may be 01058 // comments or may be data. We need to only discard the line if 01059 // it begins with **, but we must avoid calling std::getline() 01060 // since there's no way to put that back. 01061 if (_in.peek() == '*') 01062 { 01063 // The first character was a star, so actually read it from the stream. 01064 _in.get(); 01065 01066 // Peek at the next character... 01067 if (_in.peek() == '*') 01068 { 01069 // OK, second character was star also, by definition this 01070 // line must be a comment! Read the rest of the line and discard! 01071 std::getline(_in, dummy); 01072 } 01073 else 01074 { 01075 // The second character was _not_ a star, so put back the first star 01076 // we pulled out so that the line can be parsed correctly by somebody 01077 // else! 01078 _in.unget(); 01079 01080 // Finally, break out of the while loop, we are done parsing comments 01081 break; 01082 } 01083 } 01084 else 01085 { 01086 // First character was not *, so this line must be data! Break out of the 01087 // while loop! 01088 break; 01089 } 01090 } 01091 } 01092 01093 01094 01095 unsigned char AbaqusIO::max_elem_dimension_seen () 01096 { 01097 unsigned char max_dim = 0; 01098 01099 unsigned char elem_dimensions_size = cast_int<unsigned char> 01100 (elems_of_dimension.size()); 01101 // The elems_of_dimension array is 1-based in the UNV reader 01102 for (unsigned char i=1; i<elem_dimensions_size; ++i) 01103 if (elems_of_dimension[i]) 01104 max_dim = i; 01105 01106 return max_dim; 01107 } 01108 01109 01110 } // namespace