├── .gitignore ├── LICENSE ├── README.md ├── p3d2gmsh.py └── tests ├── n0012.msh ├── n0012.nmf ├── n0012.p3d ├── n0012_settings.nml ├── shear_4levelsdown.msh ├── shear_4levelsdown.nmf └── shear_4levelsdown.p3dfmt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | .DS_Store 57 | .idea 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexey Matveichev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NASA's Plot3D to Gmsh's MSH converter 2 | 3 | Small Python script to convert from Plot3D into Gmsh mesh format. In general 4 | you'll need mesh in Plot3D format (script was tested with files from 5 | ) and Neutral Map File for boundary 6 | description (also available from NASA for 3D versions of the meshes). 7 | 8 | ## Usage 9 | 10 | ```sh 11 | $ p3d2gmsh.py [-o OUT_FILE] [-m MAP_FILE] P3D_FILE 12 | 13 | P3D_FILE: name of file with the mesh 14 | MAP_FILE: Neutral Map File (if omitted script will search for a file with 15 | nmf extension and the name of mesh file) 16 | OUT_FILE: name of output file (if omitted script will save a file with msh 17 | extension and the name of the mesh file) 18 | ``` 19 | -------------------------------------------------------------------------------- /p3d2gmsh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Convert files from NASA's Plot3D mesh format to Gmsh's MSH. 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2015-2020 Alexey Matveichev 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | """ 27 | 28 | import sys 29 | import argparse 30 | import os.path 31 | import numpy as np 32 | 33 | 34 | def read_chunk(f, t): 35 | """Read a whitespace-delimited chunk of a file, returns chunk, converted to a given type.""" 36 | res = "" 37 | while True: 38 | try: 39 | c = f.read(1) 40 | if len(c) == 0: 41 | return None 42 | if c.isspace() and len(res) > 0: 43 | return t(res) 44 | if not c.isspace(): 45 | res += c 46 | except EOFError: 47 | return None 48 | 49 | 50 | class NeutralMapFile(object): 51 | """NASA's Neutral Map File representation.""" 52 | @staticmethod 53 | def skip_comments(fp): 54 | """Skip lines starting with #.""" 55 | while True: 56 | pos = fp.tell() 57 | try: 58 | l = fp.readline() 59 | if not l.startswith('#'): 60 | fp.seek(pos) 61 | break 62 | except IOError: 63 | break 64 | 65 | def __init__(self, filename=None): 66 | """Parse boundaries from :filename:.""" 67 | self.__boundaries = [] 68 | if filename is not None: 69 | fp = open(filename, 'r') 70 | # Skip initial comments 71 | NeutralMapFile.skip_comments(fp) 72 | # Blocks 73 | l = fp.readline() 74 | if l.endswith('\\\n'): 75 | l = l[0:-2] 76 | nblocks = int(l) 77 | fp.readline() 78 | for _ in range(nblocks): 79 | fp.readline() 80 | fp.readline() 81 | # Middle comments 82 | NeutralMapFile.skip_comments(fp) 83 | # Boundaries 84 | for l in fp: 85 | if l.endswith('\\'): 86 | b = l[0:-2].split() 87 | else: 88 | b = l.split() 89 | if len(b) > 0: 90 | if b[0][0] in ['\'', '"']: 91 | b[0] = b[0][1:-1] 92 | if b[0].upper() in ['ONE-TO-ONE', 'ONE_TO_ONE']: 93 | # Creating two boundaries for stitching 94 | b1 = ['to-stitch-a'] + [int(c) for c in b[1:7]] 95 | b2 = ['to-stitch-b'] + [int(c) for c in b[7:13]] 96 | self.__boundaries.append(tuple(b1)) 97 | self.__boundaries.append(tuple(b2)) 98 | continue 99 | b[1:] = map(int, b[1:7]) 100 | self.__boundaries.append(tuple(b)) 101 | fp.close() 102 | 103 | @property 104 | def boundaries(self): 105 | """Get boundaries list.""" 106 | return self.__boundaries 107 | 108 | def __str__(self): 109 | """Convert boundary to string.""" 110 | return 'Neutral map file / {0:d} boundaries'.format( 111 | len(self.__boundaries)) 112 | 113 | 114 | class P3DfmtFile(object): 115 | """P3Dfmt file representation.""" 116 | def __init__(self, filename=None, **kwargs): 117 | """Construct from components or load from file.""" 118 | if filename: 119 | self.load(filename=filename) 120 | else: 121 | if kwargs is not None: 122 | self.__nblocks = kwargs['nblocks'] \ 123 | if 'nblocks' in kwargs else 0 124 | self.__coords = kwargs['coords'] \ 125 | if 'coords' in kwargs else None 126 | else: 127 | self.__nblocks = None 128 | self.__coords = None 129 | 130 | @property 131 | def nblocks(self): 132 | """Number of blocks in the file.""" 133 | return self.__nblocks 134 | 135 | def idims(self, nblk=1): 136 | """Return i-dimensions of the file.""" 137 | return self.__coords[nblk - 1][0].shape[0] 138 | 139 | def jdims(self, nblk=1): 140 | """Return j-dimensions of the file.""" 141 | return self.__coords[nblk - 1][0].shape[1] 142 | 143 | def kdims(self, nblk=1): 144 | """Return j-dimensions of the file.""" 145 | return self.__coords[nblk - 1][0].shape[2] 146 | 147 | @property 148 | def coords(self): 149 | """Return coordinates stored in the file.""" 150 | return self.__coords 151 | 152 | def load(self, filename): 153 | """Load mesh blocks from the given file.""" 154 | fp = open(filename) 155 | 156 | # Reading number of blocks 157 | self.__nblocks = read_chunk(fp, int) 158 | 159 | # Reading dimensions 160 | idims = np.zeros(self.__nblocks, 'i') 161 | jdims = np.zeros(self.__nblocks, 'i') 162 | kdims = np.zeros(self.__nblocks, 'i') 163 | 164 | for i in range(self.__nblocks): 165 | idims[i] = read_chunk(fp, int) 166 | jdims[i] = read_chunk(fp, int) 167 | kdims[i] = read_chunk(fp, int) 168 | 169 | # Reading coordinates 170 | self.__coords = [] 171 | 172 | for b in range(self.__nblocks): 173 | idim = idims[b] 174 | jdim = jdims[b] 175 | kdim = kdims[b] 176 | x = np.zeros((idim, jdim, kdim), 'f8') 177 | y = np.zeros((idim, jdim, kdim), 'f8') 178 | z = np.zeros((idim, jdim, kdim), 'f8') 179 | 180 | coords = [x, y, z] 181 | for c in coords: 182 | for k in range(kdim): 183 | for j in range(jdim): 184 | for i in range(idim): 185 | c[i, j, k] = read_chunk(fp, float) 186 | 187 | self.__coords.append((x, y, z)) 188 | 189 | fp.close() 190 | 191 | def __str__(self): 192 | """Convert to string.""" 193 | idims = [x.shape[0] for x, _, _ in self.__coords] 194 | jdims = [x.shape[1] for x, _, _ in self.__coords] 195 | kdims = [x.shape[2] for x, _, _ in self.__coords] 196 | return 'P3Dfmt file (blocks: %d/idims: (%s)/jdims: (%s)/kdims: (%s)' % \ 197 | (self.__nblocks, ' '.join(map(str, idims)), 198 | ' '.join(map(str, jdims)), ' '.join(map(str, kdims))) 199 | 200 | def save(self, filename=None): 201 | """Save file, to stdout if no filename is given.""" 202 | raise NotImplementedError 203 | 204 | def dump_coords(self): 205 | """Dump coordinates of the file as a list.""" 206 | for n in range(self.__nblocks): 207 | idim = self.__coords[n][0].shape[0] 208 | jdim = self.__coords[n][0].shape[1] 209 | kdim = self.__coords[n][0].shape[2] 210 | 211 | x, y, z = self.__coords[n] 212 | 213 | for i in range(idim): 214 | for j in range(jdim): 215 | for k in range(kdim): 216 | print('%lf %lf %lf' % 217 | (x[i, j, k], y[i, j, k], z[i, j, k])) 218 | 219 | 220 | class GmshFile(object): 221 | """Gmsh file representation.""" 222 | 223 | # For conversion purposes I need only two types of elements: 224 | # - quadrangle (3) for the faces 225 | # - hexagon (5) for the cells 226 | # So there will be no constants. 227 | 228 | __DEFAULT_GEOMETRY_GROUP = 1 229 | 230 | def __init__(self, nodes=None, elements=None, groups=None, filename=None): 231 | """Construct from components. 232 | 233 | If filename is provided object is loaded from the file. 234 | 235 | :nodes: 236 | List of node tuples 237 | 238 | :elements: 239 | List of element tuples 240 | 241 | :groups: 242 | List of group tuples 243 | 244 | :filename: 245 | Name of file to load data from 246 | """ 247 | self.__element_id = 0 248 | if filename: 249 | self.__nodes = [] 250 | self.__elements = [] 251 | self.__groups = [] 252 | self.load(filename) 253 | else: 254 | self.__nodes = [] if nodes is None else nodes 255 | self.__elements = [] if elements is None else elements 256 | self.__groups = [] if groups is None else groups 257 | 258 | @property 259 | def nodes(self): 260 | """Return nodes of the current file.""" 261 | return self.__nodes 262 | 263 | @property 264 | def elements(self): 265 | """Return elements of the current file.""" 266 | return self.__elements 267 | 268 | @property 269 | def groups(self): 270 | """Return physical groups of the current file.""" 271 | return self.__groups 272 | 273 | def load(self, filename=None): 274 | """Load nodes, elements, and groups from the given file.""" 275 | raise NotImplementedError 276 | 277 | def __str__(self): 278 | """Create string representation of the file.""" 279 | return 'GMSH file (nodes: %d, elements: %d, groups: %d)' % \ 280 | (len(self.__nodes), len(self.__elements), len(self.__groups)) 281 | 282 | def save(self, filename=None): 283 | """Save file, to stdout if no filename is given.""" 284 | if filename: 285 | fp = open(filename, 'w') 286 | else: 287 | fp = sys.stdout 288 | 289 | GmshFile._write_header(fp) 290 | self._write_groups(fp) 291 | self._write_nodes(fp) 292 | self._write_elements(fp) 293 | 294 | @staticmethod 295 | def _write_header(out): 296 | """Write standard Gmsh file header.""" 297 | out.write('$MeshFormat\n') 298 | out.write('2.2 0 8\n') 299 | out.write('$EndMeshFormat\n') 300 | 301 | def _write_groups(self, out): 302 | """Write Gmsh file physical groups.""" 303 | out.write('$PhysicalNames\n') 304 | out.write('%d\n' % len(self.__groups)) 305 | for grp in self.__groups: 306 | out.write('%d %d "%s"\n' % grp) 307 | out.write('$EndPhysicalNames\n') 308 | 309 | def _write_nodes(self, out): 310 | """Write Gmsh file nodes.""" 311 | out.write('$Nodes\n') 312 | out.write('%d\n' % len(self.__nodes)) 313 | for node in self.__nodes: 314 | out.write('%d %15.13e %15.13e %15.13e\n' % node) 315 | out.write('$EndNodes\n') 316 | 317 | def _write_elements(self, out): 318 | """Write Gmsh file elements.""" 319 | out.write('$Elements\n') 320 | out.write('%d\n' % len(self.__elements)) 321 | for el in self.__elements: 322 | out.write('%s\n' % ' '.join(map(str, el))) 323 | out.write('$EndElements\n') 324 | 325 | def consume(self, p3dfmt_file, mapfile=None): 326 | """Convert P3Dfmt file into self. 327 | 328 | :p3dfmt_file: 329 | P3DfmtFile object to convert. 330 | 331 | :mapfile: 332 | Neutral map file name, for boundary faces 333 | """ 334 | self.__groups.append((3, 1, 'mesh')) 335 | for blkn in range(p3dfmt_file.nblocks): 336 | self._consume_block(p3dfmt_file, blkn) 337 | 338 | for bdry in mapfile.boundaries: 339 | self._gen_boundary(p3dfmt_file, bdry) 340 | 341 | @staticmethod 342 | def __find_smallest_cell(p2dfmt_file): 343 | dx, dy = 0, 0 344 | for blk in range(p2dfmt_file.nblocks): 345 | x, y = p2dfmt_file.coords[blk] 346 | idim, jdim = x.shape 347 | dx = x[1, 0] - x[0, 0] 348 | dy = y[0, 1] - y[0, 0] 349 | for i in range(1, idim): 350 | for j in range(1, jdim): 351 | dx = min(dx, x[i, j] - x[i - 1, j]) 352 | dy = min(dy, y[i, j] - y[i, j - 1]) 353 | return min(dx, dy) 354 | 355 | @staticmethod 356 | def _p3d_node_id(p3dfmt_file, n, i, j, k): 357 | if n >= p3dfmt_file.nblocks: 358 | raise IndexError('Block number %d is out of range.' % n) 359 | 360 | basen = 1 361 | if p3dfmt_file.nblocks > 1: 362 | for idx in range(n): 363 | x, _, _ = p3dfmt_file.coords[idx] 364 | di, dj, dk = x.shape 365 | basen += di * dj * dk 366 | 367 | x, _, _ = p3dfmt_file.coords[n] 368 | _, dj, dk = x.shape 369 | 370 | return basen + k + dk * j + dk * dj * i 371 | 372 | def get_next_element_id(self): 373 | """Generate ID of the next element.""" 374 | self.__element_id += 1 375 | return self.__element_id 376 | 377 | def _consume_block(self, p3dfmt_file, blkn): 378 | x, y, z = p3dfmt_file.coords[blkn] 379 | idim, jdim, kdim = x.shape 380 | 381 | # Filling nodes list 382 | for i in range(idim): 383 | for j in range(jdim): 384 | for k in range(kdim): 385 | node_id = GmshFile._p3d_node_id(p3dfmt_file, blkn, i, j, k) 386 | self.__nodes.append( 387 | (node_id, x[i, j, k], y[i, j, k], z[i, j, k])) 388 | 389 | # Generating 3D elements 390 | shifts = [ 391 | [-1, -1, -1], 392 | [-1, 0, -1], 393 | [-1, 0, 0], 394 | [-1, -1, 0], 395 | [0, -1, -1], 396 | [0, 0, -1], 397 | [0, 0, 0], 398 | [0, -1, 0], 399 | ] 400 | 401 | for i in range(1, idim): 402 | for j in range(1, jdim): 403 | for k in range(1, kdim): 404 | el_id = self.get_next_element_id() 405 | el = [el_id, 5, 2, 1, GmshFile.__DEFAULT_GEOMETRY_GROUP] 406 | for s in shifts: 407 | el.append( 408 | GmshFile._p3d_node_id(p3dfmt_file, blkn, i + s[0], 409 | j + s[1], k + s[2])) 410 | self.__elements.append(el) 411 | 412 | def _next_group_id(self): 413 | return max(self.__groups, key=lambda n: n[1])[1] + 1 414 | 415 | def _gen_boundary(self, p3df, bdry): 416 | gid = self._next_group_id() 417 | nb = (2, gid, 'b{0:d}-{1}'.format(gid, bdry[0])) 418 | self.__groups.append(nb) 419 | 420 | blkn = bdry[1] - 1 421 | blk = p3df.coords[blkn] 422 | x, _, _ = blk 423 | 424 | imax = x.shape[0] - 1 425 | jmax = x.shape[1] - 1 426 | kmax = x.shape[2] - 1 427 | 428 | s1, e1, s2, e2 = bdry[3:7] 429 | if bdry[2] == 1: 430 | for j in range(s2 - 1, e2 - 1): 431 | for i in range(s1 - 1, e1 - 1): 432 | el_id = self.get_next_element_id() 433 | n1 = GmshFile._p3d_node_id(p3df, blkn, i, j, 0) 434 | n2 = GmshFile._p3d_node_id(p3df, blkn, i + 1, j, 0) 435 | n3 = GmshFile._p3d_node_id(p3df, blkn, i + 1, j + 1, 0) 436 | n4 = GmshFile._p3d_node_id(p3df, blkn, i, j + 1, 0) 437 | 438 | self.__elements.append([ 439 | el_id, 3, 2, gid, GmshFile.__DEFAULT_GEOMETRY_GROUP, 440 | n1, n2, n3, n4 441 | ]) 442 | 443 | elif bdry[2] == 2: 444 | for j in range(s2 - 1, e2 - 1): 445 | for i in range(s1 - 1, e1 - 1): 446 | el_id = self.get_next_element_id() 447 | n1 = GmshFile._p3d_node_id(p3df, blkn, i, j, kmax) 448 | n2 = GmshFile._p3d_node_id(p3df, blkn, i + 1, j, kmax) 449 | n3 = GmshFile._p3d_node_id(p3df, blkn, i + 1, j + 1, kmax) 450 | n4 = GmshFile._p3d_node_id(p3df, blkn, i, j + 1, kmax) 451 | 452 | self.__elements.append([ 453 | el_id, 3, 2, gid, GmshFile.__DEFAULT_GEOMETRY_GROUP, 454 | n1, n2, n3, n4 455 | ]) 456 | 457 | elif bdry[2] == 3: 458 | for k in range(s2 - 1, e2 - 1): 459 | for j in range(s1 - 1, e1 - 1): 460 | el_id = self.get_next_element_id() 461 | n1 = GmshFile._p3d_node_id(p3df, blkn, 0, j, k) 462 | n2 = GmshFile._p3d_node_id(p3df, blkn, 0, j + 1, k) 463 | n3 = GmshFile._p3d_node_id(p3df, blkn, 0, j + 1, k + 1) 464 | n4 = GmshFile._p3d_node_id(p3df, blkn, 0, j, k + 1) 465 | 466 | self.__elements.append([ 467 | el_id, 3, 2, gid, GmshFile.__DEFAULT_GEOMETRY_GROUP, 468 | n1, n2, n3, n4 469 | ]) 470 | 471 | elif bdry[2] == 4: 472 | for k in range(s2 - 1, e2 - 1): 473 | for j in range(s1 - 1, e1 - 1): 474 | el_id = self.get_next_element_id() 475 | n1 = GmshFile._p3d_node_id(p3df, blkn, imax, j, k) 476 | n2 = GmshFile._p3d_node_id(p3df, blkn, imax, j + 1, k) 477 | n3 = GmshFile._p3d_node_id(p3df, blkn, imax, j + 1, k + 1) 478 | n4 = GmshFile._p3d_node_id(p3df, blkn, imax, j, k + 1) 479 | 480 | self.__elements.append([ 481 | el_id, 3, 2, gid, GmshFile.__DEFAULT_GEOMETRY_GROUP, 482 | n1, n2, n3, n4 483 | ]) 484 | 485 | elif bdry[2] == 5: 486 | for i in range(s2 - 1, e2 - 1): 487 | for k in range(s1 - 1, e1 - 1): 488 | el_id = self.get_next_element_id() 489 | n1 = GmshFile._p3d_node_id(p3df, blkn, i, 0, k) 490 | n2 = GmshFile._p3d_node_id(p3df, blkn, i + 1, 0, k) 491 | n3 = GmshFile._p3d_node_id(p3df, blkn, i + 1, 0, k + 1) 492 | n4 = GmshFile._p3d_node_id(p3df, blkn, i, 0, k + 1) 493 | 494 | self.__elements.append([ 495 | el_id, 3, 2, gid, GmshFile.__DEFAULT_GEOMETRY_GROUP, 496 | n1, n2, n3, n4 497 | ]) 498 | 499 | elif bdry[2] == 6: 500 | for i in range(s2 - 1, e2 - 1): 501 | for k in range(s1 - 1, e1 - 1): 502 | el_id = self.get_next_element_id() 503 | n1 = GmshFile._p3d_node_id(p3df, blkn, i, jmax, k) 504 | n2 = GmshFile._p3d_node_id(p3df, blkn, i + 1, jmax, k) 505 | n3 = GmshFile._p3d_node_id(p3df, blkn, i + 1, jmax, k + 1) 506 | n4 = GmshFile._p3d_node_id(p3df, blkn, i, jmax, k + 1) 507 | 508 | self.__elements.append([ 509 | el_id, 3, 2, gid, GmshFile.__DEFAULT_GEOMETRY_GROUP, 510 | n1, n2, n3, n4 511 | ]) 512 | 513 | else: 514 | raise ValueError('Unknown block face identifier.') 515 | 516 | 517 | def main(): 518 | """Parse command line options, convert files.""" 519 | # CLI options: 520 | # --output-file / -o: write resulting mesh into 521 | # --map-file / -m: read boundary description from 522 | 523 | parser = argparse.ArgumentParser(description='''\ 524 | Convert P3Dfmt mesh into Gmsh mesh''', 525 | add_help=True) 526 | parser.add_argument('files', nargs='+', help='files to convert') 527 | parser.add_argument('-m', 528 | '--map-file', 529 | nargs=1, 530 | help='''\ 531 | Neutral Map File, if omitted script will look for .nmf 532 | file''') 533 | parser.add_argument('-o', 534 | '--output-file', 535 | nargs=1, 536 | help='''\ 537 | output file name, if omitted mesh will be written to .msh''') 538 | args = parser.parse_args() 539 | 540 | for fn in args.files: 541 | if not os.path.exists(fn): 542 | print('Can\'t open {0}. Skipping.'.format(fn)) 543 | continue 544 | (name, _) = os.path.splitext(fn) 545 | 546 | mapfile = None 547 | outputfile = None 548 | if args.map_file is None: 549 | mapfile = '{0}.nmf'.format(name) 550 | else: 551 | mapfile = args.map_file.pop() 552 | 553 | if args.output_file is None: 554 | outputfile = '{0}.msh'.format(name) 555 | else: 556 | outputfile = args.output_file.pop() 557 | 558 | p3d = P3DfmtFile() 559 | p3d.load(fn) 560 | nmf = NeutralMapFile(mapfile) 561 | 562 | gmsh = GmshFile() 563 | gmsh.consume(p3d, mapfile=nmf) 564 | gmsh.save(outputfile) 565 | 566 | 567 | if __name__ == '__main__': 568 | main() 569 | -------------------------------------------------------------------------------- /tests/n0012.nmf: -------------------------------------------------------------------------------- 1 | # ==================== Neutral Map File generated by Construct2D ==================== 2 | # ==================== ========================================= ==================== 3 | # Block# IDIM JDIM KDIM 4 | # ----------------------------------------------------------------------------------- 5 | 1 6 | 7 | 1 250 2 100 8 | 9 | # =================================================================================== 10 | # Type B1 F1 S1 E1 S2 E2 B2 F2 S1 E1 S2 E2 Swap 11 | # Compute forces (walls) 12 | # ----------------------------------------------------------------------------------- 13 | VISCOUS 1 1 1 250 1 2 TRUE 14 | FARFIELD 1 2 1 250 1 2 15 | ONE_TO_ONE 1 3 1 2 1 100 1 4 1 2 1 100 FALSE 16 | SYMMETRY-Y 1 5 1 100 1 250 17 | SYMMETRY-Y 1 6 1 100 1 250 18 | -------------------------------------------------------------------------------- /tests/n0012_settings.nml: -------------------------------------------------------------------------------- 1 | &SOPT 2 | nsrf = 250 3 | lesp = 4.0000000000000001E-003 4 | tesp = 2.5200000000000000E-004 5 | radi = 15.000000000000000 6 | nwke = 50 7 | fdst = 1.0000000000000000 8 | fwkl = 1.0000000000000000 9 | fwki = 10.000000000000000 10 | / 11 | &VOPT 12 | name = 'n0012' 13 | jmax = 100 14 | slvr = 'HYPR' 15 | topo = 'CGRD' 16 | ypls = 0.90000000000000002 17 | recd = 1000000.0000000000 18 | stp1 = 1000 19 | stp2 = 20 20 | nrmt = 1 21 | nrmb = 1 22 | alfa = 1.0000000000000000 23 | epsi = 15.000000000000000 24 | epse = 0.0000000000000000 25 | funi = 0.20000000000000001 26 | asmt = 20 27 | / 28 | &OOPT 29 | gdim = 3 30 | npln = 2 31 | dpln = 1.0000000000000000 32 | / 33 | -------------------------------------------------------------------------------- /tests/shear_4levelsdown.nmf: -------------------------------------------------------------------------------- 1 | # ===== Neutral Map File generated by the V2k software of NASA Langley's GEOLAB ===== \ 2 | # =================================================================================== \ 3 | # Block# IDIM JDIM KDIM \ 4 | # ----------------------------------------------------------------------------------- \ 5 | 3 \ 6 | \ 7 | 1 2 9 17 \ 8 | 2 2 9 17 \ 9 | 3 2 33 33 \ 10 | \ 11 | # =================================================================================== \ 12 | # Type B1 F1 S1 E1 S2 E2 B2 F2 S1 E1 S2 E2 Swap \ 13 | # ----------------------------------------------------------------------------------- \ 14 | 'symmetry_y_strong' 1 3 1 9 1 17 \ 15 | 'symmetry_y_strong' 2 3 1 9 1 17 \ 16 | 'symmetry_y_strong' 3 3 1 33 1 33 \ 17 | 'symmetry_y_strong' 1 4 1 9 1 17 \ 18 | 'symmetry_y_strong' 2 4 1 9 1 17 \ 19 | 'symmetry_y_strong' 3 4 1 33 1 33 \ 20 | 'subsonic_inflow_pt' 1 5 1 17 1 2 \ 21 | 'subsonic_inflow_pt' 2 5 1 17 1 2 \ 22 | 'one-to-one' 1 6 1 17 1 2 3 5 1 17 1 2 false \ 23 | 'one-to-one' 2 6 1 17 1 2 3 5 17 33 1 2 false \ 24 | 'back_pressure' 3 6 1 33 1 2 \ 25 | 'symmetry_z_strong' 1 1 1 2 1 9 \ 26 | 'viscous_solid' 2 1 1 2 1 9 \ 27 | 'symmetry_z_strong' 3 1 1 2 1 33 \ 28 | 'viscous_solid' 1 2 1 2 1 9 \ 29 | 'symmetry_z_strong' 2 2 1 2 1 9 \ 30 | 'symmetry_z_strong' 3 2 1 2 1 33 31 | --------------------------------------------------------------------------------