diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c53c846..04a24685a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,24 @@ + # Changelog This document only describes _breaking_ changes in meshio. If you are interested in bug fixes, enhancements etc., best follow [the meshio project on GitHub](https://github.com/nschloe/meshio). +## v5.1.0 (Dec 11, 2021) + +- CellBlocks are no longer tuples, but classes. You can no longer iterate over them like + ```python + for cell_type, cell_data in cells: + pass + ``` + Instead, use + ```python + for cell_block in cells: + cell_block.type + cell_block.data + ``` + ## v5.0.0 (Aug 06, 2021) - meshio now only provides one command-line tool, `meshio`, with subcommands like @@ -15,23 +30,26 @@ GitHub](https://github.com/nschloe/meshio). number of nodes per polygon). One can simply retrieve the number of points via `cellblock.data.shape[1]`. - ## v4.0.0 (Feb 18, 2020) - `mesh.cells` used to be a dictionary of the form + ```python { "triangle": [[0, 1, 2], [0, 2, 3]], "quad": [[0, 7, 1, 10], ...] } ``` + From 4.0.0 on, `mesh.cells` is a list of tuples, + ```python [ ("triangle", [[0, 1, 2], [0, 2, 3]]), ("quad", [[0, 7, 1, 10], ...]) ] ``` + This has the advantage that multiple blocks of the same cell type can be accounted for. Also, cell ordering can be preserved. @@ -39,19 +57,23 @@ GitHub](https://github.com/nschloe/meshio). `"triangle"` type, or use `mesh.cells_dict` to build the old dictionary structure. - `mesh.cell_data` used to be a dictionary of the form + ```python { "triangle": {"a": [0.5, 1.3], "b": [2.17, 41.3]}, "quad": {"a": [1.1, -0.3, ...], "b": [3.14, 1.61, ...]}, } ``` + From 4.0.0 on, `mesh.cell_data` is a dictionary of lists, + ```python { "a": [[0.5, 1.3], [1.1, -0.3, ...]], "b": [[2.17, 41.3], [3.14, 1.61, ...]], } ``` + Each data list, e.g., `mesh.cell_data["a"]`, can be `zip`ped with `mesh.cells`. An old-style `cell_data` dictionary can be retrieved via `mesh.cell_data_dict`. diff --git a/README.md b/README.md index 825f3c958..f70dd02bd 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1173115.svg?style=flat-square)](https://doi.org/10.5281/zenodo.1173115) [![GitHub stars](https://img.shields.io/github/stars/nschloe/meshio.svg?style=flat-square&logo=github&label=Stars&logoColor=white)](https://github.com/nschloe/meshio) [![Downloads](https://pepy.tech/badge/meshio/month?style=flat-square)](https://pepy.tech/project/meshio) + [![Discord](https://img.shields.io/static/v1?logo=discord&label=chat&message=on%20discord&color=7289da&style=flat-square)](https://discord.gg/Z6DMsJh4Hr) diff --git a/setup.cfg b/setup.cfg index 87038350b..66af644c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = meshio -version = 5.0.6 +version = 5.1.0 author = Nico Schlömer et al. author_email = nico.schloemer@gmail.com description = I/O for many mesh formats diff --git a/src/meshio/_common.py b/src/meshio/_common.py index 3c219bb44..a477bc914 100644 --- a/src/meshio/_common.py +++ b/src/meshio/_common.py @@ -151,7 +151,7 @@ def cell_data_from_raw(cells, cell_data_raw): - cs = np.cumsum([len(c[1]) for c in cells])[:-1] + cs = np.cumsum([len(c) for c in cells])[:-1] return {name: np.split(d, cs) for name, d in cell_data_raw.items()} diff --git a/src/meshio/_helpers.py b/src/meshio/_helpers.py index 285bd87f9..8f23d4158 100644 --- a/src/meshio/_helpers.py +++ b/src/meshio/_helpers.py @@ -3,6 +3,7 @@ import pathlib import numpy as np +from numpy.typing import ArrayLike from ._common import num_nodes_per_cell from ._exceptions import ReadError, WriteError @@ -72,20 +73,17 @@ def read(filename, file_format: str | None = None): def write_points_cells( filename, - points, - cells, - point_data=None, - cell_data=None, + points: ArrayLike, + cells: dict[str, ArrayLike] | list[tuple[str, ArrayLike] | CellBlock], + point_data: dict[str, ArrayLike] | None = None, + cell_data: dict[str, list[ArrayLike]] | None = None, field_data=None, - point_sets=None, - cell_sets=None, - file_format=None, + point_sets: dict[str, ArrayLike] | None = None, + cell_sets: dict[str, list[ArrayLike]] | None = None, + file_format: str | None = None, **kwargs, ): points = np.asarray(points) - if isinstance(cells, dict): - cells = [CellBlock(name, vals) for name, vals in cells.items()] - cells = [(key, np.asarray(value)) for key, value in cells] mesh = Mesh( points, cells, @@ -127,7 +125,9 @@ def write(filename, mesh: Mesh, file_format: str | None = None, **kwargs): raise WriteError(f"Unknown format '{file_format}'. Pick one of {formats}") # check cells for sanity - for key, value in mesh.cells: + for cell_block in mesh.cells: + key = cell_block.type + value = cell_block.data if key in num_nodes_per_cell: if value.shape[1] != num_nodes_per_cell[key]: raise WriteError( diff --git a/src/meshio/_mesh.py b/src/meshio/_mesh.py index a6356c3d1..7f331d397 100644 --- a/src/meshio/_mesh.py +++ b/src/meshio/_mesh.py @@ -1,6 +1,5 @@ from __future__ import annotations -import collections import copy import warnings @@ -10,9 +9,27 @@ from ._common import num_nodes_per_cell -class CellBlock(collections.namedtuple("CellBlock", ["type", "data"])): +class CellBlock: + def __init__( + self, + type: str, + data: list | np.ndarray, + tags: list[str] | None = None, + ): + self.type = type + self.data = data + if not type.startswith("polyhedron"): + self.data = np.asarray(self.data) + self.tags = [] if tags is None else tags + def __repr__(self): - return f"" + items = [ + "meshio CellBlock", + f"type: {self.type}", + f"num cells: {len(self.data)}", + f"tags: {self.tags}", + ] + return "<" + ", ".join(items) + ">" def __len__(self): return len(self.data) @@ -112,11 +129,11 @@ def __repr__(self): ] if len(self.cells) > 0: lines.append(" Number of cells:") - for cell_type, elems in self.cells: - string = cell_type - if cell_type in special_cells: - string += f"({elems.shape[1]})" - lines.append(f" {string}: {len(elems)}") + for cell_block in self.cells: + string = cell_block.type + if cell_block.type in special_cells: + string += f"({cell_block.data.shape[1]})" + lines.append(f" {string}: {len(cell_block)}") else: lines.append(" No cells.") @@ -164,10 +181,10 @@ def get_cell_data(self, name: str, cell_type: str): @property def cells_dict(self): cells_dict = {} - for cell_type, data in self.cells: - if cell_type not in cells_dict: - cells_dict[cell_type] = [] - cells_dict[cell_type].append(data) + for cell_block in self.cells: + if cell_block.type not in cells_dict: + cells_dict[cell_block.type] = [] + cells_dict[cell_block.type].append(cell_block.data) # concatenate for key, value in cells_dict.items(): cells_dict[key] = np.concatenate(value) @@ -178,10 +195,10 @@ def cell_data_dict(self): cell_data_dict = {} for key, value_list in self.cell_data.items(): cell_data_dict[key] = {} - for value, (cell_type, _) in zip(value_list, self.cells): - if cell_type not in cell_data_dict[key]: - cell_data_dict[key][cell_type] = [] - cell_data_dict[key][cell_type].append(value) + for value, cell_block in zip(value_list, self.cells): + if cell_block.type not in cell_data_dict[key]: + cell_data_dict[key][cell_block.type] = [] + cell_data_dict[key][cell_block.type].append(value) for cell_type, val in cell_data_dict[key].items(): cell_data_dict[key][cell_type] = np.concatenate(val) diff --git a/src/meshio/abaqus/_abaqus.py b/src/meshio/abaqus/_abaqus.py index 081e7da0d..b0594297b 100644 --- a/src/meshio/abaqus/_abaqus.py +++ b/src/meshio/abaqus/_abaqus.py @@ -398,7 +398,9 @@ def _read_set(f, params_map): return set_ids, set_names, line -def write(filename, mesh, float_fmt=".16e", translate_cell_names=True): +def write( + filename, mesh: Mesh, float_fmt: str = ".16e", translate_cell_names: bool = True +) -> None: with open_file(filename, "wt") as f: f.write("*HEADING\n") f.write("Abaqus DataFile Version 6.14\n") @@ -408,7 +410,9 @@ def write(filename, mesh, float_fmt=".16e", translate_cell_names=True): for k, x in enumerate(mesh.points): f.write(fmt.format(k + 1, *x)) eid = 0 - for cell_type, node_idcs in mesh.cells: + for cell_block in mesh.cells: + cell_type = cell_block.type + node_idcs = cell_block.data name = ( meshio_to_abaqus_type[cell_type] if translate_cell_names else cell_type ) diff --git a/src/meshio/ansys/_ansys.py b/src/meshio/ansys/_ansys.py index b6f051999..6ead2136d 100644 --- a/src/meshio/ansys/_ansys.py +++ b/src/meshio/ansys/_ansys.py @@ -437,7 +437,9 @@ def write(filename, mesh, binary=True): np.dtype("int32"): "2012", np.dtype("int64"): "3012", } - for cell_type, values in mesh.cells: + for cell_block in mesh.cells: + cell_type = cell_block.type + values = cell_block.data key = binary_dtypes[values.dtype] if binary else "12" last_index = first_index + len(values) - 1 try: diff --git a/src/meshio/avsucd/_avsucd.py b/src/meshio/avsucd/_avsucd.py index f6da79060..59a0ab6fc 100644 --- a/src/meshio/avsucd/_avsucd.py +++ b/src/meshio/avsucd/_avsucd.py @@ -98,20 +98,20 @@ def _read_cells(f, num_cells, point_ids): cell_type = avsucd_to_meshio_type[line[2]] corner = [point_ids[int(pid)] for pid in line[3:]] - if len(cells) > 0 and cells[-1].type == cell_type: - cells[-1].data.append(corner) + if len(cells) > 0 and cells[-1][0] == cell_type: + cells[-1][1].append(corner) cell_data["avsucd:material"][-1].append(cell_mat) else: - cells.append(CellBlock(cell_type, [corner])) + cells.append((cell_type, [corner])) cell_data["avsucd:material"].append([cell_mat]) cell_ids[cell_id] = count count += 1 # Convert to numpy arrays - for k, c in enumerate(cells): + for k, (cell_type, cdata) in enumerate(cells): cells[k] = CellBlock( - c.type, np.array(c.data)[:, avsucd_to_meshio_order[c.type]] + cell_type, np.array(cdata)[:, avsucd_to_meshio_order[cell_type]] ) cell_data["avsucd:material"][k] = np.array(cell_data["avsucd:material"][k]) return cell_ids, cells, cell_data @@ -219,7 +219,9 @@ def _write_nodes(f, points): def _write_cells(f, cells, material): i = 0 - for cell_type, v in cells: + for cell_block in cells: + cell_type = cell_block.type + v = cell_block.data for cell in v[:, meshio_to_avsucd_order[cell_type]]: cell_str = " ".join(str(c) for c in cell + 1) f.write( diff --git a/src/meshio/cgns/_cgns.py b/src/meshio/cgns/_cgns.py index e98f7c2a6..ddb91534f 100644 --- a/src/meshio/cgns/_cgns.py +++ b/src/meshio/cgns/_cgns.py @@ -78,20 +78,20 @@ def write(filename, mesh, compression="gzip", compression_opts=4): # TODO write cells other than tetra elems = zone1.create_group("GridElements") rnge = elems.create_group("ElementRange") - for cell_type, data in mesh.cells: - if cell_type == "tetra": + for cell_block in mesh.cells: + if cell_block.type == "tetra": rnge.create_dataset( " data", - data=[1, data.shape[0]], + data=[1, cell_block.data.shape[0]], compression=compression, compression_opts=compression_opts, ) conn = elems.create_group("ElementConnectivity") - for cell_type, data in mesh.cells: - if cell_type == "tetra": + for cell_block in mesh.cells: + if cell_block.type == "tetra": conn.create_dataset( " data", - data=data.reshape(-1) + 1, + data=cell_block.data.reshape(-1) + 1, compression=compression, compression_opts=compression_opts, ) diff --git a/src/meshio/dolfin/_dolfin.py b/src/meshio/dolfin/_dolfin.py index bf61db079..dc304b4f9 100644 --- a/src/meshio/dolfin/_dolfin.py +++ b/src/meshio/dolfin/_dolfin.py @@ -171,9 +171,9 @@ def _write_mesh(filename, points, cell_type, cells): f.write(f' \n') idx = 0 - for ct, cls in stripped_cells: - type_string = meshio_to_dolfin_type[ct] - for cell in cls: + for cell_block in stripped_cells: + type_string = meshio_to_dolfin_type[cell_block.type] + for cell in cell_block.data: s = " ".join(f'v{k}="{c}"' for k, c in enumerate(cell)) f.write(f' <{type_string} index="{idx}" {s} />\n') idx += 1 diff --git a/src/meshio/exodus/_exodus.py b/src/meshio/exodus/_exodus.py index 4427b2836..b50bd537f 100644 --- a/src/meshio/exodus/_exodus.py +++ b/src/meshio/exodus/_exodus.py @@ -307,16 +307,16 @@ def write(filename, mesh): data = rootgrp.createVariable("eb_prop1", "i4", "num_el_blk") for k in range(len(mesh.cells)): data[k] = k - for k, (key, values) in enumerate(mesh.cells): + for k, cell_block in enumerate(mesh.cells): dim1 = f"num_el_in_blk{k + 1}" dim2 = f"num_nod_per_el{k + 1}" - rootgrp.createDimension(dim1, values.shape[0]) - rootgrp.createDimension(dim2, values.shape[1]) - dtype = numpy_to_exodus_dtype[values.dtype.name] + rootgrp.createDimension(dim1, cell_block.data.shape[0]) + rootgrp.createDimension(dim2, cell_block.data.shape[1]) + dtype = numpy_to_exodus_dtype[cell_block.data.dtype.name] data = rootgrp.createVariable(f"connect{k + 1}", dtype, (dim1, dim2)) - data.elem_type = meshio_to_exodus_type[key] + data.elem_type = meshio_to_exodus_type[cell_block.type] # Exodus is 1-based - data[:] = values + 1 + data[:] = cell_block.data + 1 # point data # The variable `name_nod_var` holds the names and indices of the node variables, the diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index 07173d5a3..fca76685e 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -317,7 +317,7 @@ def _write_cells(f, points, cells, flag, binary): count = 0 cells = _translate_zones(points, cells) else: - count = sum(len(c[1]) for c in cells if c.type in meshio_only["zone"]) + count = sum(len(c) for c in cells if c.type in meshio_only["zone"]) cells = _translate_faces(cells) if binary: @@ -401,20 +401,20 @@ def slicing_summing(a, b, c): return a[:, 0] * c0 + a[:, 1] * c1 + a[:, 2] * c2 zones = [] - for key, idx in cells: - if key not in meshio_only["zone"].keys(): + for cell_block in cells: + if cell_block.type not in meshio_only["zone"].keys(): continue # Compute scalar triple products - key = meshio_only["zone"][key] - tmp = points[idx[:, meshio_to_flac3d_order[key][:4]].T] + key = meshio_only["zone"][cell_block.type] + tmp = points[cell_block.data[:, meshio_to_flac3d_order[key][:4]].T] det = slicing_summing(tmp[1] - tmp[0], tmp[2] - tmp[0], tmp[3] - tmp[0]) # Reorder corner points data = np.where( (det > 0)[:, None], - idx[:, meshio_to_flac3d_order[key]], - idx[:, meshio_to_flac3d_order_2[key]], + cell_block.data[:, meshio_to_flac3d_order[key]], + cell_block.data[:, meshio_to_flac3d_order_2[key]], ) zones.append((key, data)) @@ -424,12 +424,12 @@ def slicing_summing(a, b, c): def _translate_faces(cells): """Reorder meshio cells to FLAC3D faces.""" faces = [] - for key, idx in cells: - if key not in meshio_only["face"].keys(): + for cell_block in cells: + if cell_block.type not in meshio_only["face"].keys(): continue - key = meshio_only["face"][key] - data = idx[:, meshio_to_flac3d_order[key]] + key = meshio_only["face"][cell_block.type] + data = cell_block.data[:, meshio_to_flac3d_order[key]] faces.append((key, data)) return faces diff --git a/src/meshio/gmsh/_gmsh22.py b/src/meshio/gmsh/_gmsh22.py index 2cb43ddd4..29feb031f 100644 --- a/src/meshio/gmsh/_gmsh22.py +++ b/src/meshio/gmsh/_gmsh22.py @@ -8,7 +8,7 @@ from .._common import cell_data_from_raw, num_nodes_per_cell, raw_from_cell_data from .._exceptions import ReadError -from .._mesh import Mesh +from .._mesh import CellBlock, Mesh from .common import ( _fast_forward_over_blank_lines, _fast_forward_to_end_block, @@ -38,6 +38,8 @@ def read_buffer(f, is_ascii, data_size): cell_tags = {} point_data = {} periodic = None + point_tags = None + has_additional_tag_data = False while True: # fast-forward over blank lines line, is_eof = _fast_forward_over_blank_lines(f) @@ -52,7 +54,7 @@ def read_buffer(f, is_ascii, data_size): if environ == "PhysicalNames": _read_physical_names(f, field_data) elif environ == "Nodes": - points, point_tags = _read_nodes(f, is_ascii, data_size) + points, point_tags = _read_nodes(f, is_ascii) elif environ == "Elements": has_additional_tag_data, cell_tags = _read_cells( f, cells, point_tags, is_ascii @@ -94,7 +96,7 @@ def read_buffer(f, is_ascii, data_size): ) -def _read_nodes(f, is_ascii, data_size): +def _read_nodes(f, is_ascii): # The first line is the number of nodes line = f.readline().decode() num_nodes = int(line) @@ -163,7 +165,7 @@ def _read_cells(f, cells, point_tags, is_ascii): return has_additional_tag_data, output_cell_tags -def _read_cells_ascii(f, cells, cell_tags, total_num_cells): +def _read_cells_ascii(f, cells, cell_tags, total_num_cells: int) -> None: for _ in range(total_num_cells): line = f.readline().decode() data = [int(k) for k in filter(None, line.split())] @@ -336,12 +338,29 @@ def _write_nodes(fh, points, float_fmt, binary): def _write_elements(fh, cells, tag_data, binary): # write elements fh.write(b"$Elements\n") + # count all cells - total_num_cells = sum(c.shape[0] for _, c in cells) + total_num_cells = 0 + for k, cell_block in enumerate(cells): + if isinstance(cell_block, tuple): + node_idcs = cell_block[1] + else: + assert isinstance(cell_block, CellBlock) + node_idcs = cell_block.data + + total_num_cells += len(node_idcs) + fh.write(f"{total_num_cells}\n".encode()) consecutive_index = 0 - for k, (cell_type, node_idcs) in enumerate(cells): + for k, cell_block in enumerate(cells): + if isinstance(cell_block, tuple): + cell_type, node_idcs = cell_block + else: + assert isinstance(cell_block, CellBlock) + cell_type = cell_block.type + node_idcs = cell_block.data + tags = [] for name in ["gmsh:physical", "gmsh:geometrical", "cell_tags"]: if name in tag_data: diff --git a/src/meshio/gmsh/_gmsh40.py b/src/meshio/gmsh/_gmsh40.py index b6a1e586e..c08235e40 100644 --- a/src/meshio/gmsh/_gmsh40.py +++ b/src/meshio/gmsh/_gmsh40.py @@ -13,7 +13,7 @@ topological_dimension, ) from .._exceptions import ReadError -from .._mesh import Mesh +from .._mesh import CellBlock, Mesh from .common import ( _fast_forward_to_end_block, _gmsh_to_meshio_order, @@ -316,11 +316,18 @@ def _write_elements(fh, cells, binary): fh.write(b"$Elements\n") if binary: - total_num_cells = sum(data.shape[0] for _, data in cells) + total_num_cells = sum(len(cell_block) for cell_block in cells) np.array([len(cells), total_num_cells], dtype=c_ulong).tofile(fh) consecutive_index = 0 - for cell_type, node_idcs in cells: + for cell_block in cells: + if isinstance(cell_block, tuple): + cell_type, node_idcs = cell_block + else: + assert isinstance(cell_block, CellBlock) + cell_type = cell_block.type + node_idcs = cell_block.data + # tagEntity(int) dimEntity(int) typeEle(int) numElements(unsigned long) np.array( [1, topological_dimension[cell_type], _meshio_to_gmsh_type[cell_type]], @@ -349,22 +356,28 @@ def _write_elements(fh, cells, binary): fh.write(b"\n") else: # count all cells - total_num_cells = sum(data.shape[0] for _, data in cells) + total_num_cells = sum(len(cell_block) for cell_block in cells) fh.write(f"{len(cells)} {total_num_cells}\n".encode()) consecutive_index = 0 - for cell_type, node_idcs in cells: + for cell_block in cells: + if isinstance(cell_block, tuple): + cell_type, cell_data = cell_block + else: + assert isinstance(cell_block, CellBlock) + cell_type = cell_block.type + cell_data = cell_block.data # tagEntity(int) dimEntity(int) typeEle(int) numElements(unsigned long) fh.write( "{} {} {} {}\n".format( 1, # tag topological_dimension[cell_type], _meshio_to_gmsh_type[cell_type], - node_idcs.shape[0], + len(cell_data), ).encode() ) # increment indices by one to conform with gmsh standard - idcs = node_idcs + 1 + idcs = cell_data + 1 fmt = " ".join(["{}"] * (num_nodes_per_cell[cell_type] + 1)) + "\n" for idx in idcs: diff --git a/src/meshio/gmsh/_gmsh41.py b/src/meshio/gmsh/_gmsh41.py index 716a55187..0c733e2ee 100644 --- a/src/meshio/gmsh/_gmsh41.py +++ b/src/meshio/gmsh/_gmsh41.py @@ -404,10 +404,16 @@ def _write_entities(fh, cells, tag_data, cell_sets, point_data, binary): return if binary: - for k, (key, value) in enumerate(cells): - if value.dtype != c_size_t: + for k, cell_block in enumerate(cells): + if isinstance(cell_block, tuple): + cell_type, cell_data = cell_block + else: + assert isinstance(cell_block, CellBlock) + cell_type = cell_block.type + cell_data = cell_block.data + if cell_data.dtype != c_size_t: # Binary Gmsh needs c_size_t. Converting." - cells[k] = CellBlock(key, value.astype(c_size_t)) + cells[k] = CellBlock(cell_type, cell_data.astype(c_size_t)) fh.write(b"$Entities\n") @@ -431,7 +437,7 @@ def _write_entities(fh, cells, tag_data, cell_sets, point_data, binary): cell_dim_tags = np.empty((len(cells), 2), dtype=int) for ci in range(len(cells)): cell_dim_tags[ci] = [ - topological_dimension[cells[ci][0]], + topological_dimension[cells[ci].type], tag_data["gmsh:geometrical"][ci][0], ] @@ -601,7 +607,7 @@ def _write_nodes(fh, points, cells, point_data, float_fmt, binary): + "to deal with more than one cell type. " ) - dim = topological_dimension[cells[0][0]] + dim = topological_dimension[cells[0].type] tag = 0 node_dim_tags = np.array([[dim, tag]]) # All nodes map to the (single) dimension-entity object @@ -658,7 +664,7 @@ def _write_elements(fh, cells, tag_data, binary): """ fh.write(b"$Elements\n") - total_num_cells = sum(len(c) for _, c in cells) + total_num_cells = sum(len(c) for c in cells) num_blocks = len(cells) min_element_tag = 1 max_element_tag = total_num_cells @@ -669,7 +675,14 @@ def _write_elements(fh, cells, tag_data, binary): ).tofile(fh) tag0 = 1 - for ci, (cell_type, node_idcs) in enumerate(cells): + for ci, cell_block in enumerate(cells): + if isinstance(cell_block, tuple): + cell_type, node_idcs = cell_block + else: + assert isinstance(cell_block, CellBlock) + cell_type = cell_block.type + node_idcs = cell_block.data + # entityDim(int) entityTag(int) elementType(int) # numElementsBlock(size_t) @@ -710,7 +723,13 @@ def _write_elements(fh, cells, tag_data, binary): ) tag0 = 1 - for ci, (cell_type, node_idcs) in enumerate(cells): + for ci, cell_block in enumerate(cells): + if isinstance(cell_block, tuple): + cell_type, cell_data = cell_block + else: + assert isinstance(cell_block, CellBlock) + cell_type = cell_block.type + cell_data = cell_block.data # entityDim(int) entityTag(int) elementType(int) numElementsBlock(size_t) dim = topological_dimension[cell_type] @@ -721,12 +740,12 @@ def _write_elements(fh, cells, tag_data, binary): entity_tag = 0 cell_type = _meshio_to_gmsh_type[cell_type] - n = node_idcs.shape[0] + n = len(cell_data) fh.write(f"{dim} {entity_tag} {cell_type} {n}\n".encode()) np.savetxt( fh, # Gmsh indexes from 1 not 0 - np.column_stack([np.arange(tag0, tag0 + n), node_idcs + 1]), + np.column_stack([np.arange(tag0, tag0 + n), cell_data + 1]), "%d", " ", ) diff --git a/src/meshio/gmsh/common.py b/src/meshio/gmsh/common.py index 3690a3269..83cbbde3a 100644 --- a/src/meshio/gmsh/common.py +++ b/src/meshio/gmsh/common.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import logging import shlex import numpy as np from .._exceptions import ReadError, WriteError +from .._mesh import CellBlock c_int = np.dtype("int32") c_double = np.dtype("float64") @@ -158,13 +161,21 @@ def _read_data(f, tag, data_dict, data_size, is_ascii): _meshio_to_gmsh_type = {v: k for k, v in _gmsh_to_meshio_type.items()} -def _reorder_cells(cells, ordering): +def _reorder_cells( + cells: list[CellBlock | tuple[str, np.ndarray]], ordering +) -> list[tuple[str, np.ndarray]]: cells = cells[:] - for i, (cell_type, cell_data) in enumerate(cells): + for i, cell_block in enumerate(cells): + if isinstance(cell_block, tuple): + cell_type, cell_data = cell_block + else: + assert isinstance(cell_block, CellBlock) + cell_type = cell_block.type + cell_data = cell_block.data + permutation = ordering.get(cell_type) if permutation is not None: - cell_data = cell_data[:, permutation] - cells[i] = (cell_type, cell_data) + cells[i] = (cell_type, cell_data[:, permutation]) return cells @@ -191,7 +202,7 @@ def _gmsh_to_meshio_order(cells): return _reorder_cells(cells, meshio_ordering) -def _meshio_to_gmsh_order(cells): +def _meshio_to_gmsh_order(cells: list[CellBlock]): # Gmsh cells are mostly ordered like VTK, with a few exceptions: gmsh_ordering = { # fmt: off diff --git a/src/meshio/h5m/_h5m.py b/src/meshio/h5m/_h5m.py index 8116dc0a4..1f96020cf 100644 --- a/src/meshio/h5m/_h5m.py +++ b/src/meshio/h5m/_h5m.py @@ -228,7 +228,9 @@ def write(filename, mesh, add_global_ids=True, compression="gzip", compression_o "triangle": {"name": "Tri3", "type": 2}, "tetra": {"name": "Tet4", "type": 5}, } - for key, data in mesh.cells: + for cell_block in mesh.cells: + key = cell_block.type + data = cell_block.data if key not in meshio_to_h5m_type: logging.warning("Unsupported H5M element type '%s'. Skipping.", key) continue diff --git a/src/meshio/mdpa/_mdpa.py b/src/meshio/mdpa/_mdpa.py index 85270a97c..67d5d5347 100644 --- a/src/meshio/mdpa/_mdpa.py +++ b/src/meshio/mdpa/_mdpa.py @@ -13,7 +13,7 @@ from .._exceptions import ReadError, WriteError from .._files import open_file from .._helpers import register_format -from .._mesh import CellBlock, Mesh +from .._mesh import Mesh ## We check if we can read/write the mesh natively from Kratos # TODO: Implement native reading @@ -158,20 +158,16 @@ def _read_cells(f, cells, is_ascii, cell_tags, environ=None): if t is None: t = inverse_num_nodes_per_cell[num_nodes_per_elem] - if len(cells) == 0 or t != cells[-1].type: - cells.append(CellBlock(t, [])) + if len(cells) == 0 or t != cells[-1][0]: + cells.append((t, [])) # Subtract one to account for the fact that python indices are 0-based. - cells[-1].data.append(np.array(data[-num_nodes_per_elem:]) - 1) + cells[-1][1].append(np.array(data[-num_nodes_per_elem:]) - 1) # Using the property id as tag if t not in cell_tags: cell_tags[t] = [] cell_tags[t].append([data[1]]) - # convert to numpy arrays - for k, c in enumerate(cells): - cells[k] = CellBlock(c.type, np.array(c.data, dtype=int)) - # Cannot convert cell_tags[key] to numpy array: There may be a # different number of tags for each cell. @@ -385,7 +381,9 @@ def _write_elements_and_conditions(fh, cells, tag_data, binary=False, dimension= dimension_name = f"{dimension}D" wrong_dimension_name = "3D" if dimension == 2 else "2D" consecutive_index = 0 - for cell_type, node_idcs in cells: + for cell_block in cells: + cell_type = cell_block.type + node_idcs = cell_block.data # NOTE: The names of the dummy conditions are not regular, require extra work # local_dimension = local_dimension_types[cell_type] # if (local_dimension < dimension): diff --git a/src/meshio/med/_med.py b/src/meshio/med/_med.py index f1c114b9b..301322466 100644 --- a/src/meshio/med/_med.py +++ b/src/meshio/med/_med.py @@ -270,7 +270,9 @@ def write(filename, mesh): raise WriteError("MED files cannot have two sections of the same cell type.") cells_group = time_step.create_group("MAI") cells_group.attrs.create("CGT", 1) - for k, (cell_type, cells) in enumerate(mesh.cells): + for k, cell_block in enumerate(mesh.cells): + cell_type = cell_block.type + cells = cell_block.data med_type = meshio_to_med_type[cell_type] med_cells = cells_group.create_group(med_type) med_cells.attrs.create("CGT", 1) diff --git a/src/meshio/medit/_medit.py b/src/meshio/medit/_medit.py index 9dab9c196..09a98ff43 100644 --- a/src/meshio/medit/_medit.py +++ b/src/meshio/medit/_medit.py @@ -351,7 +351,9 @@ def write_ascii_file(filename, mesh, float_fmt=".16e"): f"Picking {labels_key}, skipping {string}." ) - for k, (cell_type, data) in enumerate(mesh.cells): + for k, cell_block in enumerate(mesh.cells): + cell_type = cell_block.type + data = cell_block.data try: medit_name, num = medit_from_meshio[cell_type] except KeyError: @@ -390,8 +392,8 @@ def write_binary_file(f, mesh): # if we store internally 64bit integers upgrade file version has_big_ints = False - for _, data in mesh.cells: - if data.dtype.itemsize == 8: + for cell_block in mesh.cells: + if cell_block.data.dtype.itemsize == 8: has_big_ints = True break @@ -472,16 +474,17 @@ def write_binary_file(f, mesh): "pyramid": 49, "hexahedron": 10, } - for k, (cell_type, data) in enumerate(mesh.cells): + for k, cell_block in enumerate(mesh.cells): try: - medit_key = medit_from_meshio[cell_type] + medit_key = medit_from_meshio[cell_block.type] except KeyError: logging.warning( - f"MEDIT's mesh format doesn't know {cell_type} cells. Skipping." + f"MEDIT's mesh format doesn't know {cell_block.type} cells. " + + "Skipping." ) continue - num_cells, num_verts = data.shape + num_cells, num_verts = cell_block.data.shape pos += num_cells * (num_verts + 1) * itype_size pos += keyword_size + postype_size + itype_size @@ -497,7 +500,7 @@ def write_binary_file(f, mesh): labels = ( mesh.cell_data[labels_key][k] if labels_key - else np.ones(len(data), dtype=data.dtype) + else np.ones(len(cell_block.data), dtype=cell_block.data.dtype) ) field_template = medit_codes[medit_key][2] dtype = np.dtype(_produce_dtype(field_template, dim, itype, ftype)) @@ -505,7 +508,7 @@ def write_binary_file(f, mesh): tmp_array = np.empty(num_cells, dtype=dtype) i = 0 for col_type in dtype.names[:-1]: - tmp_array[col_type] = data[:, i] + 1 + tmp_array[col_type] = cell_block.data[:, i] + 1 i += 1 tmp_array[dtype.names[-1]] = labels diff --git a/src/meshio/nastran/_nastran.py b/src/meshio/nastran/_nastran.py index 16450e24a..7a88f07b9 100644 --- a/src/meshio/nastran/_nastran.py +++ b/src/meshio/nastran/_nastran.py @@ -92,13 +92,13 @@ def add_cell(nastran_type, cell, cell_type, cell_ref): cell = _convert_to_vtk_ordering(cell, nastran_type) - if len(cells) > 0 and cells[-1].type == cell_type: - cells[-1].data.append(cell) + if len(cells) > 0 and cells[-1][0] == cell_type: + cells[-1][1].append(cell) cells_id[-1].append(cell_id) if cell_ref is not None: cell_refs[-1].append(cell_ref) else: - cells.append(CellBlock(cell_type, [cell])) + cells.append((cell_type, [cell])) cells_id.append([cell_id]) if cell_ref is not None: cell_refs.append([cell_ref]) @@ -188,7 +188,7 @@ def add_cell(nastran_type, cell, cell_type, cell_ref): points = np.array(points) points_id = np.array(points_id, dtype=int) for k, (c, cid) in enumerate(zip(cells, cells_id)): - cells[k] = CellBlock(c.type, np.array(c.data, dtype=int)) + cells[k] = CellBlock(c[0], np.array(c[1], dtype=int)) cells_id[k] = np.array(cid, dtype=int) # Convert to natural point ordering @@ -269,7 +269,9 @@ def write(filename, mesh, point_format="fixed-large", cell_format="fixed-small") # CellBlock cell_id = 0 cell_refs = mesh.cell_data.get("nastran:ref", None) - for ict, (cell_type, cells) in enumerate(mesh.cells): + for ict, cell_block in enumerate(mesh.cells): + cell_type = cell_block.type + cells = cell_block.data nastran_type = meshio_to_nastran_type[cell_type] if cell_format.endswith("-large"): nastran_type += "*" diff --git a/src/meshio/obj/_obj.py b/src/meshio/obj/_obj.py index 01287379c..6b2b4a7db 100644 --- a/src/meshio/obj/_obj.py +++ b/src/meshio/obj/_obj.py @@ -126,9 +126,9 @@ def write(filename, mesh): for vt in dat: f.write(fmt.format(*vt)) - for _, cell_array in mesh.cells: - fmt = "f " + " ".join(["{}"] * cell_array.shape[1]) + "\n" - for c in cell_array: + for cell_block in mesh.cells: + fmt = "f " + " ".join(["{}"] * cell_block.data.shape[1]) + "\n" + for c in cell_block.data: f.write(fmt.format(*(c + 1))) diff --git a/src/meshio/permas/_permas.py b/src/meshio/permas/_permas.py index d5e6075b6..572664fcb 100644 --- a/src/meshio/permas/_permas.py +++ b/src/meshio/permas/_permas.py @@ -243,31 +243,32 @@ def write(filename, mesh): tet10_order = [0, 4, 1, 5, 2, 6, 7, 8, 9, 3] quad9_order = [0, 4, 1, 7, 8, 5, 3, 6, 2] wedge15_order = [0, 6, 1, 7, 2, 8, 9, 10, 11, 3, 12, 4, 13, 5, 14] - for cell_type, node_idcs in mesh.cells: + for cell_block in mesh.cells: + node_idcs = cell_block.data f.write("!\n") - f.write("$ELEMENT TYPE=" + meshio_to_permas_type[cell_type] + "\n") - if cell_type == "tetra10": + f.write("$ELEMENT TYPE=" + meshio_to_permas_type[cell_block.type] + "\n") + if cell_block.type == "tetra10": for row in node_idcs: eid += 1 mylist = row.tolist() mylist = [mylist[i] for i in tet10_order] nids_strs = (str(nid + 1) for nid in mylist) f.write(str(eid) + " " + " ".join(nids_strs) + "\n") - elif cell_type == "triangle6": + elif cell_block.type == "triangle6": for row in node_idcs: eid += 1 mylist = row.tolist() mylist = [mylist[i] for i in tria6_order] nids_strs = (str(nid + 1) for nid in mylist) f.write(str(eid) + " " + " ".join(nids_strs) + "\n") - elif cell_type == "quad9": + elif cell_block.type == "quad9": for row in node_idcs: eid += 1 mylist = row.tolist() mylist = [mylist[i] for i in quad9_order] nids_strs = (str(nid + 1) for nid in mylist) f.write(str(eid) + " " + " ".join(nids_strs) + "\n") - elif cell_type == "wedge15": + elif cell_block.type == "wedge15": for row in node_idcs: eid += 1 mylist = row.tolist() diff --git a/src/meshio/ply/_ply.py b/src/meshio/ply/_ply.py index 1e76fcae1..1c51e341b 100644 --- a/src/meshio/ply/_ply.py +++ b/src/meshio/ply/_ply.py @@ -233,10 +233,10 @@ def _read_ascii( n = ply_to_numpy_dtype[idx_dtype](data[i]) dtype = ply_to_numpy_dtype[value_dtype] idx = dtype(data[i + 1 : i + n + 1]) - if len(cell_blocks) == 0 or len(cell_blocks[-1].data[-1]) != n: - cell_blocks.append(CellBlock(cell_type_from_count(n), [idx])) + if len(cell_blocks) == 0 or len(cell_blocks[-1][1][-1]) != n: + cell_blocks.append((cell_type_from_count(n), [idx])) else: - cell_blocks[-1].data.append(idx) + cell_blocks[-1][1].append(idx) i += n + 1 else: dtype = ply_to_numpy_dtype[dtype] @@ -441,9 +441,9 @@ def write(filename, mesh: Mesh, binary: bool = True): # noqa: C901 num_cells = 0 legal_cell_types = ["vertex", "line", "triangle", "quad", "polygon"] - for cell_type, c in mesh.cells: - if cell_type in legal_cell_types: - num_cells += c.data.shape[0] + for cell_block in mesh.cells: + if cell_block.type in legal_cell_types: + num_cells += cell_block.data.shape[0] if num_cells > 0: fh.write(f"element face {num_cells:d}\n".encode()) @@ -451,10 +451,12 @@ def write(filename, mesh: Mesh, binary: bool = True): # noqa: C901 # possibly cast down to int32 # TODO don't alter the mesh data has_cast = False - for k, (cell_type, data) in enumerate(mesh.cells): - if data.dtype == np.int64: + for k, cell_block in enumerate(mesh.cells): + if cell_block.data.dtype == np.int64: has_cast = True - mesh.cells[k] = CellBlock(cell_type, data.astype(np.int32)) + mesh.cells[k] = CellBlock( + cell_block.type, cell_block.data.astype(np.int32) + ) if has_cast: warnings.warn( @@ -463,10 +465,10 @@ def write(filename, mesh: Mesh, binary: bool = True): # noqa: C901 # assert that all cell dtypes are equal cell_dtype = None - for _, cell in mesh.cells: + for cell_block in mesh.cells: if cell_dtype is None: - cell_dtype = cell.dtype - if cell.dtype != cell_dtype: + cell_dtype = cell_block.data.dtype + if cell_block.data.dtype != cell_dtype: raise WriteError() if cell_dtype is not None: @@ -482,19 +484,17 @@ def write(filename, mesh: Mesh, binary: bool = True): # noqa: C901 fh.write(out.tobytes()) # cells - for cell_type, data in mesh.cells: - if cell_type not in legal_cell_types: + for cell_block in mesh.cells: + if cell_block.type not in legal_cell_types: warnings.warn( - f'cell_type "{cell_type}" is not supported by ply format - ' - "skipping" + f'cell_type "{cell_block.type}" is not supported by PLY format ' + "- skipping" ) continue # prepend with count + d = cell_block.data out = np.rec.fromarrays( - [ - np.broadcast_to(np.uint8(data.shape[1]), data.shape[0]), - *data.T, - ] + [np.broadcast_to(np.uint8(d.shape[1]), d.shape[0]), *d.T] ) fh.write(out.tobytes()) else: @@ -507,16 +507,18 @@ def write(filename, mesh: Mesh, binary: bool = True): # noqa: C901 fh.write(out.encode()) # cells - for cell_type, data in mesh.cells: - if cell_type not in legal_cell_types: + for cell_block in mesh.cells: + if cell_block.type not in legal_cell_types: warnings.warn( - 'cell_type "{}" is not supported by ply format - skipping' + f'cell_type "{cell_block.type}" is not supported by PLY format ' + + "- skipping" ) continue # if cell_type not in cell_type_to_count.keys(): # continue + d = cell_block.data out = np.column_stack( - [np.full(data.shape[0], data.shape[1], dtype=data.dtype), data] + [np.full(d.shape[0], d.shape[1], dtype=d.dtype), d] ) # savetxt is slower # np.savetxt(fh, out, "%d %d %d %d") diff --git a/src/meshio/su2/_su2.py b/src/meshio/su2/_su2.py index 87f663c09..1b6a398a4 100644 --- a/src/meshio/su2/_su2.py +++ b/src/meshio/su2/_su2.py @@ -260,8 +260,8 @@ def write(filename, mesh): np.savetxt(f, mesh.points) # Through warnings about unsupported types - for type, _ in mesh.cells: - if type not in meshio_to_su2_type: + for cell_block in mesh.cells: + if cell_block.type not in meshio_to_su2_type: logging.warning( ".su2 does not support tags elements of type {}.\n" "Skipping ...".format(type) @@ -312,15 +312,15 @@ def write(filename, mesh): # We want to separate boundary elements in groups of same tag # First, find unique tags and how many elements per tags we have - for index, (cell_type, data) in enumerate(mesh.cells): + for index, cell_block in enumerate(mesh.cells): - if cell_type not in types: + if cell_block.type not in types: continue labels = ( mesh.cell_data[labels_key][index] if labels_key - else np.ones(len(data), dtype=data.dtype) + else np.ones(len(cell_block), dtype=cell_block.data.dtype) ) # Get unique tags and number of instances of each tag for this Cell block diff --git a/src/meshio/tetgen/_tetgen.py b/src/meshio/tetgen/_tetgen.py index 052389672..e97367931 100644 --- a/src/meshio/tetgen/_tetgen.py +++ b/src/meshio/tetgen/_tetgen.py @@ -157,7 +157,8 @@ def write(filename, mesh, float_fmt=".16e"): fh.write(f"# This file was created by meshio v{__version__}\n") if nattr > 0: fh.write("# attribute names: {}\n".format(", ".join(attr_keys))) - for id, (_, data) in enumerate(filter(lambda c: c.type == "tetra", mesh.cells)): + for id, c in enumerate(filter(lambda c: c.type == "tetra", mesh.cells)): + data = c.data fh.write(f"{data.shape[0]} {4} {nattr}\n") fmt = " ".join((5 + nattr) * ["{}"]) + "\n" for k, tet in enumerate(data): diff --git a/src/meshio/ugrid/_ugrid.py b/src/meshio/ugrid/_ugrid.py index fecaaba35..de45473f7 100644 --- a/src/meshio/ugrid/_ugrid.py +++ b/src/meshio/ugrid/_ugrid.py @@ -185,7 +185,9 @@ def _write_buffer(f, file_type, mesh): ugrid_counts["points"] = mesh.points.shape[0] - for i, (key, data) in enumerate(mesh.cells): + for i, cell_block in enumerate(mesh.cells): + key = cell_block.type + data = cell_block.data if key in ugrid_counts: if ugrid_counts[key] > 0: raise ValueError("Ugrid can only handle one cell block of a type.") @@ -214,8 +216,8 @@ def _write_buffer(f, file_type, mesh): # start next record fortran_header = mesh.points.nbytes - for key, array in mesh.cells: - fortran_header += array.nbytes + for cell_block in mesh.cells: + fortran_header += cell_block.data.nbytes # boundary tags if ugrid_counts["triangle"] > 0: fortran_header += ugrid_counts["triangle"] * np.dtype(itype).itemsize diff --git a/src/meshio/vtk/_vtk_42.py b/src/meshio/vtk/_vtk_42.py index c79a79a67..428f1e7d2 100644 --- a/src/meshio/vtk/_vtk_42.py +++ b/src/meshio/vtk/_vtk_42.py @@ -9,7 +9,7 @@ from ..__about__ import __version__ from .._exceptions import ReadError, WriteError from .._files import open_file -from .._mesh import CellBlock, Mesh +from .._mesh import Mesh from .._vtk_common import ( Info, meshio_to_vtk_order, @@ -544,19 +544,15 @@ def translate_cells(connectivity, types, cell_data_raw): if ( len(cells) > 0 - and cells[-1].type == cell_type + and cells[-1][0] == cell_type # the following check if needed for polygons; key can be equal, but # still needs to go into a new block - and len(cell) == len(cells[-1].data[-1]) + and len(cell) == len(cells[-1][1][-1]) ): - cells[-1].data.append(cell) + cells[-1][1].append(cell) else: # open up a new cell block - cells.append(CellBlock(cell_type, [cell])) - - # convert data to numpy arrays - for k, c in enumerate(cells): - cells[k] = CellBlock(c.type, np.array(c.data)) + cells.append((cell_type, [cell])) else: # Infer offsets from the cell types. This is much faster than manually going # through the data array. Slight disadvantage: This doesn't work for cells with @@ -593,7 +589,7 @@ def translate_cells(connectivity, types, cell_data_raw): else: cell_idx = idx0 + new_order indices = np.add.outer(offsets[start:end], cell_idx) - cells.append(CellBlock(meshio_type, connectivity[indices])) + cells.append((meshio_type, connectivity[indices])) for name, d in cell_data_raw.items(): if name not in cell_data: cell_data[name] = [] diff --git a/src/meshio/vtu/_vtu.py b/src/meshio/vtu/_vtu.py index 498c4d173..6117079ed 100644 --- a/src/meshio/vtu/_vtu.py +++ b/src/meshio/vtu/_vtu.py @@ -654,7 +654,9 @@ def write(filename, mesh, binary=True, compression="zlib", header_type=None): # Don't use byteswap to make sure that the dtype is changed; see # . points = points.astype(points.dtype.newbyteorder("="), copy=False) - for k, (cell_type, data) in enumerate(mesh.cells): + for k, cell_block in enumerate(mesh.cells): + cell_type = cell_block.type + data = cell_block.data # Treatment of polyhedra is different from other types if is_polyhedron_grid: new_cell_info = [] @@ -841,12 +843,13 @@ def _polyhedron_face_cells(face_cells): # types types_array = [] - for key, v in mesh.cells: + for cell_block in mesh.cells: + key = cell_block.type # some adaptions for polyhedron if key.startswith("polyhedron"): # Get face-cell relation on the vtu format. See comments in helper # function for more information of how to specify this. - faces_loc, faceoffsets_loc = _polyhedron_face_cells(v) + faces_loc, faceoffsets_loc = _polyhedron_face_cells(cell_block.data) # Adjust offsets to global numbering assert faceoffsets is not None if len(faceoffsets) > 0: @@ -857,7 +860,7 @@ def _polyhedron_face_cells(face_cells): faceoffsets += faceoffsets_loc key = "polyhedron" - types_array.append(np.full(len(v), meshio_to_vtk_type[key])) + types_array.append(np.full(len(cell_block), meshio_to_vtk_type[key])) types = np.concatenate( types_array diff --git a/src/meshio/xdmf/main.py b/src/meshio/xdmf/main.py index c1ea79375..438104d12 100644 --- a/src/meshio/xdmf/main.py +++ b/src/meshio/xdmf/main.py @@ -462,13 +462,16 @@ def write_cells(self, cells, grid): np.hstack( [ np.full( - (value.shape[0], 2 if key in {"vertex", "line"} else 1), - meshio_type_to_xdmf_index[key], + ( + cell_block.data.shape[0], + 2 if cell_block.type in {"vertex", "line"} else 1, + ), + meshio_type_to_xdmf_index[cell_block.type], ), - value, + cell_block.data, ] ).flatten() - for key, value in cells + for cell_block in cells ] ) dt, prec = numpy_to_xdmf_dtype[cd.dtype.name] diff --git a/src/meshio/xdmf/time_series.py b/src/meshio/xdmf/time_series.py index b3da25e2b..3706ab685 100644 --- a/src/meshio/xdmf/time_series.py +++ b/src/meshio/xdmf/time_series.py @@ -1,10 +1,12 @@ +from __future__ import annotations + import os import pathlib -import warnings from io import BytesIO from xml.etree import ElementTree as ET import numpy as np +from numpy.typing import ArrayLike from .._common import cell_data_from_raw, raw_from_cell_data, write_xml from .._exceptions import ReadError, WriteError @@ -268,7 +270,11 @@ def __exit__(self, *_): if self.data_format == "HDF": self.h5_file.close() - def write_points_cells(self, points, cells): + def write_points_cells( + self, + points: ArrayLike, + cells: dict[str, ArrayLike] | list[tuple[str, ArrayLike] | CellBlock], + ) -> None: # # # maxwell.h5:/Mesh/0/mesh/topology @@ -282,9 +288,7 @@ def write_points_cells(self, points, cells): self.domain, "Grid", Name=self.mesh_name, GridType="Uniform" ) self.points(grid, np.asarray(points)) - self.cells( - [CellBlock(cell_type, np.asarray(data)) for cell_type, data in cells], grid - ) + self.cells(cells, grid) self.has_mesh = True def write_data(self, t, point_data=None, cell_data=None): @@ -356,15 +360,6 @@ def points(self, grid, points): data_item.text = self.numpy_to_xml_string(points) def cells(self, cells, grid): - if isinstance(cells, dict): - warnings.warn( - "cell dictionaries are deprecated, use list of tuples, e.g., " - '[("triangle", [[0, 1, 2], ...])]', - DeprecationWarning, - ) - cells = [CellBlock(cell_type, data) for cell_type, data in cells.items()] - else: - cells = [CellBlock(cell_type, data) for cell_type, data in cells] if len(cells) == 1: meshio_type = cells[0].type num_cells = len(cells[0].data) diff --git a/tests/helpers.py b/tests/helpers.py index e6f92a7d4..bdcc0263b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -286,7 +286,8 @@ [1.0, 1.0, 1.0], [0.0, 1.0, 1.0], ], - [ # Split the cube into tets and pyramids. + # Split the cube into tets and pyramids. + [ ( "polyhedron4", [ @@ -611,8 +612,8 @@ def add_cell_data(mesh, specs: list[tuple[str, tuple[int, ...], type]]): mesh2.cell_data = { name: [ - (100 * rng.random((len(cells),) + shape)).astype(dtype) - for _, cells in mesh.cells + (100 * rng.random((len(cellblock),) + shape)).astype(dtype) + for cellblock in mesh.cells ] for name, shape, dtype in specs } diff --git a/tests/test_flac3d.py b/tests/test_flac3d.py index a5d3a180c..40cc67838 100644 --- a/tests/test_flac3d.py +++ b/tests/test_flac3d.py @@ -59,7 +59,9 @@ def test_reference_file(filename): ("quad", 15), ("triangle", 3), ] - assert [(k, len(v)) for k, v in mesh.cells] == ref_num_cells + assert [ + (cell_block.type, len(cell_block)) for cell_block in mesh.cells + ] == ref_num_cells # Cell sets for arr in mesh.cell_sets.values(): assert len(arr) == 12 diff --git a/tests/test_med.py b/tests/test_med.py index 3bd2216bf..324eb2edb 100644 --- a/tests/test_med.py +++ b/tests/test_med.py @@ -54,7 +54,9 @@ def test_reference_file_with_mixed_cells(tmp_path): # CellBlock ref_num_cells = {"pyramid": 18, "quad": 18, "line": 17, "tetra": 63, "triangle": 4} - assert {k: len(v) for k, v in mesh.cells} == ref_num_cells + assert { + cell_block.type: len(cell_block) for cell_block in mesh.cells + } == ref_num_cells # Point tags assert mesh.point_data["point_tags"].sum() == 52 @@ -96,7 +98,9 @@ def test_reference_file_with_point_cell_data(tmp_path): assert np.isclose(mesh.points.sum(), 12) # CellBlock - assert {k: len(v) for k, v in mesh.cells} == {"hexahedron": 1} + assert {cell_block.type: len(cell_block) for cell_block in mesh.cells} == { + "hexahedron": 1 + } # Point data data_u = mesh.point_data["resu____DEPL"] diff --git a/tests/test_medit.py b/tests/test_medit.py index e00ab3b9c..914062cec 100644 --- a/tests/test_medit.py +++ b/tests/test_medit.py @@ -83,9 +83,9 @@ def test_reference_file( "hexahedron": None, } - for i, (key, data) in enumerate(mesh.cells): - if key in medit_meshio_id: - medit_meshio_id[key] = i + for i, cell_block in enumerate(mesh.cells): + if cell_block.type in medit_meshio_id: + medit_meshio_id[cell_block.type] = i # validate element counts if ref_num_triangle > 0: diff --git a/tests/test_nastran.py b/tests/test_nastran.py index 9eed281a0..61515be28 100644 --- a/tests/test_nastran.py +++ b/tests/test_nastran.py @@ -49,7 +49,9 @@ def test_reference_file(filename): "pyramid": 1180, "tetra": 5309, } - assert {k: v.sum() for k, v in mesh.cells} == ref_num_cells + assert { + cell_block.type: cell_block.data.sum() for cell_block in mesh.cells + } == ref_num_cells def test_long_format(): diff --git a/tests/test_ugrid.py b/tests/test_ugrid.py index 81f5c771d..eeb0b63d8 100644 --- a/tests/test_ugrid.py +++ b/tests/test_ugrid.py @@ -91,9 +91,9 @@ def test_reference_file( "hexahedron": None, } - for i, (key, data) in enumerate(mesh.cells): - if key in ugrid_meshio_id: - ugrid_meshio_id[key] = i + for i, cell_block in enumerate(mesh.cells): + if cell_block.type in ugrid_meshio_id: + ugrid_meshio_id[cell_block.type] = i # validate element counts if ref_num_triangle > 0: @@ -229,9 +229,9 @@ def test_area(filename, area_tria_ref, area_quad_ref, accuracy): "hexahedron": None, } - for i, (key, data) in enumerate(mesh.cells): - if key in ugrid_meshio_id: - ugrid_meshio_id[key] = i + for i, cell_block in enumerate(mesh.cells): + if cell_block.type in ugrid_meshio_id: + ugrid_meshio_id[cell_block.type] = i tria = mesh.cells[ugrid_meshio_id["triangle"]] total_tri_area = 0 diff --git a/tox.ini b/tox.ini index 8074ff54b..2a7ad1f1c 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ isolated_build = True [testenv] deps = pytest - pytest-codeblocks + pytest-codeblocks >= 0.12.1 pytest-cov pytest-randomly extras = all