def test_ply(mesh, binary): def writer(*args, **kwargs): return meshio.ply.write(*args, binary=binary, **kwargs) for k, c in enumerate(mesh.cells): mesh.cells[k] = meshio.CellBlock(c.type, c.data.astype(np.int32)) helpers.write_read(writer, meshio.ply.read, mesh, 1.0e-12)
def test_obj(mesh): def writer(*args, **kwargs): return meshio.obj.write(*args, **kwargs) for k, c in enumerate(mesh.cells): mesh.cells[k] = meshio.CellBlock(c.type, c.data.astype(numpy.int32)) helpers.write_read(writer, meshio.obj.read, mesh, 1.0e-12)
def export_domain(msh, dim, directory, prefix): """ Export the domain XDMF file as well as the subdomains values. """ # Set cell type if dim == 2: cell_type = "triangle" elif dim == 3: cell_type = "tetra" # Generate the cell block for the domain cells data_array = [arr for (t, arr) in msh.cells if t == cell_type] if len(data_array) == 0: print("WARNING: No domain physical group found.") return else: data = np.concatenate(data_array) cells = [ meshio.CellBlock( type=cell_type, data=data, ) ] # Generate the domain cells data (for the subdomains) try: cell_data = { "subdomains": [ np.concatenate( [ msh.cell_data["gmsh:physical"][i] for i, cellBlock in enumerate(msh.cells) if cellBlock.type == cell_type ] ) ] } except KeyError: raise ValueError( """ No physical group found for the domain. Define the domain physical group. - if dim=2, the domain is a surface - if dim=3, the domain is a volume """ ) # Generate a meshio Mesh for the domain domain = meshio.Mesh( points=msh.points[:, :dim], cells=cells, cell_data=cell_data, ) # Export the XDMF mesh of the domain meshio.write( "{}/{}_{}".format(directory, prefix, "domain.xdmf"), domain, file_format="xdmf" )
def _export_1d( self, gs: Iterable[pp.Grid] ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Export the geometrical data (point coordinates) and connectivity information from the 1d PorePy grids to meshio. """ gs = np.atleast_1d(gs) # in 1d we have only one cell type cell_type = "line" # cell connectivity information num_cells = np.sum([g.num_cells for g in gs]) cell_to_nodes = {cell_type: np.empty((num_cells, 2))} # cell id map cell_id = {cell_type: np.empty(num_cells, dtype=np.int)} cell_pos = 0 # points num_pts = np.sum([g.num_nodes for g in gs]) meshio_pts = np.empty((num_pts, 3)) pts_pos = 0 # loop on all the 1d grids for g in gs: # save the points information sl = slice(pts_pos, pts_pos + g.num_nodes) meshio_pts[sl, :] = g.nodes.T # Cell-node relations g_cell_nodes = g.cell_nodes() g_nodes_cells, g_cells, _ = sps.find(g_cell_nodes) # Ensure ordering of the cells g_nodes_cells = g_nodes_cells[np.argsort(g_cells)] # loop on all the grid cells for c in np.arange(g.num_cells): loc = slice(g_cell_nodes.indptr[c], g_cell_nodes.indptr[c + 1]) # get the local nodes and save them cell_to_nodes[cell_type][cell_pos, :] = g_nodes_cells[loc] + pts_pos cell_id[cell_type][cell_pos] = cell_pos cell_pos += 1 pts_pos += g.num_nodes # construct the meshio data structure num_block = len(cell_to_nodes) meshio_cells = np.empty(num_block, dtype=np.object) meshio_cell_id = np.empty(num_block, dtype=np.object) for block, (cell_type, cell_block) in enumerate(cell_to_nodes.items()): meshio_cells[block] = meshio.CellBlock(cell_type, cell_block.astype(np.int)) meshio_cell_id[block] = np.array(cell_id[cell_type]) return meshio_pts, meshio_cells, meshio_cell_id
def extract_to_meshio(): # extract point coords idx, points, _ = gmsh.model.mesh.getNodes() points = points.reshape(-1, 3) idx -= 1 srt = numpy.argsort(idx) assert numpy.all(idx[srt] == numpy.arange(len(idx))) points = points[srt] # extract cells elem_types, elem_tags, node_tags = gmsh.model.mesh.getElements() cells = [] for elem_type, node_tags in zip(elem_types, node_tags): # `elementName', `dim', `order', `numNodes', `localNodeCoord', # `numPrimaryNodes' num_nodes_per_cell = gmsh.model.mesh.getElementProperties(elem_type)[3] meshio.gmsh.gmsh_to_meshio_type cells.append( meshio.CellBlock( meshio.gmsh.gmsh_to_meshio_type[elem_type], node_tags.reshape(-1, num_nodes_per_cell) - 1, ) ) cell_sets = {} for dim, tag in gmsh.model.getPhysicalGroups(): name = gmsh.model.getPhysicalName(dim, tag) cell_sets[name] = [[] for _ in range(len(cells))] for e in gmsh.model.getEntitiesForPhysicalGroup(dim, tag): # TODO node_tags? # elem_types, elem_tags, node_tags elem_types, elem_tags, _ = gmsh.model.mesh.getElements(dim, e) assert len(elem_types) == len(elem_tags) assert len(elem_types) == 1 elem_type = elem_types[0] elem_tags = elem_tags[0] meshio_cell_type = meshio.gmsh.gmsh_to_meshio_type[elem_type] # make sure that the cell type appears only once in the cell list # -- for now idx = [] for k, cell_block in enumerate(cells): if cell_block.type == meshio_cell_type: idx.append(k) assert len(idx) == 1 idx = idx[0] cell_sets[name][idx].append(elem_tags - 1) cell_sets[name] = [ (None if len(idcs) == 0 else numpy.concatenate(idcs)) for idcs in cell_sets[name] ] # make meshio mesh return meshio.Mesh(points, cells, cell_sets=cell_sets)
def RequestData(self, request, inInfoVec, outInfoVec): mesh = dsa.WrapDataObject(vtkUnstructuredGrid.GetData(inInfoVec[0])) # Read points points = np.asarray(mesh.GetPoints()) # Read cells # Adapted from test/legacy_reader.py cell_conn = mesh.GetCells() cell_offsets = mesh.GetCellLocations() cell_types = mesh.GetCellTypes() cells_dict = {} for vtk_cell_type in np.unique(cell_types): offsets = cell_offsets[cell_types == vtk_cell_type] ncells = len(offsets) npoints = cell_conn[offsets[0]] array = np.empty((ncells, npoints), dtype=int) for i in range(npoints): array[:, i] = cell_conn[offsets + i + 1] cells_dict[vtk_to_meshio_type[vtk_cell_type]] = array cells = [meshio.CellBlock(key, cells_dict[key]) for key in cells_dict] # Read point and field data # Adapted from test/legacy_reader.py def _read_data(data): out = {} for i in range(data.VTKObject.GetNumberOfArrays()): name = data.VTKObject.GetArrayName(i) array = np.asarray(data.GetArray(i)) out[name] = array return out point_data = _read_data(mesh.GetPointData()) field_data = _read_data(mesh.GetFieldData()) # Read cell data cell_data_flattened = _read_data(mesh.GetCellData()) cell_data = {} for name, array in cell_data_flattened.items(): cell_data[name] = [] for cell_type in cells_dict: vtk_cell_type = meshio_to_vtk_type[cell_type] mask_cell_type = cell_types == vtk_cell_type cell_data[name].append(array[mask_cell_type]) # Use meshio to write mesh meshio.write_points_cells( self._filename, points, cells, point_data=point_data, cell_data=cell_data, field_data=field_data, ) return 1
def test_obj(mesh, tmp_path): for k, c in enumerate(mesh.cells): mesh.cells[k] = meshio.CellBlock(c.type, c.data.astype(np.int32)) helpers.write_read( tmp_path, meshio.obj.write, meshio.obj.read, mesh, 1.0e-12, test_memory_file=True, )
def main(argv=None): parser = _get_parser() args = parser.parse_args(argv) if not (args.max_num_steps < math.inf or args.tolerance > 0.0): parser.error( "At least one of --max-num-steps or --tolerance required.") mesh = meshio.read(args.input_file) # Remove all points which do not belong to the highest-order simplex. Those would # lead to singular equations systems further down the line. mesh.cells = [ meshio.CellBlock("triangle", mesh.get_cells_type("triangle")) ] prune(mesh) if args.subdomain_field_name: field = mesh.cell_data["triangle"][args.subdomain_field_name] subdomain_idx = np.unique(field) cell_sets = [idx == field for idx in subdomain_idx] else: cell_sets = [ np.ones(mesh.get_cells_type("triangle").shape[0], dtype=bool) ] cells = mesh.get_cells_type("triangle") for cell_idx in cell_sets: X, cls = optimize_points_cells( mesh.points, cells[cell_idx], args.method, args.tolerance, args.max_num_steps, omega=args.omega, verbose=~args.quiet, step_filename_format=args.step_filename_format, ) cells[cell_idx] = cls q = meshplex.MeshTri(X, cls).q_radius_ratio meshio.write_points_cells( args.output_file, X, [("triangle", cells)], cell_data={"cell_quality": [q]}, )
def test_ply(mesh, binary, tmp_path): def writer(*args, **kwargs): return meshio.ply.write(*args, binary=binary, **kwargs) for k, c in enumerate(mesh.cells): mesh.cells[k] = meshio.CellBlock(c.type, c.data.astype(np.int32)) helpers.write_read( tmp_path, writer, meshio.ply.read, mesh, 1.0e-12, memory_file_is_binary=True, test_memory_file=True, )
def export_boundaries(msh, dim, directory, prefix): """ Export the boundaries XDMF file. """ # Set the cell type if dim == 2: cell_type = "line" elif dim == 3: cell_type = "triangle" # Generate the cell block for the boundaries cells data_array = [arr for (t, arr) in msh.cells if t == cell_type] if len(data_array) == 0: print("WARNING: No boundary physical group found.") return else: data = np.concatenate(data_array) boundaries_cells = [ meshio.CellBlock( type=cell_type, data=data, ) ] # Generate the boundaries cells data cell_data = { "boundaries": [ np.concatenate( [ msh.cell_data["gmsh:physical"][i] for i, cellBlock in enumerate(msh.cells) if cellBlock.type == cell_type ] ) ] } # Generate the meshio Mesh for the boundaries physical groups boundaries = meshio.Mesh( points=msh.points[:, :dim], cells=boundaries_cells, cell_data=cell_data, ) # Export the XDMF mesh of the lines boundaries meshio.write( "{}/{}_{}".format(directory, prefix, "boundaries.xdmf"), boundaries, file_format="xdmf" )
def to_file(self, file_name: str, data: Dict[str, np.ndarray] = None, **kwargs) -> None: """ Export the fracture network to file. The file format is given as an kwarg, by default vtu will be used. The writing is outsourced to meshio, thus the file format should be supported by that package. The fractures are treated as lines, with no special treatment of intersections. Fracture numbers are always exported (1-offset). In addition, it is possible to export additional data, as specified by the keyword-argument data. Parameters: file_name (str): Name of the target file. data (dictionary, optional): Data associated with the fractures. The values in the dictionary should be numpy arrays. 1d and 3d data is supported. Fracture numbers are always exported. Optional arguments in kwargs: binary (boolean): Use binary export format. Default to True. fracture_offset (int): Use to define the offset for a fracture id. Default to 1. folder_name (string): Path to save the file. Default to "./". extension (string): File extension. Default to ".vtu". """ if data is None: data = {} binary: bool = kwargs.pop("binary", True) fracture_offset: int = kwargs.pop("fracture_offset", 1) extension: str = kwargs.pop("extension", ".vtu") folder_name: str = kwargs.pop("folder_name", "") if kwargs: msg = "Got unexpected keyword argument '{}'" raise TypeError(msg.format(kwargs.popitem()[0])) if not file_name.endswith(extension): file_name += extension # in 1d we have only one cell type cell_type = "line" # cell connectivity information meshio_cells = np.empty(1, dtype=np.object) meshio_cells[0] = meshio.CellBlock(cell_type, self.edges.T) # prepare the points meshio_pts = self.pts.T # make points 3d if meshio_pts.shape[1] == 2: meshio_pts = np.hstack( (meshio_pts, np.zeros((meshio_pts.shape[0], 1)))) # Cell-data to be exported is at least the fracture numbers meshio_cell_data = {} meshio_cell_data["fracture_number"] = [ fracture_offset + np.arange(self.edges.shape[1]) ] # process the for key, val in data.items(): if val.ndim == 1: meshio_cell_data[key] = [val] elif val.ndim == 2: meshio_cell_data[key] = [val.T] meshio_grid_to_export = meshio.Mesh(meshio_pts, meshio_cells, cell_data=meshio_cell_data) meshio.write(folder_name + file_name, meshio_grid_to_export, binary=binary)
def generate_mesh( # noqa: C901 geo_object, verbose=True, dim=3, prune_vertices=True, prune_z_0=False, remove_lower_dim_cells=False, gmsh_path=None, extra_gmsh_arguments=None, # for debugging purposes: geo_filename=None, msh_filename=None, mesh_file_type="msh", ): """Return a meshio.Mesh, storing the mesh points, cells, and data, generated by Gmsh from the `geo_object`, written to a temporary file, and reread by `meshio`. Gmsh's native "msh" format is ill-suited to fast I/O. This can greatly reduce the performance of pygmsh. As alternatives, try `mesh_file_type=`: - "vtk"`, though Gmsh doesn't write the physical tags to VTK <https://gitlab.onelab.info/gmsh/gmsh/issues/389> or - `"mesh"`, though this only supports a few basic elements - "line", "triangle", "quad", "tetra", "hexahedron" - and doesn't preserve the `$PhysicalNames`, just the `int` tags. """ if extra_gmsh_arguments is None: extra_gmsh_arguments = [] # For format "mesh", ask Gmsh to save the physical tags # http://gmsh.info/doc/texinfo/gmsh.html#index-Mesh_002eSaveElementTagType if mesh_file_type == "mesh": extra_gmsh_arguments += ["-string", "Mesh.SaveElementTagType=2;"] preserve_geo = geo_filename is not None if geo_filename is None: with tempfile.NamedTemporaryFile(suffix=".geo") as f: geo_filename = f.name with open(geo_filename, "w") as f: f.write(geo_object.get_code()) # As of Gmsh 4.1.3, the mesh format options are # ``` # auto, msh1, msh2, msh3, msh4, msh, unv, vtk, wrl, mail, stl, p3d, mesh, bdf, cgns, # med, diff, ir3, inp, ply2, celum, su2, x3d, dat, neu, m, key # ``` # Pick the correct filename suffix. filename_suffix = "msh" if mesh_file_type[:3] == "msh" else mesh_file_type preserve_msh = msh_filename is not None if msh_filename is None: with tempfile.NamedTemporaryFile(suffix="." + filename_suffix) as handle: msh_filename = handle.name gmsh_executable = gmsh_path if gmsh_path is not None else _get_gmsh_exe() args = [ f"-{dim}", geo_filename, "-format", mesh_file_type, "-bin", "-o", msh_filename, ] + extra_gmsh_arguments # https://stackoverflow.com/a/803421/353337 try: p = subprocess.Popen([gmsh_executable] + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except FileNotFoundError: print("Is gmsh installed?") raise if verbose: while True: line = p.stdout.readline() if not line: break print(line.decode("utf-8"), end="") p.communicate() assert p.returncode == 0, "Gmsh exited with error (return code {}).".format( p.returncode) mesh = meshio.read(msh_filename) if remove_lower_dim_cells: # Only keep the cells of highest topological dimension; discard faces and such. cells_2d = {"triangle", "quad"} cells_3d = { "tetra", "hexahedron", "wedge", "pyramid", "penta_prism", "hexa_prism", } if any(c.type in cells_3d for c in mesh.cells): keep_types = cells_3d elif any(c.type in cells_2d for c in mesh.cells): keep_types = cells_2d else: keep_types = set(cell_type for cell_type, _ in mesh.cells) for name, val in mesh.cell_data.items(): mesh.cell_data[name] = [ d for d, c in zip(val, mesh.cells) if c[0] in keep_types ] mesh.cells = [c for c in mesh.cells if c[0] in keep_types] if prune_vertices: # Make sure to include only those vertices which belong to a cell. ncells = numpy.concatenate( [numpy.concatenate(c) for _, c in mesh.cells]) uvertices, uidx = numpy.unique(ncells, return_inverse=True) k = 0 cells = [] for key, cellblock in mesh.cells: n = numpy.prod(cellblock.shape) cells.append( meshio.CellBlock(key, uidx[k:k + n].reshape(cellblock.shape))) k += n mesh.cells = cells mesh.points = mesh.points[uvertices] for key in mesh.point_data: mesh.point_data[key] = mesh.point_data[key][uvertices] # clean up if preserve_msh: print(f"\nmsh file: {msh_filename}") else: Path(msh_filename).unlink() if preserve_geo: print(f"\ngeo file: {geo_filename}") else: Path(geo_filename).unlink() if (prune_z_0 and mesh.points.shape[1] == 3 and numpy.all(numpy.abs(mesh.points[:, 2]) < 1.0e-13)): mesh.points = mesh.points[:, :2] return mesh
def main(argv=None): parser = _get_parser() args = parser.parse_args(argv) if not (args.max_num_steps < math.inf or args.tolerance > 0.0): parser.error( "At least one of --max-num-steps or --tolerance required.") mesh = meshio.read(args.input_file) # Remove all nodes which do not belong to the highest-order simplex. Those would # lead to singular equations systems further down the line. mesh.cells = [ meshio.CellBlock("triangle", mesh.get_cells_type("triangle")) ] prune(mesh) if args.subdomain_field_name: field = mesh.cell_data["triangle"][args.subdomain_field_name] subdomain_idx = numpy.unique(field) cell_sets = [idx == field for idx in subdomain_idx] else: cell_sets = [ numpy.ones(mesh.get_cells_type("triangle").shape[0], dtype=bool) ] method = { "laplace": laplace.fixed_point, # "cpt-dp": cpt.linear_solve_density_preserving, "cpt-uniform-fp": cpt.fixed_point_uniform, "cpt-uniform-qn": cpt.quasi_newton_uniform, # "lloyd": cvt.quasi_newton_uniform_lloyd, "cvt-uniform-fp": cvt.quasi_newton_uniform_lloyd, "cvt-uniform-qnb": cvt.quasi_newton_uniform_blocks, "cvt-uniform-qnf": cvt.quasi_newton_uniform_full, # "odt-dp-fp": odt.fixed_point_density_preserving, "odt-uniform-fp": odt.fixed_point_uniform, "odt-uniform-bfgs": odt.nonlinear_optimization_uniform, }[args.method] cells = mesh.get_cells_type("triangle") for cell_idx in cell_sets: if args.method in ["odt-uniform-bfgs"]: # no relaxation parameter omega X, cls = method( mesh.points, cells[cell_idx], args.tolerance, args.max_num_steps, verbose=~args.quiet, step_filename_format=args.step_filename_format, ) else: X, cls = method( mesh.points, cells[cell_idx], args.tolerance, args.max_num_steps, omega=args.omega, verbose=~args.quiet, step_filename_format=args.step_filename_format, ) cells[cell_idx] = cls q = meshplex.MeshTri(X, cls).cell_quality meshio.write_points_cells( args.output_file, X, [("triangle", cells)], cell_data={"cell_quality": [q]}, )
def generate_mesh( # noqa: C901 self, dim=3, order=None, # http://gmsh.info/doc/texinfo/gmsh.html#index-Mesh_002eAlgorithm algorithm=None, ): """Return a meshio.Mesh, storing the mesh points, cells, and data, generated by Gmsh from the `self`. """ self.synchronize() for item in self._AFTER_SYNC_QUEUE: item.exec() for item, host in self._EMBED_QUEUE: gmsh.model.mesh.embed(item.dim, [item._ID], host.dim, host._ID) # set compound entities after sync for c in self._COMPOUND_ENTITIES: gmsh.model.mesh.setCompound(*c) for s in self._RECOMBINE_ENTITIES: gmsh.model.mesh.setRecombine(*s) for t in self._TRANSFINITE_CURVE_QUEUE: gmsh.model.mesh.setTransfiniteCurve(*t) for t in self._TRANSFINITE_SURFACE_QUEUE: gmsh.model.mesh.setTransfiniteSurface(*t) for e in self._TRANSFINITE_VOLUME_QUEUE: gmsh.model.mesh.setTransfiniteVolume(*e) for item, size in self._SIZE_QUEUE: gmsh.model.mesh.setSize( gmsh.model.getBoundary(item.dim_tags, False, False, True), size ) for entities, label in self._PHYSICAL_QUEUE: tag = gmsh.model.addPhysicalGroup(dim, [e._ID for e in entities]) if label is not None: gmsh.model.setPhysicalName(dim, tag, label) if order is not None: gmsh.model.mesh.setOrder(order) # set algorithm # http://gmsh.info/doc/texinfo/gmsh.html#index-Mesh_002eAlgorithm if algorithm: gmsh.option.setNumber("Mesh.Algorithm", algorithm) gmsh.model.mesh.generate(dim) # extract point coords idx, points, _ = gmsh.model.mesh.getNodes() points = points.reshape(-1, 3) idx -= 1 srt = numpy.argsort(idx) assert numpy.all(idx[srt] == numpy.arange(len(idx))) points = points[srt] # extract cells elem_types, elem_tags, node_tags = gmsh.model.mesh.getElements() cells = [] for elem_type, node_tags in zip(elem_types, node_tags): # `elementName', `dim', `order', `numNodes', `localNodeCoord', # `numPrimaryNodes' num_nodes_per_cell = gmsh.model.mesh.getElementProperties(elem_type)[3] meshio.gmsh.gmsh_to_meshio_type cells.append( meshio.CellBlock( meshio.gmsh.gmsh_to_meshio_type[elem_type], node_tags.reshape(-1, num_nodes_per_cell) - 1, ) ) cell_sets = {} for dim, tag in gmsh.model.getPhysicalGroups(): name = gmsh.model.getPhysicalName(dim, tag) cell_sets[name] = [[] for _ in range(len(cells))] for e in gmsh.model.getEntitiesForPhysicalGroup(dim, tag): # TODO node_tags? # elem_types, elem_tags, node_tags elem_types, elem_tags, _ = gmsh.model.mesh.getElements(dim, e) assert len(elem_types) == len(elem_tags) assert len(elem_types) == 1 elem_type = elem_types[0] elem_tags = elem_tags[0] meshio_cell_type = meshio.gmsh.gmsh_to_meshio_type[elem_type] # make sure that the cell type appears only once in the cell list # -- for now idx = [] for k, cell_block in enumerate(cells): if cell_block.type == meshio_cell_type: idx.append(k) assert len(idx) == 1 idx = idx[0] cell_sets[name][idx].append(elem_tags - 1) cell_sets[name] = [ (None if len(idcs) == 0 else numpy.concatenate(idcs)) for idcs in cell_sets[name] ] # make meshio mesh return meshio.Mesh(points, cells, cell_sets=cell_sets)
def _export_2d( self, gs: Iterable[pp.Grid] ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Export the geometrical data (point coordinates) and connectivity information from the 2d PorePy grids to meshio. """ # use standard name for simple object type polygon_map = {"polygon3": "triangle", "polygon4": "quad"} # cell->nodes connectivity information cell_to_nodes: Dict[str, np.ndarray] = {} # cell id map cell_id: Dict[str, List[int]] = {} # points num_pts = np.sum([g.num_nodes for g in gs]) meshio_pts = np.empty((num_pts, 3)) # type: ignore pts_pos = 0 cell_pos = 0 # loop on all the 2d grids for g in gs: # save the points information sl = slice(pts_pos, pts_pos + g.num_nodes) meshio_pts[sl, :] = g.nodes.T # Cell-face and face-node relations g_faces_cells, g_cells, _ = sps.find(g.cell_faces) # Ensure ordering of the cells g_faces_cells = g_faces_cells[np.argsort(g_cells)] g_nodes_faces, _, _ = sps.find(g.face_nodes) # loop on all the grid cells for c in np.arange(g.num_cells): loc = slice(g.cell_faces.indptr[c], g.cell_faces.indptr[c + 1]) # get the nodes for the current cell nodes = np.array( [ g_nodes_faces[ g.face_nodes.indptr[f] : g.face_nodes.indptr[f + 1] ] for f in g_faces_cells[loc] ] ).T # sort the nodes nodes_loc, *_ = pp.utils.sort_points.sort_point_pairs(nodes) # define the type of cell we are currently saving cell_type = "polygon" + str(nodes_loc.shape[1]) cell_type = polygon_map.get(cell_type, cell_type) # if the cell type is not present, then add it if cell_type not in cell_to_nodes: cell_to_nodes[cell_type] = np.atleast_2d(nodes_loc[0] + pts_pos) cell_id[cell_type] = [cell_pos] else: cell_to_nodes[cell_type] = np.vstack( (cell_to_nodes[cell_type], nodes_loc[0] + pts_pos) ) cell_id[cell_type] += [cell_pos] cell_pos += 1 pts_pos += g.num_nodes # construct the meshio data structure num_block = len(cell_to_nodes) meshio_cells = np.empty(num_block, dtype=object) meshio_cell_id = np.empty(num_block, dtype=object) for block, (cell_type, cell_block) in enumerate(cell_to_nodes.items()): meshio_cells[block] = meshio.CellBlock(cell_type, cell_block.astype(int)) meshio_cell_id[block] = np.array(cell_id[cell_type]) return meshio_pts, meshio_cells, meshio_cell_id
def _export_3d( self, gs: Iterable[pp.Grid] ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Export the geometrical data (point coordinates) and connectivity information from the 3d PorePy grids to meshio. """ # use standard name for simple object type # NOTE: this part is not developed # polygon_map = {"polyhedron4": "tetra", "polyhedron8": "hexahedron"} # cell-faces and cell nodes connectivity information cell_to_faces: Dict[str, List[List[int]]] = {} cell_to_nodes: Dict[str, np.ndarray] = {} # cell id map cell_id: Dict[str, List[int]] = {} # points num_pts = np.sum([g.num_nodes for g in gs]) meshio_pts = np.empty((num_pts, 3)) # type: ignore pts_pos = 0 cell_pos = 0 # loop on all the 3d grids for g in gs: # save the points information sl = slice(pts_pos, pts_pos + g.num_nodes) meshio_pts[sl, :] = g.nodes.T # Cell-face and face-node relations g_faces_cells, g_cells, _ = sps.find(g.cell_faces) # Ensure ordering of the cells g_faces_cells = g_faces_cells[np.argsort(g_cells)] g_nodes_faces, _, _ = sps.find(g.face_nodes) cptr = g.cell_faces.indptr fptr = g.face_nodes.indptr face_per_cell = np.diff(cptr) nodes_per_face = np.diff(fptr) # Total number of nodes to be written in the face-node relation num_cell_nodes = np.array([nodes_per_face[i] for i in g.cell_faces.indices]) n = g.nodes fc = g.face_centers normal_vec = g.face_normals / g.face_areas # Use numba if available, unless the problem is very small, in which # case the pure python version probably is faster than combined compile # and runtime for numba # The number 1000 here is somewhat random. if self.has_numba and g.num_cells > 1000: logger.info("Construct 3d grid information using numba") cell_nodes = self._point_ind_numba( cptr, fptr, g_faces_cells, g_nodes_faces, n, fc, normal_vec, num_cell_nodes, ) else: logger.info("Construct 3d grid information using pure python") cell_nodes = self._point_ind( cptr, fptr, g_faces_cells, g_nodes_faces, n, fc, normal_vec, num_cell_nodes, ) # implementation note: I did not even try feeding this to numba, my # guess is that it will not like the vtk specific stuff. nc = 0 f_counter = 0 # loop on all the grid cells for c in np.arange(g.num_cells): faces_loc: List[int] = [] # loop on all the cell faces for _ in np.arange(face_per_cell[c]): fi = g.cell_faces.indices[f_counter] faces_loc += [cell_nodes[nc : (nc + nodes_per_face[fi])]] nc += nodes_per_face[fi] f_counter += 1 # collect all the nodes for the cell nodes_loc = np.unique(faces_loc).astype(int) # define the type of cell we are currently saving cell_type = "polyhedron" + str(nodes_loc.size) # cell_type = polygon_map.get(cell_type, cell_type) # if the cell type is not present, then add it if cell_type not in cell_to_nodes: cell_to_nodes[cell_type] = np.atleast_2d(nodes_loc + pts_pos) cell_to_faces[cell_type] = [faces_loc] cell_id[cell_type] = [cell_pos] else: cell_to_nodes[cell_type] = np.vstack( (cell_to_nodes[cell_type], nodes_loc + pts_pos) ) cell_to_faces[cell_type] += [faces_loc] cell_id[cell_type] += [cell_pos] cell_pos += 1 pts_pos += g.num_nodes # NOTE: only cell->faces relation will be kept, so far we export only polyhedron # otherwise in the next lines we should consider also the cell->nodes map # construct the meshio data structure num_block = len(cell_to_nodes) meshio_cells = np.empty(num_block, dtype=object) meshio_cell_id = np.empty(num_block, dtype=object) for block, (cell_type, cell_block) in enumerate(cell_to_faces.items()): meshio_cells[block] = meshio.CellBlock(cell_type, cell_block) meshio_cell_id[block] = np.array(cell_id[cell_type]) return meshio_pts, meshio_cells, meshio_cell_id
def generate_mesh( # noqa: C901 self, dim=3, order=None, prune_vertices=True, prune_z_0=False, remove_lower_dim_cells=False, # http://gmsh.info/doc/texinfo/gmsh.html#index-Mesh_002eAlgorithm algorithm=None, ): """Return a meshio.Mesh, storing the mesh points, cells, and data, generated by Gmsh from the `self`. """ self.synchronize() for item in self._AFTER_SYNC_QUEUE: item.exec() for item, host in self._EMBED_QUEUE: gmsh.model.mesh.embed(item.dim, [item._ID], host.dim, host._ID) # set compound entities after sync for c in self._COMPOUND_ENTITIES: gmsh.model.mesh.setCompound(*c) for s in self._RECOMBINE_ENTITIES: gmsh.model.mesh.setRecombine(*s) for t in self._TRANSFINITE_CURVE_QUEUE: gmsh.model.mesh.setTransfiniteCurve(*t) for t in self._TRANSFINITE_SURFACE_QUEUE: gmsh.model.mesh.setTransfiniteSurface(*t) for e in self._TRANSFINITE_VOLUME_QUEUE: gmsh.model.mesh.setTransfiniteVolume(*e) for item, size in self._SIZE_QUEUE: gmsh.model.mesh.setSize( gmsh.model.getBoundary(item.dim_tags, False, False, True), size) if order is not None: gmsh.model.mesh.setOrder(order) # set algorithm # http://gmsh.info/doc/texinfo/gmsh.html#index-Mesh_002eAlgorithm if algorithm: gmsh.option.setNumber("Mesh.Algorithm", algorithm) gmsh.model.mesh.generate(dim) # extract point coords idx, points, _ = gmsh.model.mesh.getNodes() points = points.reshape(-1, 3) idx -= 1 srt = numpy.argsort(idx) assert numpy.all(idx[srt] == numpy.arange(len(idx))) points = points[srt] if prune_z_0 and numpy.all(numpy.abs(points[:, 2]) < 1.0e-13): points = points[:, :2] # extract cells elem_types, elem_tags, node_tags = gmsh.model.mesh.getElements() cells = [] for elem_type, node_tags in zip(elem_types, node_tags): # `elementName', `dim', `order', `numNodes', `localNodeCoord', # `numPrimaryNodes' num_nodes_per_cell = gmsh.model.mesh.getElementProperties( elem_type)[3] meshio.gmsh.gmsh_to_meshio_type cells.append( meshio.CellBlock( meshio.gmsh.gmsh_to_meshio_type[elem_type], node_tags.reshape(-1, num_nodes_per_cell) - 1, )) # print("a", gmsh.model.getEntities()) # grps = gmsh.model.getPhysicalGroups() # print("a", grps) # for dim, tag in grps: # print("a", gmsh.model.getPhysicalName(dim, tag)) # ent = gmsh.model.getEntitiesForPhysicalGroup(dim, tag) # print("a", ent) # assert len(ent) == 1 # print("a", gmsh.model.mesh.getElements(dim, ent[0])) # make meshio mesh mesh = meshio.Mesh(points, cells) if remove_lower_dim_cells: # Only keep the cells of highest topological dimension; discard faces and # such. cells_2d = {"triangle", "quad"} cells_3d = { "tetra", "hexahedron", "wedge", "pyramid", "penta_prism", "hexa_prism", } if any(c.type in cells_3d for c in mesh.cells): keep_types = cells_3d elif any(c.type in cells_2d for c in mesh.cells): keep_types = cells_2d else: keep_types = set(cell_type for cell_type, _ in mesh.cells) for name, val in mesh.cell_data.items(): mesh.cell_data[name] = [ d for d, c in zip(val, mesh.cells) if c[0] in keep_types ] mesh.cells = [c for c in mesh.cells if c[0] in keep_types] if prune_vertices: # Make sure to include only those vertices which belong to a cell. ncells = numpy.concatenate( [numpy.concatenate(c) for _, c in mesh.cells]) uvertices, uidx = numpy.unique(ncells, return_inverse=True) k = 0 cells = [] for key, cellblock in mesh.cells: n = numpy.prod(cellblock.shape) cells.append( meshio.CellBlock(key, uidx[k:k + n].reshape(cellblock.shape))) k += n mesh.cells = cells mesh.points = mesh.points[uvertices] for key in mesh.point_data: mesh.point_data[key] = mesh.point_data[key][uvertices] return mesh