Skip to content

Fortran ​

Exodus series and pipeline Version 2 ​

type(mio_exodus_series) owns a netCDF-enabled series. Call %create(path), %write_points_cells(mesh), then %write_data(time, mesh) per step; %flush(), %finalize(), %finalized() and %num_steps() expose the lifecycle. %free() is explicit and idempotent, like mesh handles. Mutators accept optional stat/errmsg; missing netCDF is a named failure. Point/side sets and attributes belong to the fixed grid, not a step. See Exodus.

Existing pipeline status/report routines accept Version 2 JSON with per-step Inputs and terminal {key}/{part} outputs. No bind(C) layout changed; transient sequence documents remain Version 1.

Pipeline reports ​

Structured pipeline reports use call mio_pipeline_run_file_report(path, report, stat, errmsg) or mio_pipeline_run_json_report(text, report, ...), plus the corresponding mio_sequence_pipeline_run_*_report routines. The allocatable report string is JSON with steps and warnings; the native handle is released automatically. Failure returns an empty string and follows the existing stat/errmsg convention. See pipeline reports.

MED named meshes ​

call mio_med_mesh_names(path, names, stat, errmsg) returns an allocatable character array in file link order. call mio_med_read_named(path, name, mesh, time_step=0, lenient=.false., stat=stat, errmsg=errmsg) replaces the output mesh only after success; free it with call mesh%free(). call mio_med_write_multi(path, meshes, names, version='4.1.0', stat=stat, errmsg=errmsg) writes an array of borrowed meshes with one unique name per mesh. These APIs require HDF5 and expand ordinary nodal/element profiles with NaN fill. See MED.

call m%write('cloud.pcd', codec='lzf') selects PCD compressed binary without an optional codec library; explicit ASCII or non-PCD combinations fail. encoding, codec and float_format are optional keyword arguments appended after the existing status arguments; encoding='binary' on a .frd writes CalculiX's binary result layout. Parameterized glTF uses type(mio_gltf_options) :: opts and call m%write_gltf('colored.glb', options=opts); set opts%color_by, %cmap, %up_axis (0 auto, 1 Z, 2 Y, 3 X) and %scale as needed. %component is 1-based and only used with %component_set=.true.; %vmin_set/%vmax_set control optional bounds. Native reads also support XDMF2/3 DataItem references and Netgen names/periodic arrays; Netgen periodic arrays are numeric field data with 1-based file node ids, and .vol.gz requires zlib.

XDMF series write_points_cells(mesh) now stores the mesh's fixed point/cell/side regions once with the shared topology. Existing step, flush and append calls retain them without new arguments. See shared named regions.

The shared native reader supports multiple pieces without welding and appended raw/base64 arrays in vtu, vtp, vts, vtr and vti, with UInt32/UInt64 headers and either byte order. VTP/VTS/VTR/VTI writers remain inline; optional codecs follow the C library build. Legacy vtk also reads structured points, structured grids and rectilinear grids in ASCII and big-endian binary through the existing read API. See formats.

meshio++ ships a modern object-oriented Fortran 2008 module, meshioplusplus, layered on the C API via ISO_C_BINDING — in the HDF5/PETSc style, aimed at Fortran HPC codes:

fortran
use meshioplusplus
type(mio_mesh) :: m

call m%read("bracket.msh")
print *, m%num_points(), "points,", m%num_cell_blocks(), "cell blocks"
call m%write("bracket.vtu")
call m%free()

Building ​

sh
build/configure.sh --fortran --build     # implies --c-api
cmake --install build/cpp-release --prefix /opt/meshioplusplus

installs libmeshioplusplus_fortran.so next to libmeshioplusplus.so, plus include/meshioplusplus/fortran/meshioplusplus.mod and the module source meshioplusplus.f90. Compile and link:

sh
gfortran my_solver.f90 -I /opt/meshioplusplus/include/meshioplusplus/fortran \
  -L /opt/meshioplusplus/lib -lmeshioplusplus_fortran -lmeshioplusplus -o my_solver

.mod files are compiler-specific

A .mod compiled by gfortran N is unreadable by gfortran N±2, ifort, or flang. If your compiler rejects the installed .mod, recompile the module from the installed meshioplusplus.f90 with your own compiler and link the same libraries — that is exactly why the source is installed (the HDF5 approach). CMake consumers can instead find_package(meshioplusplus) and link meshioplusplus::meshioplusplus_fortran from the same build.

Array layout and 1-based indexing ​

The C core stores points as row-major (num_points, dim) and connectivity as (num_cells, nodes_per_cell). Because Fortran is column-major, the same memory is naturally the Fortran arrays

fortran
real(real64)   :: points(dim, num_points)     ! points(:, i) = coordinates of point i
integer(int64) :: conn(nodes_per_cell, num_cells)

so nothing is ever transposed. Node indices are 1-based in this module; the ±1 shift happens inside the copying setters/getters (where a copy is made anyway):

fortran
call m%set_points(points)                     ! copies
call m%add_cell_block("tetra", conn)          ! copies, shifts to the core's 0-based
call m%get_cell_block(1, rconn)               ! allocates, copies, shifts back to 1-based

Vector data uses the same rule — a per-point field of c components is data(c, num_points) in Fortran and round-trips as the C API's (num_points, c).

Zero-copy borrows exist where no index shift is needed:

fortran
real(real64), pointer :: p(:, :)
call m%points_ptr(p)     ! p(dim, num_points) aliases mesh memory --
                         ! valid until the next mutating call or m%free()

Error handling ​

Every fallible procedure takes optional stat and errmsg arguments:

fortran
integer :: ierr
character(:), allocatable :: msg

call m%read("missing.vtu", stat=ierr, errmsg=msg)
if (ierr /= 0) print *, "read failed: ", msg

If stat is absent and the call fails, the message is printed and the program error stops — convenient for straight-line tools, pass stat when you need to recover. mio_error_message() returns the most recent failure message on the calling thread.

API summary ​

m%region_adjacency([regions(:)]) returns conforming shared facets; m%find_interface(region_a, region_b [, mesh_b, mode, master, tolerances]) matches Cell-region boundaries and returns an owned facet mesh plus 0-based local-facet Side entries; m%contact_pairs(slave_points, master_cells [, master_mesh, tolerance, require_complete]) projects a Point region and returns copied arrays; and m%split_interface(side [, add_cohesive, counts]) splits point fans along a Side region. Global point/cell ids in Fortran results are 1-based, but local-facet ordinals remain 0-based. See interfaces and contact.

Lifecyclem%create() (implicit in read/setters), m%read(path [, format]), m%write(path [, format]), m%free(), m%is_valid()
Buildingm%set_points(points), m%add_cell_block(type, conn) (int32 or int64), m%add_point_data(name, data) (rank-1 or rank-2), m%add_cell_data(name, data) (once per block, in order), m%add_field_data(name, data)
Countsm%num_points(), m%point_dim(), m%num_cell_blocks(), m%num_point_data(), m%num_cell_data(), m%num_field_data(), m%cell_data_num_blocks(name)
Cell blocks (1-based)m%cell_block_type(i), m%cell_block_num_cells(i), m%cell_block_nodes_per_cell(i), m%cell_block_is_ragged(i), m%cell_block_is_polyhedron(i), m%get_cell_block(i, conn)
Ragged cell blocksCells of varying size, carried as a 1-based flat CSR pair or triple (see Polyhedra and ragged cells). Building: m%add_polygon_block(type, row_offsets, nodes), m%add_polyhedron_block(type, cell_offsets, face_offsets, nodes). Reading: m%get_polygon_block(i, row_offsets, nodes), m%get_polyhedron_block(i, cell_offsets, face_offsets, nodes). The offsets are shifted to 1-based along with the node ids, because they index a Fortran array — so cell c is nodes(row_offsets(c) : row_offsets(c + 1) - 1), and face f of cell c is nodes(face_offsets(j) : face_offsets(j + 1) - 1) with j = cell_offsets(c) + f - 1
Data (copies, real64)m%get_points(points), m%get_point_data(name, data) (rank-1 or rank-2), m%get_cell_data(name, block, data), m%get_field_data(name, data); names via m%point_data_name(i) etc. (sorted order)
Zero-copym%points_ptr(p)
Isogeometricinfo%iga_refine([subdivisions, elevate, sample_subdivisions, trim_samples, edges, trim, refined]), info%iga_sample(...) (each returns the sampled mio_mesh; refined / mapped receive the model for m%write_with_info) and info%iga_evaluate(patch, params(d, N) [, derivatives, trim_samples]) returning a mio_iga_evaluation with points() (space dim, N), derivatives(), normals(), inside(), field(k) and num_fields(); see isogeometric layer
Operationsm%extract_surface([record_parent_ids]), m%extract_skin([linearize]), m%normals([point_normals, cell_normals, weight, split_angle, record_parent_ids, region, num_added_points, …]) (point and cell normals of a surface, optionally splitting vertices at creases — see normals), m%feature_edges([feature_angle, feature, boundary, non_manifold, inconsistent, region, num_feature, …]) (the sharp/open/non-manifold/inconsistent edges as a line mesh — see feature edges), m%tessellate([levels, curved, fields, record_stencil, num_source_points, num_source_cells, num_curved_source_cells, num_pass_through_source_cells, num_points, num_cells, num_boundary_facets, num_interior_facets, num_facets_with_bad_count, stat, errmsg]) (isoparametric tessellation of curved quadratic cells into triangles and tetrahedra with tessellate:* provenance data; levels is int64 and defaults to 2 — see tessellation), m%segment([feature_angle, normal_tolerance, curvature_tolerance, min_patch_cells, region, prefix, num_patches, num_merged, …]) (smooth patches — see segment), m%fit_primitives([patch_data, tolerance, relative, min_inlier_fraction, types, max_iterations, num_plane, …, num_unfitted]) (types is the bit mask 2**type, 62 for all five — see fit primitives), m%fit_cad([step_path, tolerance, exact_edges, types, mesh_deflection, max_demotion_rounds, valid, volume_ratio, max_deviation, num_faces, num_faceted_faces, num_demoted, freeform, freeform_degree, freeform_max_control, num_bspline, freeform_split, fillets, threads, num_split_patches, num_fillet, num_helical, num_variable_fillet, num_blend, num_rounded_thread, num_vertex_blend, num_chamfer, num_conic_blend, num_tapered_thread, num_variable_pitch_thread]) (the fitted patches as a CAD solid with analytic faces, and rolling-ball fillet and blend, helical thread and B-spline faces for what no primitive explains, the logical freeform, freeform_split, fillets and threads on by default; needs a library built with OpenCASCADE, which the free function mio_has_occt() reports — see fit CAD), m%hausdorff_distance(other [, face_samples, region_a, region_b, a_to_b, b_to_a, …]) (→ real(real64); see Hausdorff distance), m%edit_regions(op, inputs(:) [, output, kind, dim, tag, keep_inputs]) and m%remove_region(index) (1-based; see editing regions), call m%match_periodic_nodes(slave, master, slave_ids, master_ids [, translate(3), matrix(16), atol, require_complete, unmatched, num_fixed, max_residual]) (1-based ids; matrix row-major — see periodic node pairs), m%attach_quality(), m%quality_counts(...), m%check_quality([spec, max_inverted, max_degenerate, num_checks, num_failed, summary]) (the quality gate; → logical passed), m%reorder(method [, node_perm]) (method "rcm"/"morton"/"hilbert"; node_perm is an optional allocatable 1-based old→new node permutation), m%compute_bandwidth(), and comparison: m%equals(other [, atol, rtol, unordered]) (logical) / m%diff(other [, atol, rtol, unordered]) (verdict 0=identical / 1=equal within tolerance / 2=different), and merging: the module-level mio_merge(meshes [, weld, atol, source_tag, data_policy, drop_duplicate_cells]) (an array of type(mio_mesh) → a new type(mio_mesh); data_policy is "intersection" or "fill"), and the editing/stats bundle: m%transform(matrix(16) [, rotate_vector_data]), m%clean([weld, atol, remove_orphans, drop_degenerate, drop_duplicate_cells, points_welded, ...]), m%crop_bbox(lo, hi [, mode, record_ids]) / m%crop_plane(point, normal [, mode, record_ids]) (mode "all"/"any") / m%crop_predicate(array [, compare, value, record_ids]) (v9.25.0; keeps the cells whose scalar cell_data value satisfies a comparison — "<", "<=", ">", ">=", "==", "!=", the same vocabulary m%refine's where_op uses. A non-finite cell value never matches. There is deliberately no mode: a per-cell value has nothing for an all/any rule to reduce), m%split(by [, tag_name, keys]) (→ an allocatable array of type(mio_mesh)), m%stats() (→ a type(mio_stats_report) of scalar geometric measures), and m%convert_cells(mode [, record_parent_ids, point_map]) (mode "linearize"/"simplexify"/"elevate"; point_map is an optional allocatable 1-based input→output point map, 0 where the point was pruned), m%subdivide([record_parent_ids]) (polyhedral refinement: one polyhedral child per face of every eligible 3D cell, connected to a new interior point — no per-type template table, tabulated types and existing polyhedron blocks handled uniformly, and automatically conforming, unlike m%refine; → a new type(mio_mesh) with no point map, unlike m%convert_cells, since subdivide never prunes or renumbers a point; fails through stat/errmsg naming the cell when its faces are not a closed orientable surface), m%agglomerate([target_group_size, stat, errmsg, merge_coplanar_faces, coplanar_angle, min_sphericity, num_faces_merged, num_rejected]) (the options and counts of agglomerate, through mio_agglomerate_ex) (polyhedral coarsening, the many-to-one counterpart to m%subdivide: greedy seed-and-grow over the mesh's shared-face dual, absorbing face-adjacent neighbours by accumulated shared-face area until each group reaches target_group_size (default 8) members, then emitting one polyhedron per group whose faces are exactly its external boundary — conserving volume exactly, since internal faces are dropped rather than re-triangulated; → a new type(mio_mesh) with, like m%subdivide, no point map at all — the flat input→output cell map the underlying mio_agglomerate_result carries is a documented flat-binding gap here, not exposed on this wrapper, since an agglomerated cell's output index is a function of which group it joined rather than a per-block quantity Fortran's existing point_map/allocatable-array idioms fit; fails through stat/errmsg naming the face when the input is non-manifold), and m%refine(levels [, record_parent_ids, point_map, cells, region, where_array, where_op, where_value, closure, record_levels, record_hierarchy]) (subdivision into same-type children; levels defaults to 1. At most one of cells (global block-major, 1-based here), region and where_array + where_op + where_value selects a subset to refine, and the hanging nodes that leaves are resolved by closure — "redgreen" keeps the extra refinement local, "propagate" works for every cell type but reaches the whole edge-connected component. record_levels attaches the refine:level array; record_hierarchy attaches refine:cell_id/refine:parent_id — the persistent parent/child hierarchy a multigrid caller resolves across the sequence of meshes it keeps, and it also forces refine:entity to be attached even when the closure leaves no hanging node), and partitioning: m%partition(nparts [, method, imbalance, mode, seed, record_ids, ghost_layers, weights_key]) (→ an allocatable array of exactly nparts type(mio_mesh) pieces, part id = index − 1; method "sfc"/"kahip"/"auto") and m%partition_labels(nparts [, method, imbalance, mode, seed, weights_key]) (→ a flat block-major integer(int64) array of part ids in [0, nparts) — the values are ids, not indices, so no 1-based shift applies), and smoothing: m%smooth(method, iterations [, lambda, mu, fix_boundary, preserve_features, feature_angle, guard_inversion, nodes_moved, max_displacement, skipped_inversion, frozen]) (method "taubin"/"laplacian"; a negative lambda means the method's own default; frozen an optional 1-based integer(int64) point-id array, shifted to 0-based across the ABI; → a new type(mio_mesh) with the same cells and data and only its point coordinates moved, plus optional run-summary out-arguments), and green-element undo: the module-level mio_undo_green(coarse, fine [, num_groups_undone, num_cells_removed, stat, errmsg]) (a TWO-mesh function like mio_interpolate/mio_merge, not type-bound; fine must carry refine:cell_id/refine:parent_id/refine:level, i.e. must come from m%refine(..., record_hierarchy=.true., record_levels=.true.), and coarse must be the mesh that call was run on) → a new type(mio_mesh) restoring fine's transitional (green) sibling groups to the row coarse already has for them verbatim — a lookup, not a reconstruction, since refine never renumbers or prunes points — with the six reserved refine:* arrays always dropped and only a single-pass (levels=1) hierarchy supported; the per-block cell maps are a documented flat-ABI gap here, like m%agglomerate's, and mass-preserving field transfer: the module-level mio_conservative_interpolate(source, target [, arrays, default_value, on_conflict, stat, errmsg]) (a TWO-mesh function like mio_interpolate/mio_merge, not type-bound; unlike mio_interpolate, an omitted/zero-sized arrays means every source point_data AND cell_data array — one algorithm regardless of location) → a new type(mio_mesh) conserving sum(value * measure) over the region the two meshes share (both meshes simplexified first, accepting ragged/polyhedron blocks for free), unlike mio_interpolate's pointwise sampling; deliberately no extrapolate argument, since a silent fallback would break that guarantee, and the two cutters: m%slice(origin, normal [, record_parent_ids]) (the planar cross-section, one topological dimension below the cut cells) and its data-driven sibling m%isosurface(array, isovalues(:) [, component, record_parent_ids]) (the level set of a scalar point_data array; component defaults to −1 meaning the row magnitude, several isovalues land in one mesh tagged per cell with iso:value/iso:index, and a cell_data name fails through stat — convert it with m%data_cell_to_point first), and field differentiation: m%gradient(array [, op, method, location, output, component, overwrite, num_skipped, num_fallback]) (the gradient, divergence or curl of a point_data array; op "gradient"/"divergence"/"curl", method "green-gauss"/"least-squares", location "cell"/"point". component defaults to −1 meaning every component — the opposite of m%isosurface's sentinel. Cells that cannot be differentiated yield NaN and are reported in num_skipped; least-squares cells with a degenerate neighbourhood fall back to Green-Gauss and are reported in num_fallback), and second derivatives: m%hessian(array [, method, location, output, overwrite, num_skipped, num_fallback]) (the Hessian of a scalar point_data field -- m%gradient's companion one order further, a composition of TWO m%gradient calls, not a new kernel: the field is differentiated once at location="point" regardless of the caller's own location, then that (n,3) gradient is differentiated again with the default operator, producing (n,9) -- the flattened row-major 3x3 Hessian. method is forwarded to BOTH internal passes. A field that is at most linear has an exactly zero Hessian everywhere -- the one mesh-shape-independent guarantee; a genuinely quadratic field's composition is exact on a structured/symmetric mesh away from its own boundary and approximate on an irregular mesh (see doc/hessian.md). Input must have exactly one component -- a vector field's Hessian is a separate quantity per component, rejected naming the per-component workaround. A curvature-driven refinement indicator needs no new procedure: norm(...) in m%data_calc on the 9-component output is exactly its Frobenius norm, ready for m%refine's where_array/where_op/where_value selector), and error estimation: m%estimate_error(array [, method, marking, marking_value, output, marked, overwrite, global_error, num_skipped, num_marked]) (the Zienkiewicz-Zhu recovery-based error indicator of a point_data field — a composition of m%gradient with the point↔cell averaging round trip, not a new kernel — plus optional marking; method "zz", marking "none"/"absolute"/"fraction"/"dorfler". error:zz is always attached; error:marked too when marking is not "none", so m%refine's own where_array/where_op/where_value needs no change to consume it. Cells that cannot be evaluated read NaN in error:zz and 0 in error:marked, counted in num_skipped and excluded from global_error/num_marked), and field integration: m%data_integrate([arrays, keys, totals, means, domain_measures, num_nans]) (→ an allocatable array of type(mio_field_integral_info){num_components, num_cells, num_skipped}, one per requested cell_data array (omitted arrays = every array); the four optional out-arguments are flat, array-major, per-component buffers, mesh_regions's own flat-buffer idiom — gradient's integration counterpart, a cell-measure-weighted total/mean, never a fallback weight of 1 for an unmeasurable cell or non-finite value) plus m%data_integrate_region(array_name [, keys, totals, means, domain_measures, num_nans]) for one named array's per-region breakdown — split into a second call rather than nesting array × region × component into one, mirroring m%data_integrate's own shape but one array at a time; every named Cell region present gets its own independent entry (not a partition: a cell in two regions contributes fully to both), and a point_data-only name fails through stat/errmsg naming m%data_point_to_cell as the fix
Data operationsAct on the mesh's data arrays; the geometry is never modified (see data operations). m%data_drop(location, names [, ignore_missing]), m%data_keep(...), m%data_rename(location, from, to), m%data_point_to_cell([names, suffix]), m%data_cell_to_point([names, weight, suffix]), m%data_calc(expression, output_name [, location, overwrite]), m%data_condition(location [, names, mode, lo, hi, scope, nan_policy, nan_replacement, suffix]), m%tensor_invariants(location [, names, outputs, prefix, suffix, overwrite]) — each returns a new type(mio_mesh) — and m%data_info([keys]) → an allocatable array of type(mio_data_array_info). location is MIO_DATA_POINT / _CELL / _FIELD; the other enumerations are MIO_WEIGHT_*, MIO_COND_*, MIO_SCOPE_*, MIO_NAN_* and MIO_TINV_* (a bitmask, OR together with ior). The module also exports STRBUF_LEN, the fixed width of the keys out-argument arrays used by m%split, m%data_info and m%regions
Named regionsNamed groups of points, cells or cell facets (see Named regions). m%regions([keys, entries]) → an allocatable array of type(mio_region_info), with the group names in keys and the flat integer(int64) entries in entries, laid out back to back in report order; m%add_region(name, kind, entries [, dim, tag]). kind is MIO_REGION_POINT / _CELL / _SIDE. Point and cell indices are shifted to Fortran's 1-based convention by the copying getter and setter; the facet column of a side region is not, matching the partition_labels rule that a value which is not an index stays as it is
Module-levelmio_convert(in, out [, in_format, out_format]), mio_version(), mio_mesh_backend(), mio_format_readable(f), mio_format_writable(f), mio_error_message(), and the settings pipeline: mio_pipeline_run_file(settings_path [, stat, errmsg]) / mio_pipeline_run_json(json_text [, stat, errmsg]) run a whole settings.json (read → operation chain → write), and mio_pipeline_has_json() reports whether the linked library carries the JSON parser (a build without it fails through stat naming -DMESHIOPLUSPLUS_WITH_JSON=ON)

Format inference is the shared registry's, the C API's: the extension picks the format, with seven exceptions: a .mesh file whose first line names an MFEM mesh is read as mfem rather than medit (v16.5.0), Z88's fixed file names (z88i1.txt, z88o2.txt, …) are z88 rather than xyz (v16.7.0), a .dat file that opens as an MSC Marc input deck is marc rather than tecplot (v16.8.0), a file named d3plot, which has no extension, is LS-DYNA's lsdyna_d3plot (v16.9.0), a file named like crashA001 (a stem, A and three or more digits, no extension) is an OpenRadioss animation file, radioss_anim (v16.11.0), a file named binout (or binout0000…) is LS-DYNA's lsdyna_binout, and one named like crashT01 (a stem, T and two digits, no extension) an OpenRadioss time-history file, radioss_th (both v16.12.0). The two content checks are for reading: a write goes by the extension alone, so a .dat is written as Tecplot and a .mesh as Medit whatever the file being replaced holds (v16.17.0), and a Marc deck is written by naming marc. Formats added to the registry (Patran, Femap and MFEM in v16.5.0; libMesh, Z88, Abaqus .fil and Radioss in v16.7.0; Marc's marc and marc_t19 and the ansys_rst_cyclic full rotor in v16.8.0; lsdyna_d3plot and nastran_op2 in v16.9.0; Tecplot's binary .plt in v16.10.0; radioss_anim, the libMesh writer and its .gz/.bz2 names in v16.11.0; lsdyna_binout and radioss_th in v16.12.0; vtx, DOLFINx's ADIOS2 .bp directories, and szplt, Tecplot SZL, in v16.13.0, when the library was built with ADIOS2 and TecIO; the radioss and marc writers in v16.17.0, the radioss_th, nastran_op2, lsdyna_binout and abaqus_fil writers after them) reach this binding with no code change; mio_format_readable/mio_format_writable report what the linked library holds.

The complete CI-tested example lives at doc/examples/fortran_example.f90; format support and the remaining limitations (side-channel metadata) are identical to the C API, which this module wraps; ragged blocks are no longer among them (see the table above). Copy-getters deliver real(real64) regardless of the stored dtype (float32/int32/int64 are converted); Fortran on Windows/MSVC is untested in v1.

m%read(path, 'gmsh') accepts non-periodic 4.0 files as well as 2.2/4.1, in ASCII or binary (4- or 8-byte producer counts). For periodic files use read_with_info / write_with_info; writes stay 4.1 (gmsh) or 2.2 (gmsh22). Info-less reads refuse $Periodic rather than lose it. See Gmsh.

Selective reads and file summaries ​

Sets↔data conversions return a new mesh through m%sets_to_data(MIO_DATA_POINT [, data_name, join_char, order, stat, errmsg]) and m%data_to_sets(MIO_DATA_CELL, key [, stat, errmsg]). Only point/cell locations are accepted. Set order defaults to native region-name order; an explicit order lists every set name once. Labels are scalar integer values, not indices: they stay zero-based with -1 for uncovered entities. See sets/data semantics.

read takes optional points_only, arrays, time_step, lenient and (last, so positional stat/errmsg callers keep working) piece and drop_ghosts, and the module-level mio_read_metadata returns a type(mio_metadata):

fortran
type(mio_mesh) :: m
type(mio_metadata) :: meta

call m%read('big.msh', points_only=.true.)
call m%read('big.msh', arrays=['u   ', 'p   '])   ! fixed-length, as Fortran requires
call m%read('run.exo', time_step=-1)              ! the last step; 0 (default) is the first

meta = mio_read_metadata('run.exo')
print *, meta%num_points, meta%num_cells
print *, meta%fell_back_to_full_read      ! .true. => the file was read in full
print *, size(meta%time_values)           ! how many steps `time_step` may name

time_step picks a step of a multi-step file (0 = the first, negative counts from the end). Out of range is an error naming the available count, never a silent clamp; meta%time_values is allocated to size 0 for a format with no time concept.

A zero-sized arrays means no arrays, which is distinct from omitting the argument (every array). Names are trimmed and NUL-terminated internally; the copies outlive the call.

meta%cell_blocks is an array of type(mio_cell_block_info), and the name lists use the exported STRBUF_LEN convention, as split and data_info do.

Transient (time-series) XDMF writing ​

type(mio_xdmf_series) is the write half of what time_step and meta%time_values expose above, and the one writer m%write() cannot express: a series is a stateful multi-call object, so it gets its own handle rather than a write argument. The grid goes out once and each solve appends a cheap step. See XDMF time series.

fortran
type(mio_mesh) :: m
type(mio_xdmf_series) :: series
integer :: k

call series%create('simulation.xdmf')          ! "HDF" by default
call series%write_points_cells(m)              ! the static grid, once
do k = 0, nsteps - 1
    call solve(m)
    call series%write_data(k*dt, m)            ! point_data/cell_data only
end do
print *, series%num_steps()
call series%finalize()                         ! free() would do this too
call series%free()
Lifecycleseries%create(path [, data_format, gzip_level]), series%free(), series%is_valid()
Writingseries%write_points_cells(mesh), series%write_data(time, mesh), series%finalize(), series%num_steps()

data_format is 'HDF' (the default; needs an HDF5-enabled build), 'XML' (everything inline in the .xdmf) or 'Binary'; gzip_level applies to 'HDF' datasets only and is negative (no compression) by default. An unknown format, or 'HDF' against a library built without HDF5, fails through stat/errmsg like every other fallible procedure.

Two things worth knowing before reading the result back:

  • the .xdmf light data is buffered until the series is finalized, so the file is only readable after series%finalize() (or series%free(), which finalizes first);
  • free() swallows a write failure during that implicit finalize — call finalize() explicitly, with stat, to see one.

Handles are freed explicitly, exactly like type(mio_mesh); there is no finalizer. Reading a finished series back is the ordinary m%read(path, time_step=k), with mio_read_metadata(path)%time_values reporting the steps.

Software rendering (v16.34.0) ​

A deterministic software rasterizer and its terminal, HTML and PNG encodings, with no display or GPU: see terminal rendering for what is drawn and every option. Options are the plain-Fortran mio_render_settings (allocatable strings, .true./.false. flags, an allocated vmin, vmax, clip_low or clip_high meaning "set", colours as integer(int64) 0xRRGGBBAA from mio_rgba(r, g, b, a)), whose defaults are the C ones.

fortran
type(mio_render_settings) :: set
type(mio_frame) :: frame
integer, allocatable :: rgba(:, :, :), png(:)

set%width = 800; set%height = 600; set%supersample = 2
set%color_by = 'temperature'; set%colorbar = .true.
allocate (set%clip_low, source=2.0_real64)
frame = mesh%render(set, stat=ierr)          ! frame%is_valid(), %width(), %height(), %range(vmin, vmax)
call frame%get_rgba(rgba)                    ! rgba(4, width, height), channels 0..255, row 1 on top
call frame%png(png)                          ! the file's bytes as 0..255 values
call frame%free()

text = mesh%render_text(set, tset)           ! tset%cols x tset%rows cells; mio_text_settings
call mesh%write_snapshot('part.png', set)    ! .png .txt .ansi .html .cast

Cut-aways (v16.36.0). set%cutaways is an allocatable real(real64) array of one or two planes, six numbers each (a point, then the normal of the side kept), and set%cutaway_tint a colour (mio_rgba); the geometry on the far side is clipped away and the back faces now in view are tinted. A length that is not 6 or 12, or a zero normal, is refused by name: set%cutaways = [0.5_real64, 0.0_real64, 0.0_real64, 1.0_real64, 0.0_real64, 0.0_real64].

Streamlines (v16.37.0). set%streamlines names a vector point array to follow; set%stream_seeds (default 40), set%stream_length (model diagonals each way, default 0.5) and set%stream_color (mio_rgba) shape the lines. An unknown array or a seed count outside 1 to 10000 is refused by name: set%streamlines = 'velocity'.

frame%get_cell_ids(ids) gives the input cell at each pixel (ids(width, height), -1 for none), frame%num_notes() and frame%note(i) the colour range, ticks and keys, and frame%text(tset) encodes a frame whose size fits the encoding's cell grid. mio_detect_color_depth(no_color=, color_term=, term=) returns a MIO_COLOR_* constant. The MIO_SHADING_*, MIO_EDGES_*, MIO_SCALE_*, MIO_DIAGNOSTIC_*, MIO_ENCODING_*, MIO_COLOR_* and MIO_TEXT_* constants name the enum values. Without stat= a failure stops the program with the library's message, as everywhere in this module.

v9.1.0 additions ​

  • m%read(..., lenient=.true.) — see doc/selective_read.md.
  • m%read(path, piece=k) — keep one piece of a partitioned file (VTKHDF partitions or composite blocks): 0 is the first, negative counts from the end, omitted merges every piece into one mesh with one cell region per piece; out of range fails, never clamps. On a type(mio_read_opts_t) set piece and piece_set = 1 (piece_set is what selects; a zeroed struct merges). See doc/selective_read.md.
  • m%read(path, drop_ghosts=.true.) — remove the ghost cells (halo) of a partitioned .pvtu, .pvtp or .pvd (every cell with a vtkGhostType bit set, and the points only they used); omitted keeps them, and every other reader ignores it. On a type(mio_read_opts_t) set drop_ghosts = 1 (it took the fifth former reserved slot; size unchanged). See doc/selective_read.md.
  • XDMF series: s%flush(), s%finalized(), and s%create(..., mode='append', auto_flush=...).

Gap, deliberate: four Python-only formats — pmsh, zarr, cae and usd (v10.35.0, the physics-ML data path) — are registered in the Python layer only, not in the shared C++ dispatch registry, so this surface cannot read or write them; see formats. And there is no Fortran counterpart to the solver-array write_data overload. An array of derived types holding interop pointers is a poor fit for Fortran, and a Fortran solver already holds an mio_mesh handle it can add_point_data into before write_data. MdpaInfo is reached through mio_format_info (below).

Format side channels ​

Gmsh read_with_info keeps periodic links and 4.1 bounding-entity tags. info%gmsh_count(MIO_GMSH_PERIODIC) counts links; gmsh_tags(section, i) copies bounding tags (MIO_GMSH_BOUNDING_ENTITIES) or [dimension, slave entity tag, master entity tag] (MIO_GMSH_PERIODIC). gmsh_affine(i) copies 0 or 16 coefficients; gmsh_pairs(i) copies (2, N) slave/master 1-based point rows, keeping ordering and duplicates. Item indices are 1-based, entity tags stay raw file ids. write_with_info(path, info, 'gmsh') or 'gmsh22' restores the links; mesh operations do not remap them, so rebuild info after point/topology edits.

MFEM NURBS meshes (v16.40.0) keep their patches the same way: read_with_info(path, info, 'mfem'), then info%nurbs_count(MIO_NURBS_PATCHES), nurbs_degrees(p), nurbs_knots(p, dir), nurbs_control_points(p) ((space dimension, points), direction 1 fastest), nurbs_weights(p), nurbs_field(p, k) ((components, points)), nurbs_field_name(k), nurbs_int(section, i, field) and nurbs_ids(section, field), which copies a MIO_NURBS_TOPOLOGY or MIO_NURBS_CELL_MAP index array as (columns, rows) (a one-dimensional one as (1, n)). Patches, directions and fields are 1-based, and so are the vertices, patches and knot spans the arrays hold; attributes and the edges' knot-vector ids stay as the file spelled them. write_with_info(path, info, 'mfem') writes the patches back as an MFEM NURBS mesh (MFEM).

Kratos CAD JSON (and, with OpenCASCADE, STEP and IGES) keep their B-rep the same way: read_with_info(path, info, 'cad_json'), then info%brep_count(MIO_BREP_FACES), brep_int(section, i, field), brep_values(section, i, field) (a Float64 array: weights, knots, an active range), brep_points(section, i, field) ((3, points)), brep_ids(section, i, field) (an Int64 array: control-point ids, a brep's faces) and brep_string(section, i, field), with the MIO_BREP_* sections and the field numbers of the C API. Items are 1-based, and so is an int field that indexes another section (a face's patch, first loop or first trim; a loop's or trim's face; a trim's loop; an edge's or vertex's first topology entry; 0 for none), and a brep's face, edge and vertex lists; ids, flags, counts and trim indices stay as the file spells them. write_with_info(path, info, 'cad_json') writes the B-rep back as CAD JSON.

MDPA sub-model-part membership is available through info%mdpa_ids(MIO_MDPA_SUBMODELPARTS, i, field): field 0 is tables, field 1 geometry ids and field 2 constraint ids. The item index i is 1-based, but these values are raw file ids (no index shift), retain ordering and duplicates and are not remapped by mesh operations. write_with_info restores nested membership-only parts; constraints remain opaque.

call m%read_with_info(path, info [, format, lenient, stat, errmsg]) fills a type(mio_format_info) for a format with a side channel (MDPA: tables, geometries, Mesh blocks, sub-model-part data, text ModelPartData, raw blocks such as Constraints); call m%write_with_info(path, info) writes it back and call info%free() releases it. Accessors take a MIO_MDPA_* section and a 1-based item index: info%mdpa_count, mdpa_string, mdpa_int, mdpa_ids, mdpa_table(i) (a (rows, cols) array), mdpa_geometry(i) (conn(nodes, n), 1-based points) and mdpa_data_* for key/value entries. See MDPA.

Sequences (transient / multi-file datasets) ​

type(mio_sequence) wraps the C API's ordered plan over a set of files (or the steps inside one multi-step file). Ordering is natural-numeric, so out_9.vtu precedes out_10.vtu; nothing is read until %read_step, and the sequence caches nothing, so a 500-step dataset is traversable without materialising it.

fortran
type(mio_sequence) :: seq
type(mio_mesh) :: step
integer :: i

call seq%open('out_*.vtu')
do i = 1, int(seq%count())
    step = seq%read_step(i)          ! OWNED; free it yourself
    print *, seq%time(i), seq%time_source(i)
    call step%free()
end do
call seq%to_timeseries('series.xdmf')            ! fan-in
call seq%resample('u_{index}.vtu', [0.0_real64, 0.5_real64, 1.0_real64], &
                  method='linear', clamp=.false.)  ! onto new times
call seq%free()

call mio_timeseries_to_sequence('series.xdmf', 'step_{step}.vtu')  ! fan-out
call mio_sequence_pipeline_run_file('transient.json')              ! per-step chain

Indices are 1-based, like every other Fortran accessor, and handles are freed explicitly (%free()) exactly as mio_mesh is — there is no finalizer. Every fallible procedure takes the usual optional stat/errmsg.

seq%resample(out_path, times [, method, clamp, blend_points, out_format, stat, errmsg]) resamples onto new times (method 'linear', 'nearest' or 'previous'), and the module function mio_blend_steps(a, b, w [, blend_points, stat, errmsg]) blends two steps of one topology into a new mesh. See Resampling onto new times.

See sequences for the ordering rule, the time-value precedence and the streaming guarantee.

Released under the MIT License.