I/O for many mesh formats
—
System for registering custom file formats and managing format detection based on file extensions, enabling extensibility of meshio with user-defined formats.
Core functions for managing the format registry that controls which readers and writers are available for different file extensions.
def register_format(format_name, extensions, reader, writer_map):
"""
Register a new file format with meshio's format detection system.
Parameters:
- format_name: str - Unique identifier for the format
Used internally to reference the format and in error messages
Should be descriptive and unique (e.g., "custom_fem", "my_format")
- extensions: List[str] - List of file extensions associated with this format
Extensions should include the dot (e.g., [".myfmt", ".dat"])
Multiple extensions can map to the same format
Extensions are case-insensitive during detection
- reader: Callable | None - Function to read files of this format
Signature: reader(filename) -> Mesh
Can be None if format is write-only
Function should handle both file paths and file-like objects
- writer_map: Dict[str, Callable] - Dictionary mapping format names to writer functions
Key: format name (usually same as format_name parameter)
Value: writer function with signature writer(filename, mesh, **kwargs)
Can be empty dict {} if format is read-only
Side Effects:
- Updates extension_to_filetypes mapping for auto-detection
- Registers reader function in global reader_map
- Registers writer functions in global writer_map
Examples:
>>> def my_reader(filename):
... # Custom reading logic
... return meshio.Mesh(points, cells)
>>>
>>> def my_writer(filename, mesh, **kwargs):
... # Custom writing logic
... pass
>>>
>>> meshio.register_format(
... "my_format",
... [".myfmt", ".dat"],
... my_reader,
... {"my_format": my_writer}
... )
"""
def deregister_format(format_name):
"""
Remove a previously registered file format from meshio.
Parameters:
- format_name: str - Name of format to remove
Must match the format_name used in register_format()
Side Effects:
- Removes format from extension_to_filetypes mapping
- Removes reader from reader_map if present
- Removes writer from writer_map if present
- After deregistration, files with associated extensions will no longer be auto-detected
Examples:
>>> meshio.deregister_format("my_format")
>>> # Now .myfmt files will not be recognized
"""Internal mappings and utilities used by meshio's automatic format detection.
extension_to_filetypes: Dict[str, List[str]]
"""
Dictionary mapping file extensions to lists of format names.
Structure:
- Keys: File extensions including dot (e.g., ".vtk", ".msh", ".stl")
- Values: List of format names that handle this extension
- Multiple formats can handle the same extension
- Extensions are stored in lowercase for case-insensitive matching
Examples:
>>> print(meshio.extension_to_filetypes[".vtk"])
['vtk']
>>> print(meshio.extension_to_filetypes[".msh"])
['gmsh']
Note: This is primarily for internal use and format introspection.
Direct modification is not recommended - use register_format() instead.
"""
# Internal mappings (not directly exposed but used by format system)
reader_map: Dict[str, Callable]
"""Internal mapping from format names to reader functions."""
_writer_map: Dict[str, Callable]
"""Internal mapping from format names to writer functions."""import meshio
import numpy as np
def read_simple_format(filename):
"""
Reader for a hypothetical simple format.
File format:
Line 1: number_of_points number_of_triangles
Next lines: point coordinates (x, y, z)
Final lines: triangle indices (i, j, k)
"""
with open(filename, 'r') as f:
# Read header
n_points, n_triangles = map(int, f.readline().split())
# Read points
points = []
for _ in range(n_points):
x, y, z = map(float, f.readline().split())
points.append([x, y, z])
points = np.array(points)
# Read triangles
triangles = []
for _ in range(n_triangles):
i, j, k = map(int, f.readline().split())
triangles.append([i, j, k])
triangles = np.array(triangles)
# Create mesh
cells = [("triangle", triangles)]
return meshio.Mesh(points, cells)
def write_simple_format(filename, mesh, **kwargs):
"""
Writer for the simple format.
"""
# Extract triangles from mesh
triangles = mesh.get_cells_type("triangle")
with open(filename, 'w') as f:
# Write header
f.write(f"{len(mesh.points)} {len(triangles)}\n")
# Write points
for point in mesh.points:
f.write(f"{point[0]} {point[1]} {point[2]}\n")
# Write triangles
for triangle in triangles:
f.write(f"{triangle[0]} {triangle[1]} {triangle[2]}\n")
# Register the custom format
meshio.register_format(
"simple_triangle", # Format name
[".tri", ".simple"], # File extensions
read_simple_format, # Reader function
{"simple_triangle": write_simple_format} # Writer function
)
# Now can use the format automatically
mesh = meshio.read("model.tri") # Auto-detects simple_triangle format
meshio.write("output.simple", mesh) # Auto-detects and uses custom writerdef read_special_format(filename):
"""Reader for a read-only format."""
# ... reading logic ...
return meshio.Mesh(points, cells)
# Register read-only format
meshio.register_format(
"read_only_format",
[".readonly", ".ro"],
read_special_format,
{} # Empty writer map - format is read-only
)
# Usage
mesh = meshio.read("data.readonly") # Works
# meshio.write("output.readonly", mesh) # Would raise WriteErrordef write_visualization_format(filename, mesh, **kwargs):
"""Writer for a visualization-only format."""
# ... writing logic for visualization ...
pass
# Register write-only format
meshio.register_format(
"viz_format",
[".viz", ".display"],
None, # No reader function
{"viz_format": write_visualization_format}
)
# Usage
# mesh = meshio.read("file.viz") # Would raise ReadError
meshio.write("visualization.viz", mesh) # Worksdef read_multi_ext_format(filename):
"""Reader that handles multiple extensions with slight variations."""
# Different behavior based on extension
if filename.endswith('.v1'):
# Handle version 1 format
pass
elif filename.endswith('.v2'):
# Handle version 2 format
pass
return meshio.Mesh(points, cells)
def write_multi_ext_format(filename, mesh, **kwargs):
"""Writer that adapts to extension."""
if filename.endswith('.v1'):
# Write version 1 format
pass
elif filename.endswith('.v2'):
# Write version 2 format
pass
# Register format with multiple extensions
meshio.register_format(
"versioned_format",
[".v1", ".v2", ".legacy"],
read_multi_ext_format,
{"versioned_format": write_multi_ext_format}
)# Check what formats are available
print("Registered formats:")
for ext, formats in meshio.extension_to_filetypes.items():
print(f" {ext}: {formats}")
# Check if specific format is registered
def is_format_registered(format_name):
"""Check if a format is currently registered."""
from meshio._helpers import reader_map, _writer_map
return (format_name in reader_map or
format_name in _writer_map)
print("Is 'vtk' registered?", is_format_registered("vtk"))
print("Is 'my_format' registered?", is_format_registered("my_format"))
# Find formats for extension
def get_formats_for_extension(extension):
"""Get all formats that handle a given extension."""
return meshio.extension_to_filetypes.get(extension.lower(), [])
print("Formats for .msh:", get_formats_for_extension(".msh"))
print("Formats for .vtk:", get_formats_for_extension(".vtk"))import importlib
def register_plugin_format(plugin_module_name):
"""
Dynamically load and register a format from a plugin module.
The plugin module should define:
- FORMAT_NAME: str
- EXTENSIONS: List[str]
- read_function: Callable
- write_function: Callable (optional)
"""
try:
plugin = importlib.import_module(plugin_module_name)
# Check required attributes
if not hasattr(plugin, 'FORMAT_NAME'):
raise ValueError("Plugin must define FORMAT_NAME")
if not hasattr(plugin, 'EXTENSIONS'):
raise ValueError("Plugin must define EXTENSIONS")
if not hasattr(plugin, 'read_function'):
print(f"Warning: {plugin_module_name} has no read_function")
reader = None
else:
reader = plugin.read_function
# Optional writer
writer_map = {}
if hasattr(plugin, 'write_function'):
writer_map[plugin.FORMAT_NAME] = plugin.write_function
# Register the format
meshio.register_format(
plugin.FORMAT_NAME,
plugin.EXTENSIONS,
reader,
writer_map
)
print(f"Successfully registered format '{plugin.FORMAT_NAME}'")
except ImportError:
print(f"Could not import plugin module '{plugin_module_name}'")
except Exception as e:
print(f"Error registering plugin: {e}")
# Usage
# register_plugin_format("my_mesh_plugin")from contextlib import contextmanager
@contextmanager
def temporary_format(format_name, extensions, reader, writer_map):
"""
Context manager for temporary format registration.
Useful for testing or temporary format overrides.
"""
# Store original state
original_extensions = {}
for ext in extensions:
if ext in meshio.extension_to_filetypes:
original_extensions[ext] = meshio.extension_to_filetypes[ext].copy()
try:
# Register temporary format
meshio.register_format(format_name, extensions, reader, writer_map)
yield
finally:
# Restore original state
meshio.deregister_format(format_name)
# Restore original extension mappings
for ext, original_formats in original_extensions.items():
meshio.extension_to_filetypes[ext] = original_formats
# Usage
def temp_reader(filename):
return meshio.Mesh(np.array([[0, 0, 0]]), [])
with temporary_format("temp_fmt", [".tmp"], temp_reader, {}):
# Format is available only within this block
mesh = meshio.read("test.tmp") # Uses temporary reader
# Outside the block, .tmp files are no longer recognizeddef validate_custom_format(reader_func, writer_func=None):
"""
Validate that custom format functions have correct signatures.
"""
import inspect
# Validate reader signature
if reader_func is not None:
sig = inspect.signature(reader_func)
if len(sig.parameters) < 1:
raise ValueError("Reader function must accept at least one parameter (filename)")
# Validate writer signature
if writer_func is not None:
sig = inspect.signature(writer_func)
if len(sig.parameters) < 2:
raise ValueError("Writer function must accept at least two parameters (filename, mesh)")
return True
# Example validation before registration
def my_reader(filename):
return meshio.Mesh(np.array([[0, 0, 0]]), [])
def my_writer(filename, mesh, **kwargs):
pass
# Validate before registering
if validate_custom_format(my_reader, my_writer):
meshio.register_format("validated_format", [".val"], my_reader, {"validated_format": my_writer})# Handle registration errors gracefully
try:
meshio.register_format(
"problematic_format",
[".bad"],
lambda x: None, # Bad reader - doesn't return Mesh
{"problematic_format": lambda x, y: None}
)
except Exception as e:
print(f"Registration failed: {e}")
# Check for conflicts
def register_format_safely(format_name, extensions, reader, writer_map):
"""Register format with conflict detection."""
# Check for extension conflicts
conflicts = []
for ext in extensions:
if ext in meshio.extension_to_filetypes:
existing = meshio.extension_to_filetypes[ext]
conflicts.extend(existing)
if conflicts:
print(f"Warning: Extensions {extensions} conflict with formats: {conflicts}")
response = input("Continue? (y/n): ")
if response.lower() != 'y':
return False
# Register format
meshio.register_format(format_name, extensions, reader, writer_map)
return True# Efficient readers for large files
def efficient_reader(filename):
"""Reader optimized for large files."""
# Use memory mapping for large files
if os.path.getsize(filename) > 100_000_000: # 100MB
# Use memory-mapped reading
pass
else:
# Standard reading
pass
return mesh
# Lazy loading for complex formats
def lazy_reader(filename):
"""Reader that supports lazy loading of data."""
# Read only metadata first, load data on demand
return meshThe format registration system provides a flexible way to extend meshio with custom file formats while maintaining compatibility with the existing I/O system and automatic format detection.
Install with Tessl CLI
npx tessl i tessl/pypi-meshio