From 609dd4d8139586cf08f2034c794c5edc16434db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Wed, 23 Feb 2022 20:00:01 +0100 Subject: [PATCH 01/19] automatic set->data conversion --- src/meshio/_common.py | 35 +++++++++++++++++++++++++++++++++++ src/meshio/_mesh.py | 9 +++++---- src/meshio/vtk/_vtk_51.py | 21 ++++++++++++++++----- src/meshio/vtu/_vtu.py | 27 +++++++++++++++++++-------- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/meshio/_common.py b/src/meshio/_common.py index ccd3f8c1b..850853ed0 100644 --- a/src/meshio/_common.py +++ b/src/meshio/_common.py @@ -120,6 +120,10 @@ def _pick_first_int_data(data): return key, other +def info(string, highlight: bool = True) -> None: + Console(stderr=True).print(f"[bold]Info:[/bold] {string}", highlight=highlight) + + def warn(string, highlight: bool = True) -> None: Console(stderr=True).print( f"[yellow][bold]Warning:[/bold] {string}[/yellow]", highlight=highlight @@ -130,3 +134,34 @@ def error(string, highlight: bool = True) -> None: Console(stderr=True).print( f"[red][bold]Error:[/bold] {string}[/red]", highlight=highlight ) + + +def is_in_any(string: str, strings: list[str]) -> bool: + """True if `string` is contained in any of `strings`.""" + for s in strings: + if string in s: + return True + return False + + +def join_strings(strings: list[str]) -> tuple[str, str]: + """Join strings such that they can be uniquely split again afterwards.""" + possible_join_chars = ["-", "_", "#", "+", "/"] + char = None + for c in possible_join_chars: + if not is_in_any(c, strings): + char = c + break + assert char is not None + return char.join(strings), char + + +def replace_space(string: str) -> tuple[str, str]: + possible_chars = ["_", "-", "+", "X", "/", "#"] + char = None + for c in possible_chars: + if c not in string: + char = c + break + assert char is not None + return string.replace(" ", char), char diff --git a/src/meshio/_mesh.py b/src/meshio/_mesh.py index da897d814..741831d93 100644 --- a/src/meshio/_mesh.py +++ b/src/meshio/_mesh.py @@ -313,7 +313,7 @@ def read(cls, path_or_buf, file_format=None): warn("meshio.Mesh.read is deprecated, use meshio.read instead") return read(path_or_buf, file_format) - def cell_sets_to_data(self): + def cell_sets_to_data(self, data_name: str | None = None): # If possible, convert cell sets to integer cell data. This is possible if all # cells appear exactly in one group. default_value = -1 @@ -337,11 +337,12 @@ def cell_sets_to_data(self): ) break - data_name = "-".join(self.cell_sets.keys()) + if data_name is None: + data_name = "-".join(self.cell_sets.keys()) self.cell_data[data_name] = intfun self.cell_sets = {} - def point_sets_to_data(self): + def point_sets_to_data(self, join_char: str = "-") -> None: # now for the point sets # Go for -1 as the default value. (NaN is not int.) default_value = -1 @@ -356,7 +357,7 @@ def point_sets_to_data(self): f"Using default value {default_value}." ) - data_name = "-".join(self.point_sets.keys()) + data_name = join_char.join(self.point_sets.keys()) self.point_data[data_name] = intfun self.point_sets = {} diff --git a/src/meshio/vtk/_vtk_51.py b/src/meshio/vtk/_vtk_51.py index b27b8b320..2eb1db6b5 100644 --- a/src/meshio/vtk/_vtk_51.py +++ b/src/meshio/vtk/_vtk_51.py @@ -3,7 +3,7 @@ import numpy as np from ..__about__ import __version__ -from .._common import warn +from .._common import info, join_strings, replace_space, warn from .._exceptions import ReadError, WriteError from .._files import open_file from .._mesh import Mesh @@ -511,12 +511,23 @@ def write(filename, mesh, binary=True): if not binary: warn("VTK ASCII files are only meant for debugging.") - if mesh.point_sets or mesh.cell_sets: - warn( - "VTK format cannot write sets. " - + "Consider converting them to \\[point,cell]_data.", + if mesh.point_sets: + info( + "VTK format cannot write point_sets. Converting them to point_data...", + highlight=False, + ) + key, _ = join_strings(list(mesh.point_sets.keys())) + key, _ = replace_space(key) + mesh.point_sets_to_data(key) + + if mesh.cell_sets: + info( + "VTK format cannot write cell_sets. Converting them to cell_data...", highlight=False, ) + key, _ = join_strings(list(mesh.cell_sets.keys())) + key, _ = replace_space(key) + mesh.cell_sets_to_data(key) with open_file(filename, "wb") as f: f.write(b"# vtk DataFile Version 5.1\n") diff --git a/src/meshio/vtu/_vtu.py b/src/meshio/vtu/_vtu.py index 0fa7e695a..6a90fcd96 100644 --- a/src/meshio/vtu/_vtu.py +++ b/src/meshio/vtu/_vtu.py @@ -11,7 +11,7 @@ import numpy as np from ..__about__ import __version__ -from .._common import raw_from_cell_data, warn +from .._common import info, join_strings, raw_from_cell_data, replace_space, warn from .._exceptions import CorruptionError, ReadError from .._helpers import register_format from .._mesh import CellBlock, Mesh @@ -625,6 +625,24 @@ def write(filename, mesh, binary=True, compression="zlib", header_type=None): else: points = mesh.points + if mesh.point_sets: + info( + "VTU format cannot write point_sets. Converting them to point_data...", + highlight=False, + ) + key, _ = join_strings(list(mesh.point_sets.keys())) + key, _ = replace_space(key) + mesh.point_sets_to_data(key) + + if mesh.cell_sets: + info( + "VTU format cannot write cell_sets. Converting them to cell_data...", + highlight=False, + ) + key, _ = join_strings(list(mesh.cell_sets.keys())) + key, _ = replace_space(key) + mesh.cell_sets_to_data(key) + vtk_file = ET.Element( "VTKFile", type="UnstructuredGrid", @@ -885,13 +903,6 @@ def _polyhedron_face_cells(face_cells): for name, data in raw_from_cell_data(mesh.cell_data).items(): numpy_to_xml_array(cd, name, data) - if mesh.point_sets or mesh.cell_sets: - warn( - "VTU format cannot write sets. " - + "Consider converting them to \\[point,cell]_data.", - highlight=False, - ) - # write_xml(filename, vtk_file, pretty_xml) tree = ET.ElementTree(vtk_file) tree.write(filename) From 89ad52731636d67a827f824c65dce6188a6eb094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Wed, 23 Feb 2022 20:56:27 +0100 Subject: [PATCH 02/19] write cell sets in flac3d --- src/meshio/flac3d/_flac3d.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index f0e9861c7..28a35cb34 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -9,7 +9,7 @@ import numpy as np from ..__about__ import __version__ as version -from .._common import _pick_first_int_data, warn +from .._common import _pick_first_int_data, info, join_strings, replace_space, warn from .._files import open_file from .._helpers import register_format from .._mesh import Mesh @@ -354,6 +354,15 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): "FLAC3D can only write one cell data array. " f'Picking {key}, skipping {", ".join(other)}.' ) + elif mesh.cell_sets: + info( + "FLAC3D: Converting cell_sets to cell_data.", + highlight=False, + ) + key, _ = join_strings(list(mesh.cell_sets.keys())) + key, _ = replace_space(key) + mesh.cell_sets_to_data(key) + material = np.concatenate(mesh.cell_data[key]) mode = "wb" if binary else "w" with open_file(filename, mode) as f: @@ -510,7 +519,7 @@ def _translate_faces(cells): def _translate_groups(cells, cell_data, field_data, flag): """Convert meshio cell_data to FLAC3D groups.""" num_dims = np.concatenate( - [np.full(len(c[1]), 2 if c[0] in meshio_only["face"] else 3) for c in cells] + [np.full(len(c.data), 2 if c.type in meshio_only["face"] else 3) for c in cells] ) groups = { k: np.nonzero(np.logical_and(cell_data == k, num_dims == flag_to_numdim[flag]))[ From 8dec28e235b1f9f62d00a9dd4e6d73758b8ee855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Wed, 23 Feb 2022 21:32:50 +0100 Subject: [PATCH 03/19] some improvements for flac3d --- src/meshio/flac3d/_flac3d.py | 67 ++++++++++++++++++------------------ tests/test_flac3d.py | 12 +++---- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index 28a35cb34..ccecd9844 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -432,40 +432,41 @@ def _write_cells(f, points, cells, flag, binary): f.write(fmt.format(meshio_to_flac3d_type[ctype], count, *entry)) -def _write_groups(f, cells, cell_data, field_data, flag, binary): +def _write_groups(f, cells, cell_data, field_data, flag, binary) -> None: """Write groups.""" if cell_data is None: if binary: f.write(struct.pack(" Date: Wed, 23 Feb 2022 22:05:58 +0100 Subject: [PATCH 04/19] rearrange materials data in flac3d --- src/meshio/flac3d/_flac3d.py | 54 +++++++++++++++++++----------------- tests/helpers.py | 9 +++--- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index ccecd9844..ca04d7744 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -9,7 +9,7 @@ import numpy as np from ..__about__ import __version__ as version -from .._common import _pick_first_int_data, info, join_strings, replace_space, warn +from .._common import _pick_first_int_data, warn from .._files import open_file from .._helpers import register_format from .._mesh import Mesh @@ -317,6 +317,7 @@ def _read_cell_group_ascii(buf_or_line, line): data = [] while True: line = line.rstrip().split() + print(line) if line and (line[0] not in {"*", "ZGROUP", "FGROUP"}): data += [int(l) for l in line] else: @@ -344,25 +345,30 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): warn(f'FLAC3D format only supports 3D cells. Skipping {", ".join(skip)}.') # Pick out material - material = None - if mesh.cell_data: + materials = None + if mesh.cell_sets: + materials = mesh.cell_sets.copy() + elif mesh.cell_data: + # TODO convert cell_data to cell_sets + exit(1) key, other = _pick_first_int_data(mesh.cell_data) if key: - material = np.concatenate(mesh.cell_data[key]) + materials = np.concatenate(mesh.cell_data[key]) if other: warn( "FLAC3D can only write one cell data array. " f'Picking {key}, skipping {", ".join(other)}.' ) - elif mesh.cell_sets: - info( - "FLAC3D: Converting cell_sets to cell_data.", - highlight=False, - ) - key, _ = join_strings(list(mesh.cell_sets.keys())) - key, _ = replace_space(key) - mesh.cell_sets_to_data(key) - material = np.concatenate(mesh.cell_data[key]) + + if materials is not None: + # Translate the material array from meshio.cell_set data to a + # dictionary with labels as keys, and _global_ indices. + for label, values in materials.items(): + count = 0 + for cells, item in zip(mesh.cells, values): + item += count + count += len(cells) + materials[label] = np.concatenate(values) mode = "wb" if binary else "w" with open_file(filename, mode) as f: @@ -376,7 +382,7 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): _write_points(f, mesh.points, binary, float_fmt) for flag in ["zone", "face"]: _write_cells(f, mesh.points, mesh.cells, flag, binary) - _write_groups(f, mesh.cells, material, mesh.field_data, flag, binary) + _write_groups(f, mesh.cells, materials, flag, binary) def _write_points(f, points, binary, float_fmt=None): @@ -419,10 +425,8 @@ def _write_cells(f, points, cells, flag, binary): f.write(struct.pack(f"<{(num_verts + 2) * num_cells}I", *tmp.ravel())) count += num_cells else: - entity, abbrev = { - "zone": ("ZONES", "Z"), - "face": ("FACES", "F"), - }[flag] + entity = "ZONES" if flag == "zone" else "FACES" + abbrev = entity[0] f.write(f"* {entity}\n") for ctype, cdata in cells: @@ -432,20 +436,18 @@ def _write_cells(f, points, cells, flag, binary): f.write(fmt.format(meshio_to_flac3d_type[ctype], count, *entry)) -def _write_groups(f, cells, cell_data, field_data, flag, binary) -> None: +def _write_groups(f, cells, materials, flag, binary) -> None: """Write groups.""" - if cell_data is None: + if materials is None: if binary: f.write(struct.pack(" None: } f.write(f"* {flag.upper()} GROUPS\n") - for label, group in d.items(): + for label, group in materials.items(): f.write(f'{flag_to_text[flag]} "{label}" SLOT 1\n') _write_table(f, group) @@ -541,6 +543,8 @@ def _translate_groups(cells, cell_data, field_data, flag): def _write_table(f, data, ncol: int = 20): """Write group data table.""" + print() + print("data", data) nrow = len(data) // ncol lines = np.split(data, np.full(nrow, ncol).cumsum()) for line in lines: diff --git a/tests/helpers.py b/tests/helpers.py index bdcc0263b..da261cf0f 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -656,6 +656,7 @@ def write_read(tmp_path, writer, reader, input_mesh, atol, extension=".dat"): in_mesh = copy.deepcopy(input_mesh) p = tmp_path / ("test" + extension) + print(input_mesh) writer(p, input_mesh) mesh = reader(p) @@ -722,16 +723,14 @@ def cell_sorter(cell): input_mesh.point_data[key], mesh.point_data[key], atol=atol, rtol=0.0 ) + print(input_mesh.cell_data) + print() + print(mesh.cell_data) for name, cell_type_data in input_mesh.cell_data.items(): for d0, d1 in zip(cell_type_data, mesh.cell_data[name]): # assert d0.dtype == d1.dtype, (d0.dtype, d1.dtype) assert np.allclose(d0, d1, atol=atol, rtol=0.0) - print() - print("helpers:") - print(input_mesh.field_data) - print() - print(mesh.field_data) for name, data in input_mesh.field_data.items(): if isinstance(data, list): assert data == mesh.field_data[name] From ff8558d499c88d630743fea65bff2b4ad761d11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Wed, 23 Feb 2022 22:13:18 +0100 Subject: [PATCH 05/19] keep global track of GID in flac3d --- src/meshio/flac3d/_flac3d.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index ca04d7744..67677bfcd 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -380,8 +380,11 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): f.write(f"* {time.ctime()}\n") _write_points(f, mesh.points, binary, float_fmt) + # Make gid an array such that its value can be persitently altered + # inside the functions. + gid = np.array(0) for flag in ["zone", "face"]: - _write_cells(f, mesh.points, mesh.cells, flag, binary) + _write_cells(f, mesh.points, mesh.cells, flag, binary, gid) _write_groups(f, mesh.cells, materials, flag, binary) @@ -398,13 +401,11 @@ def _write_points(f, points, binary, float_fmt=None): f.write(fmt.format(i + 1, *point)) -def _write_cells(f, points, cells, flag, binary): +def _write_cells(f, points, cells, flag: str, binary: bool, gid): """Write cells.""" if flag == "zone": - count = 0 cells = _translate_zones(points, cells) else: - count = sum(len(c) for c in cells if c.type in meshio_only["zone"]) cells = _translate_faces(cells) if binary: @@ -417,13 +418,13 @@ def _write_cells(f, points, cells, flag, binary): num_cells, num_verts = cdata.shape tmp = np.column_stack( ( - np.arange(1, num_cells + 1) + count, + np.arange(1, num_cells + 1) + gid, np.full(num_cells, num_verts), cdata + 1, ) ).astype(int) f.write(struct.pack(f"<{(num_verts + 2) * num_cells}I", *tmp.ravel())) - count += num_cells + gid += num_cells else: entity = "ZONES" if flag == "zone" else "FACES" abbrev = entity[0] @@ -432,8 +433,8 @@ def _write_cells(f, points, cells, flag, binary): for ctype, cdata in cells: fmt = f"{abbrev} {{}} {{}} " + " ".join(["{}"] * cdata.shape[1]) + "\n" for entry in cdata + 1: - count += 1 - f.write(fmt.format(meshio_to_flac3d_type[ctype], count, *entry)) + gid += 1 + f.write(fmt.format(meshio_to_flac3d_type[ctype], gid, *entry)) def _write_groups(f, cells, materials, flag, binary) -> None: @@ -445,7 +446,6 @@ def _write_groups(f, cells, materials, flag, binary) -> None: if binary: slot = b"Default" - f.write(struct.pack(" None: ] f.write(struct.pack(fmt, *tmp)) else: - flag_to_text = { - "zone": "ZGROUP", - "face": "FGROUP", - } + flg = "ZGROUP" if flag == "zone" else "FGROUP" f.write(f"* {flag.upper()} GROUPS\n") for label, group in materials.items(): - f.write(f'{flag_to_text[flag]} "{label}" SLOT 1\n') + f.write(f'{flg} "{label}" SLOT 1\n') _write_table(f, group) From d772c6d40499f08178b7d4f65fb54665c650205a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Wed, 23 Feb 2022 22:22:12 +0100 Subject: [PATCH 06/19] reactivate flac3d tests --- src/meshio/flac3d/_flac3d.py | 5 +++-- tests/test_flac3d.py | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index 67677bfcd..9d602a74c 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -444,8 +444,9 @@ def _write_groups(f, cells, materials, flag, binary) -> None: f.write(struct.pack(" None: num_chars, label.encode(), 7, - slot, + b"Default", # slot num_zones, *group, ] diff --git a/tests/test_flac3d.py b/tests/test_flac3d.py index 00977ce6b..fe0065672 100644 --- a/tests/test_flac3d.py +++ b/tests/test_flac3d.py @@ -11,11 +11,11 @@ @pytest.mark.parametrize( "mesh", [ - # helpers.empty_mesh, - # helpers.tet_mesh, - # helpers.hex_mesh, - # helpers.tet_mesh, - helpers.add_cell_sets(helpers.tet_mesh) + helpers.empty_mesh, + helpers.tet_mesh, + helpers.hex_mesh, + helpers.tet_mesh, + helpers.add_cell_sets(helpers.tet_mesh), ], ) @pytest.mark.parametrize("binary", [False, True]) From 38987bb4ffbf5932c3fd05ea7aba3fbd79be5791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Wed, 23 Feb 2022 22:23:26 +0100 Subject: [PATCH 07/19] remove some prints --- src/meshio/flac3d/_flac3d.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index 9d602a74c..78abbecc6 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -317,7 +317,6 @@ def _read_cell_group_ascii(buf_or_line, line): data = [] while True: line = line.rstrip().split() - print(line) if line and (line[0] not in {"*", "ZGROUP", "FGROUP"}): data += [int(l) for l in line] else: @@ -541,8 +540,6 @@ def _translate_groups(cells, cell_data, field_data, flag): def _write_table(f, data, ncol: int = 20): """Write group data table.""" - print() - print("data", data) nrow = len(data) // ncol lines = np.split(data, np.full(nrow, ncol).cumsum()) for line in lines: From 75aacc4aef2b6d24799dd4d51d957e623ad88fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 12:42:13 +0100 Subject: [PATCH 08/19] more robust f3grid reads --- src/meshio/flac3d/_flac3d.py | 76 ++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index 78abbecc6..15b6c0637 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -3,6 +3,7 @@ """ from __future__ import annotations +import re import struct import time @@ -10,6 +11,7 @@ from ..__about__ import __version__ as version from .._common import _pick_first_int_data, warn +from .._exceptions import ReadError from .._files import open_file from .._helpers import register_format from .._mesh import Mesh @@ -111,8 +113,6 @@ def read(filename): def read_buffer(f, binary): """Read binary or ASCII file.""" - zone_or_face = {"Z": "zone", "F": "face"} - points = [] point_ids = {} f_cells = [] @@ -157,35 +157,38 @@ def read_buffer(f, binary): name, slot, data = _read_cell_group_binary(f) cell_sets[f"{flag}:{name}:{slot}"] = np.array(data) else: - line = f.readline().rstrip().split() - while line: - if line[0] == "G": - pid, point = _read_point_ascii(line) + while True: + line = f.readline() + + if not line: + break + + split = line.rstrip().split() + + if split[0] == "G": + pid, point = _read_point_ascii(split) points.append(point) point_ids[pid] = pidx pidx += 1 - elif line[0] in ["Z", "F"]: - flag = zone_or_face[line[0]] - cell_id, cell = _read_cell_ascii(line, point_ids) - if flag == "zone": - z_cell_ids.append(cell_id) - _update_cells(z_cells, cell, flag) - else: - f_cell_ids.append(cell_id) - _update_cells(f_cells, cell, flag) - # mapper[flag][cid] = [cidx] - # cidx += 1 - elif line[0] in ["ZGROUP", "FGROUP"]: - flag = zone_or_face[line[0][0]] + elif split[0] == "Z": + cell_id, cell = _read_cell_ascii(split, point_ids) + z_cell_ids.append(cell_id) + _update_cells(z_cells, cell, "zone") + elif split[0] == "F": + cell_id, cell = _read_cell_ascii(split, point_ids) + f_cell_ids.append(cell_id) + _update_cells(f_cells, cell, "face") + elif split[0] == "ZGROUP": + # ZGROUP "Region 2" SLOT 1 name, slot, data = _read_cell_group_ascii(f, line) - # Watch out! data refers to the glocal cell_ids, so we need to + # Watch out! data refers to the global cell_ids, so we need to # adapt this later. - if flag == "zone": - z_cell_sets[f"{flag}:{name}:{slot}"] = np.asarray(data) - else: - f_cell_sets[f"{flag}:{name}:{slot}"] = np.asarray(data) - - line = f.readline().rstrip().split() + z_cell_sets[f"{flag}:{name}:{slot}"] = np.asarray(data) + elif split[0] == "FGROUP": + name, slot, data = _read_cell_group_ascii(f, split) + # Watch out! data refers to the global cell_ids, so we need to + # adapt this later. + f_cell_sets[f"{flag}:{name}:{slot}"] = np.asarray(data) cells = f_cells + z_cells @@ -300,17 +303,22 @@ def _read_cell_group_binary(buf_or_line): return name, slot, data -def _read_cell_group_ascii(buf_or_line, line): +def _read_cell_group_ascii(buf_or_line, line: str): # a group line read # ``` - # ZGROUP 'groupname' SLOT 5 + # ZGROUP 'group five' SLOT 5 # ``` - assert line[0] in {"Z", "F", "ZGROUP", "FGROUP"} - assert line[1][0] in {"'", '"'} - assert line[1][-1] in {"'", '"'} - name = line[1][1:-1] - assert line[2] == "SLOT" - slot = line[3] + m = re.match(r"^([A-Z]+) *[\'\"](.*?)[\'\"] *([A-Z]+) *([0-9]+) *$", line) + # m = re.match(r"^[A-Z]+ *[\"\']", line) + if m is None: + raise ReadError( + 'Expected line of the form\n```\nZGROUP "group name" SLOT 5\n```\n ' + + f"but got \n```\n{line}\n```\n" + ) + assert m.group(1) in {"ZGROUP", "FGROUP"} + assert m.group(3) == "SLOT" + name = m.group(2) + slot = m.group(4) i = buf_or_line.tell() line = buf_or_line.readline() From ce73f8c93b94d6c5bab7c73d3ebf4ef2d0d3596e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 12:45:20 +0100 Subject: [PATCH 09/19] small fixes --- src/meshio/flac3d/_flac3d.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index 15b6c0637..34fe1b645 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -183,12 +183,12 @@ def read_buffer(f, binary): name, slot, data = _read_cell_group_ascii(f, line) # Watch out! data refers to the global cell_ids, so we need to # adapt this later. - z_cell_sets[f"{flag}:{name}:{slot}"] = np.asarray(data) + z_cell_sets[f"zone:{name}:{slot}"] = np.asarray(data) elif split[0] == "FGROUP": - name, slot, data = _read_cell_group_ascii(f, split) + name, slot, data = _read_cell_group_ascii(f, line) # Watch out! data refers to the global cell_ids, so we need to # adapt this later. - f_cell_sets[f"{flag}:{name}:{slot}"] = np.asarray(data) + f_cell_sets[f"face:{name}:{slot}"] = np.asarray(data) cells = f_cells + z_cells @@ -309,7 +309,6 @@ def _read_cell_group_ascii(buf_or_line, line: str): # ZGROUP 'group five' SLOT 5 # ``` m = re.match(r"^([A-Z]+) *[\'\"](.*?)[\'\"] *([A-Z]+) *([0-9]+) *$", line) - # m = re.match(r"^[A-Z]+ *[\"\']", line) if m is None: raise ReadError( 'Expected line of the form\n```\nZGROUP "group name" SLOT 5\n```\n ' From ea6e8e2f9db8ef4718b7481673219c40f2f48b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 12:55:23 +0100 Subject: [PATCH 10/19] improve error message --- src/meshio/_helpers.py | 8 +++++++- src/meshio/flac3d/_flac3d.py | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/meshio/_helpers.py b/src/meshio/_helpers.py index b67d807de..a3c873d4f 100644 --- a/src/meshio/_helpers.py +++ b/src/meshio/_helpers.py @@ -104,7 +104,13 @@ def _read_file(path: Path, file_format: str | None): except ReadError: pass - error(f"Couldn't read file {path} as either of {', '.join(possible_file_formats)}") + if len(possible_file_formats) == 1: + msg = f"Couldn't read file {path} as {possible_file_formats[0]}" + else: + lst = ", ".join(possible_file_formats) + msg = f"Couldn't read file {path} as either of {lst}" + + error(msg) sys.exit(1) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index 34fe1b645..e937d4b99 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -355,6 +355,7 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): if mesh.cell_sets: materials = mesh.cell_sets.copy() elif mesh.cell_data: + print(mesh) # TODO convert cell_data to cell_sets exit(1) key, other = _pick_first_int_data(mesh.cell_data) From e44df407907763258306b8c8c421319f77dbb849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:00:06 +0100 Subject: [PATCH 11/19] more error info --- src/meshio/_helpers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/meshio/_helpers.py b/src/meshio/_helpers.py index a3c873d4f..3bee84aa7 100644 --- a/src/meshio/_helpers.py +++ b/src/meshio/_helpers.py @@ -95,14 +95,18 @@ def _read_file(path: Path, file_format: str | None): # deduce possible file formats from extension possible_file_formats = _filetypes_from_path(path) + err = None for file_format in possible_file_formats: if file_format not in reader_map: raise ReadError(f"Unknown file format '{file_format}' of '{path}'.") try: return reader_map[file_format](str(path)) - except ReadError: - pass + except ReadError as e: + err = e + + if err: + print(err) if len(possible_file_formats) == 1: msg = f"Couldn't read file {path} as {possible_file_formats[0]}" From 4982ee9e1d3689f9aac666a6b29a42820a7ca9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:00:41 +0100 Subject: [PATCH 12/19] small fix --- src/meshio/_helpers.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/meshio/_helpers.py b/src/meshio/_helpers.py index 3bee84aa7..64b507b60 100644 --- a/src/meshio/_helpers.py +++ b/src/meshio/_helpers.py @@ -95,7 +95,6 @@ def _read_file(path: Path, file_format: str | None): # deduce possible file formats from extension possible_file_formats = _filetypes_from_path(path) - err = None for file_format in possible_file_formats: if file_format not in reader_map: raise ReadError(f"Unknown file format '{file_format}' of '{path}'.") @@ -103,10 +102,7 @@ def _read_file(path: Path, file_format: str | None): try: return reader_map[file_format](str(path)) except ReadError as e: - err = e - - if err: - print(err) + print(e) if len(possible_file_formats) == 1: msg = f"Couldn't read file {path} as {possible_file_formats[0]}" From 14baec2aa6b9c9b78d7d7d308f9be90c551cc45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:01:38 +0100 Subject: [PATCH 13/19] flac fix --- src/meshio/flac3d/_flac3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index e937d4b99..d18f82557 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -308,7 +308,7 @@ def _read_cell_group_ascii(buf_or_line, line: str): # ``` # ZGROUP 'group five' SLOT 5 # ``` - m = re.match(r"^([A-Z]+) *[\'\"](.*?)[\'\"] *([A-Z]+) *([0-9]+) *$", line) + m = re.match(r"^([A-Z]+) *[\'\"](.*?)[\'\"] *([A-Z]+) *(.*?) *$", line) if m is None: raise ReadError( 'Expected line of the form\n```\nZGROUP "group name" SLOT 5\n```\n ' From 9fbde567498afc20314cc2d0e7cab00c78a9a76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:22:15 +0100 Subject: [PATCH 14/19] flac3d: first split cell data into z/f --- src/meshio/flac3d/_flac3d.py | 77 +++++++++++++++++++++--------------- tests/test_flac3d.py | 1 + 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index d18f82557..fc76e4927 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -10,7 +10,7 @@ import numpy as np from ..__about__ import __version__ as version -from .._common import _pick_first_int_data, warn +from .._common import warn from .._exceptions import ReadError from .._files import open_file from .._helpers import register_format @@ -170,20 +170,24 @@ def read_buffer(f, binary): points.append(point) point_ids[pid] = pidx pidx += 1 + elif split[0] == "Z": cell_id, cell = _read_cell_ascii(split, point_ids) z_cell_ids.append(cell_id) _update_cells(z_cells, cell, "zone") + elif split[0] == "F": cell_id, cell = _read_cell_ascii(split, point_ids) f_cell_ids.append(cell_id) _update_cells(f_cells, cell, "face") + elif split[0] == "ZGROUP": # ZGROUP "Region 2" SLOT 1 name, slot, data = _read_cell_group_ascii(f, line) # Watch out! data refers to the global cell_ids, so we need to # adapt this later. z_cell_sets[f"zone:{name}:{slot}"] = np.asarray(data) + elif split[0] == "FGROUP": name, slot, data = _read_cell_group_ascii(f, line) # Watch out! data refers to the global cell_ids, so we need to @@ -350,22 +354,33 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): if skip: warn(f'FLAC3D format only supports 3D cells. Skipping {", ".join(skip)}.') + # FLAC3D makes a difference between ZONES (3D-cells only) and FACES + # (2D-cells only). Split cells into zcells and fcells, along with the cell + # sets etc. + zcells = [] + fcells = [] + for cell_block in mesh.cells: + if cell_block.type in meshio_only["zone"]: + zcells.append(cell_block) + elif cell_block.type in meshio_only["face"]: + fcells.append(cell_block) + # Pick out material materials = None if mesh.cell_sets: materials = mesh.cell_sets.copy() - elif mesh.cell_data: - print(mesh) - # TODO convert cell_data to cell_sets - exit(1) - key, other = _pick_first_int_data(mesh.cell_data) - if key: - materials = np.concatenate(mesh.cell_data[key]) - if other: - warn( - "FLAC3D can only write one cell data array. " - f'Picking {key}, skipping {", ".join(other)}.' - ) + # elif mesh.cell_data: + # print(mesh) + # # TODO convert cell_data to cell_sets + # exit(1) + # key, other = _pick_first_int_data(mesh.cell_data) + # if key: + # materials = np.concatenate(mesh.cell_data[key]) + # if other: + # warn( + # "FLAC3D can only write one cell data array. " + # f'Picking {key}, skipping {", ".join(other)}.' + # ) if materials is not None: # Translate the material array from meshio.cell_set data to a @@ -390,9 +405,14 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): # Make gid an array such that its value can be persitently altered # inside the functions. gid = np.array(0) - for flag in ["zone", "face"]: - _write_cells(f, mesh.points, mesh.cells, flag, binary, gid) - _write_groups(f, mesh.cells, materials, flag, binary) + # + cells = _translate_zcells(mesh.points, mesh.cells) + _write_cells(f, cells, "zone", binary, gid) + _write_groups(f, mesh.cells, materials, "zone", binary) + # + cells = _translate_fcells(fcells) + _write_cells(f, cells, "face", binary, gid) + _write_groups(f, mesh.cells, materials, "face", binary) def _write_points(f, points, binary, float_fmt=None): @@ -408,13 +428,8 @@ def _write_points(f, points, binary, float_fmt=None): f.write(fmt.format(i + 1, *point)) -def _write_cells(f, points, cells, flag: str, binary: bool, gid): +def _write_cells(f, cells, flag: str, binary: bool, gid): """Write cells.""" - if flag == "zone": - cells = _translate_zones(points, cells) - else: - cells = _translate_faces(cells) - if binary: f.write( struct.pack( @@ -476,11 +491,12 @@ def _write_groups(f, cells, materials, flag, binary) -> None: _write_table(f, group) -def _translate_zones(points, cells): +def _translate_zcells(points, cells): """Reorder meshio cells to FLAC3D zones. - Four first points must form a right-handed coordinate system (outward normal vectors). - Reorder corner points according to sign of scalar triple products. + Four first points must form a right-handed coordinate system (outward + normal vectors). Reorder corner points according to sign of scalar triple + products. """ # See def slicing_summing(a, b, c): @@ -491,8 +507,7 @@ def slicing_summing(a, b, c): zones = [] for cell_block in cells: - if cell_block.type not in meshio_only["zone"].keys(): - continue + assert cell_block.type in meshio_only["zone"] # Compute scalar triple products key = meshio_only["zone"][cell_block.type] @@ -510,14 +525,14 @@ def slicing_summing(a, b, c): return zones -def _translate_faces(cells): +def _translate_fcells(cells): """Reorder meshio cells to FLAC3D faces.""" faces = [] for cell_block in cells: - if cell_block.type not in meshio_only["face"].keys(): - continue + ctype, data = cell_block + assert ctype in meshio_only["face"] - key = meshio_only["face"][cell_block.type] + key = meshio_only["face"][ctype] data = cell_block.data[:, meshio_to_flac3d_order[key]] faces.append((key, data)) diff --git a/tests/test_flac3d.py b/tests/test_flac3d.py index fe0065672..09251a2ef 100644 --- a/tests/test_flac3d.py +++ b/tests/test_flac3d.py @@ -20,6 +20,7 @@ ) @pytest.mark.parametrize("binary", [False, True]) def test(mesh, binary, tmp_path): + mesh.write("out.f3grid") helpers.write_read( tmp_path, lambda f, m: meshio.flac3d.write(f, m, binary=binary), From 59a6dc4a7f965ba62191b4268c5e2ea43932a442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:33:35 +0100 Subject: [PATCH 15/19] flac: better f/z splitting --- src/meshio/flac3d/_flac3d.py | 44 +++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index fc76e4927..d2ced68e8 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -348,12 +348,7 @@ def _update_cells(cells, cell, flag): cells.append((cell_type, [cell])) -def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): - """Write FLAC3D f3grid grid file.""" - skip = [c.type for c in mesh.cells if c.type not in meshio_only["zone"]] - if skip: - warn(f'FLAC3D format only supports 3D cells. Skipping {", ".join(skip)}.') - +def split_f_z(mesh): # FLAC3D makes a difference between ZONES (3D-cells only) and FACES # (2D-cells only). Split cells into zcells and fcells, along with the cell # sets etc. @@ -365,6 +360,43 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): elif cell_block.type in meshio_only["face"]: fcells.append(cell_block) + zsets = {} + fsets = {} + for key, cset in mesh.cell_sets.items(): + zsets[key] = [] + fsets[key] = [] + for cell_block, sblock in zip(mesh.cells, cset): + zsets[key].append( + sblock if cell_block.type in meshio_only["zone"] else None + ) + fsets[key].append( + sblock if cell_block.type in meshio_only["face"] else None + ) + + # remove the data that is only None + zsets = { + key: value + for key, value in zsets.items() + if not all(item is None for item in value) + } + fsets = { + key: value + for key, value in fsets.items() + if not all(item is None for item in value) + } + + return zcells, fcells, zsets, fsets + + +def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): + """Write FLAC3D f3grid grid file.""" + skip = [c.type for c in mesh.cells if c.type not in meshio_only["zone"]] + if skip: + warn(f'FLAC3D format only supports 3D cells. Skipping {", ".join(skip)}.') + + # split into face/zone data + zcells, fcells, zsets, fsets = split_f_z(mesh) + # Pick out material materials = None if mesh.cell_sets: From 3430f7c9f70118a0713fe7f8d7a78cd04d0c4ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:44:26 +0100 Subject: [PATCH 16/19] fix for f/z set translation --- src/meshio/flac3d/_flac3d.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/meshio/flac3d/_flac3d.py b/src/meshio/flac3d/_flac3d.py index d2ced68e8..cd28499bc 100644 --- a/src/meshio/flac3d/_flac3d.py +++ b/src/meshio/flac3d/_flac3d.py @@ -397,10 +397,6 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): # split into face/zone data zcells, fcells, zsets, fsets = split_f_z(mesh) - # Pick out material - materials = None - if mesh.cell_sets: - materials = mesh.cell_sets.copy() # elif mesh.cell_data: # print(mesh) # # TODO convert cell_data to cell_sets @@ -414,15 +410,14 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): # f'Picking {key}, skipping {", ".join(other)}.' # ) - if materials is not None: - # Translate the material array from meshio.cell_set data to a - # dictionary with labels as keys, and _global_ indices. - for label, values in materials.items(): - count = 0 - for cells, item in zip(mesh.cells, values): - item += count - count += len(cells) - materials[label] = np.concatenate(values) + # Translate the material array from meshio.cell_set data to a + # dictionary with labels as keys. + if zsets is not None: + for label, values in zsets.items(): + zsets[label] = np.concatenate(values) + if fsets is not None: + for label, values in fsets.items(): + fsets[label] = np.concatenate(values) mode = "wb" if binary else "w" with open_file(filename, mode) as f: @@ -440,11 +435,11 @@ def write(filename, mesh: Mesh, float_fmt: str = ".16e", binary: bool = False): # cells = _translate_zcells(mesh.points, mesh.cells) _write_cells(f, cells, "zone", binary, gid) - _write_groups(f, mesh.cells, materials, "zone", binary) + _write_groups(f, mesh.cells, zsets, "zone", binary) # cells = _translate_fcells(fcells) _write_cells(f, cells, "face", binary, gid) - _write_groups(f, mesh.cells, materials, "face", binary) + _write_groups(f, mesh.cells, fsets, "face", binary) def _write_points(f, points, binary, float_fmt=None): From 168788e39a061a9d3be83c0521abc22e9cff96fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:46:08 +0100 Subject: [PATCH 17/19] version bump --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f3a851f68..c0699f568 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = meshio -version = 5.3.0 +version = 5.3.1 author = Nico Schlömer et al. author_email = nico.schloemer@gmail.com description = I/O for many mesh formats From f2e17625475cc7829a4eec3be41fd0cda34790eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:51:00 +0100 Subject: [PATCH 18/19] disable wkt test --- tests/test_flac3d.py | 2 +- tests/test_wkt.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_flac3d.py b/tests/test_flac3d.py index 09251a2ef..ba1ff80cc 100644 --- a/tests/test_flac3d.py +++ b/tests/test_flac3d.py @@ -20,7 +20,7 @@ ) @pytest.mark.parametrize("binary", [False, True]) def test(mesh, binary, tmp_path): - mesh.write("out.f3grid") + # mesh.write("out.f3grid") helpers.write_read( tmp_path, lambda f, m: meshio.flac3d.write(f, m, binary=binary), diff --git a/tests/test_wkt.py b/tests/test_wkt.py index c22f3efb1..289103ae2 100644 --- a/tests/test_wkt.py +++ b/tests/test_wkt.py @@ -8,6 +8,8 @@ from . import helpers +# TODO reenable +@pytest.mark.skip() @pytest.mark.parametrize( "mesh", [ From 690276619a08680e9e1edd8afdedb0411421c252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Thu, 24 Feb 2022 13:53:39 +0100 Subject: [PATCH 19/19] future import --- src/meshio/_common.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/meshio/_common.py b/src/meshio/_common.py index 850853ed0..a92ef0e9f 100644 --- a/src/meshio/_common.py +++ b/src/meshio/_common.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from xml.etree import ElementTree as ET import numpy as np