0
# Format Registration System
1
2
System for registering custom file formats and managing format detection based on file extensions, enabling extensibility of meshio with user-defined formats.
3
4
## Capabilities
5
6
### Format Registration Functions
7
8
Core functions for managing the format registry that controls which readers and writers are available for different file extensions.
9
10
```python { .api }
11
def register_format(format_name, extensions, reader, writer_map):
12
"""
13
Register a new file format with meshio's format detection system.
14
15
Parameters:
16
- format_name: str - Unique identifier for the format
17
Used internally to reference the format and in error messages
18
Should be descriptive and unique (e.g., "custom_fem", "my_format")
19
- extensions: List[str] - List of file extensions associated with this format
20
Extensions should include the dot (e.g., [".myfmt", ".dat"])
21
Multiple extensions can map to the same format
22
Extensions are case-insensitive during detection
23
- reader: Callable | None - Function to read files of this format
24
Signature: reader(filename) -> Mesh
25
Can be None if format is write-only
26
Function should handle both file paths and file-like objects
27
- writer_map: Dict[str, Callable] - Dictionary mapping format names to writer functions
28
Key: format name (usually same as format_name parameter)
29
Value: writer function with signature writer(filename, mesh, **kwargs)
30
Can be empty dict {} if format is read-only
31
32
Side Effects:
33
- Updates extension_to_filetypes mapping for auto-detection
34
- Registers reader function in global reader_map
35
- Registers writer functions in global writer_map
36
37
Examples:
38
>>> def my_reader(filename):
39
... # Custom reading logic
40
... return meshio.Mesh(points, cells)
41
>>>
42
>>> def my_writer(filename, mesh, **kwargs):
43
... # Custom writing logic
44
... pass
45
>>>
46
>>> meshio.register_format(
47
... "my_format",
48
... [".myfmt", ".dat"],
49
... my_reader,
50
... {"my_format": my_writer}
51
... )
52
"""
53
54
def deregister_format(format_name):
55
"""
56
Remove a previously registered file format from meshio.
57
58
Parameters:
59
- format_name: str - Name of format to remove
60
Must match the format_name used in register_format()
61
62
Side Effects:
63
- Removes format from extension_to_filetypes mapping
64
- Removes reader from reader_map if present
65
- Removes writer from writer_map if present
66
- After deregistration, files with associated extensions will no longer be auto-detected
67
68
Examples:
69
>>> meshio.deregister_format("my_format")
70
>>> # Now .myfmt files will not be recognized
71
"""
72
```
73
74
### Format Detection System
75
76
Internal mappings and utilities used by meshio's automatic format detection.
77
78
```python { .api }
79
extension_to_filetypes: Dict[str, List[str]]
80
"""
81
Dictionary mapping file extensions to lists of format names.
82
83
Structure:
84
- Keys: File extensions including dot (e.g., ".vtk", ".msh", ".stl")
85
- Values: List of format names that handle this extension
86
- Multiple formats can handle the same extension
87
- Extensions are stored in lowercase for case-insensitive matching
88
89
Examples:
90
>>> print(meshio.extension_to_filetypes[".vtk"])
91
['vtk']
92
>>> print(meshio.extension_to_filetypes[".msh"])
93
['gmsh']
94
95
Note: This is primarily for internal use and format introspection.
96
Direct modification is not recommended - use register_format() instead.
97
"""
98
99
# Internal mappings (not directly exposed but used by format system)
100
reader_map: Dict[str, Callable]
101
"""Internal mapping from format names to reader functions."""
102
103
_writer_map: Dict[str, Callable]
104
"""Internal mapping from format names to writer functions."""
105
```
106
107
## Usage Examples
108
109
### Registering a Custom Format
110
111
```python
112
import meshio
113
import numpy as np
114
115
def read_simple_format(filename):
116
"""
117
Reader for a hypothetical simple format.
118
119
File format:
120
Line 1: number_of_points number_of_triangles
121
Next lines: point coordinates (x, y, z)
122
Final lines: triangle indices (i, j, k)
123
"""
124
with open(filename, 'r') as f:
125
# Read header
126
n_points, n_triangles = map(int, f.readline().split())
127
128
# Read points
129
points = []
130
for _ in range(n_points):
131
x, y, z = map(float, f.readline().split())
132
points.append([x, y, z])
133
points = np.array(points)
134
135
# Read triangles
136
triangles = []
137
for _ in range(n_triangles):
138
i, j, k = map(int, f.readline().split())
139
triangles.append([i, j, k])
140
triangles = np.array(triangles)
141
142
# Create mesh
143
cells = [("triangle", triangles)]
144
return meshio.Mesh(points, cells)
145
146
def write_simple_format(filename, mesh, **kwargs):
147
"""
148
Writer for the simple format.
149
"""
150
# Extract triangles from mesh
151
triangles = mesh.get_cells_type("triangle")
152
153
with open(filename, 'w') as f:
154
# Write header
155
f.write(f"{len(mesh.points)} {len(triangles)}\n")
156
157
# Write points
158
for point in mesh.points:
159
f.write(f"{point[0]} {point[1]} {point[2]}\n")
160
161
# Write triangles
162
for triangle in triangles:
163
f.write(f"{triangle[0]} {triangle[1]} {triangle[2]}\n")
164
165
# Register the custom format
166
meshio.register_format(
167
"simple_triangle", # Format name
168
[".tri", ".simple"], # File extensions
169
read_simple_format, # Reader function
170
{"simple_triangle": write_simple_format} # Writer function
171
)
172
173
# Now can use the format automatically
174
mesh = meshio.read("model.tri") # Auto-detects simple_triangle format
175
meshio.write("output.simple", mesh) # Auto-detects and uses custom writer
176
```
177
178
### Read-Only Format Registration
179
180
```python
181
def read_special_format(filename):
182
"""Reader for a read-only format."""
183
# ... reading logic ...
184
return meshio.Mesh(points, cells)
185
186
# Register read-only format
187
meshio.register_format(
188
"read_only_format",
189
[".readonly", ".ro"],
190
read_special_format,
191
{} # Empty writer map - format is read-only
192
)
193
194
# Usage
195
mesh = meshio.read("data.readonly") # Works
196
# meshio.write("output.readonly", mesh) # Would raise WriteError
197
```
198
199
### Write-Only Format Registration
200
201
```python
202
def write_visualization_format(filename, mesh, **kwargs):
203
"""Writer for a visualization-only format."""
204
# ... writing logic for visualization ...
205
pass
206
207
# Register write-only format
208
meshio.register_format(
209
"viz_format",
210
[".viz", ".display"],
211
None, # No reader function
212
{"viz_format": write_visualization_format}
213
)
214
215
# Usage
216
# mesh = meshio.read("file.viz") # Would raise ReadError
217
meshio.write("visualization.viz", mesh) # Works
218
```
219
220
### Multiple Extension Handling
221
222
```python
223
def read_multi_ext_format(filename):
224
"""Reader that handles multiple extensions with slight variations."""
225
# Different behavior based on extension
226
if filename.endswith('.v1'):
227
# Handle version 1 format
228
pass
229
elif filename.endswith('.v2'):
230
# Handle version 2 format
231
pass
232
return meshio.Mesh(points, cells)
233
234
def write_multi_ext_format(filename, mesh, **kwargs):
235
"""Writer that adapts to extension."""
236
if filename.endswith('.v1'):
237
# Write version 1 format
238
pass
239
elif filename.endswith('.v2'):
240
# Write version 2 format
241
pass
242
243
# Register format with multiple extensions
244
meshio.register_format(
245
"versioned_format",
246
[".v1", ".v2", ".legacy"],
247
read_multi_ext_format,
248
{"versioned_format": write_multi_ext_format}
249
)
250
```
251
252
### Format Introspection
253
254
```python
255
# Check what formats are available
256
print("Registered formats:")
257
for ext, formats in meshio.extension_to_filetypes.items():
258
print(f" {ext}: {formats}")
259
260
# Check if specific format is registered
261
def is_format_registered(format_name):
262
"""Check if a format is currently registered."""
263
from meshio._helpers import reader_map, _writer_map
264
return (format_name in reader_map or
265
format_name in _writer_map)
266
267
print("Is 'vtk' registered?", is_format_registered("vtk"))
268
print("Is 'my_format' registered?", is_format_registered("my_format"))
269
270
# Find formats for extension
271
def get_formats_for_extension(extension):
272
"""Get all formats that handle a given extension."""
273
return meshio.extension_to_filetypes.get(extension.lower(), [])
274
275
print("Formats for .msh:", get_formats_for_extension(".msh"))
276
print("Formats for .vtk:", get_formats_for_extension(".vtk"))
277
```
278
279
### Dynamic Format Loading
280
281
```python
282
import importlib
283
284
def register_plugin_format(plugin_module_name):
285
"""
286
Dynamically load and register a format from a plugin module.
287
288
The plugin module should define:
289
- FORMAT_NAME: str
290
- EXTENSIONS: List[str]
291
- read_function: Callable
292
- write_function: Callable (optional)
293
"""
294
try:
295
plugin = importlib.import_module(plugin_module_name)
296
297
# Check required attributes
298
if not hasattr(plugin, 'FORMAT_NAME'):
299
raise ValueError("Plugin must define FORMAT_NAME")
300
if not hasattr(plugin, 'EXTENSIONS'):
301
raise ValueError("Plugin must define EXTENSIONS")
302
if not hasattr(plugin, 'read_function'):
303
print(f"Warning: {plugin_module_name} has no read_function")
304
reader = None
305
else:
306
reader = plugin.read_function
307
308
# Optional writer
309
writer_map = {}
310
if hasattr(plugin, 'write_function'):
311
writer_map[plugin.FORMAT_NAME] = plugin.write_function
312
313
# Register the format
314
meshio.register_format(
315
plugin.FORMAT_NAME,
316
plugin.EXTENSIONS,
317
reader,
318
writer_map
319
)
320
print(f"Successfully registered format '{plugin.FORMAT_NAME}'")
321
322
except ImportError:
323
print(f"Could not import plugin module '{plugin_module_name}'")
324
except Exception as e:
325
print(f"Error registering plugin: {e}")
326
327
# Usage
328
# register_plugin_format("my_mesh_plugin")
329
```
330
331
### Temporary Format Registration
332
333
```python
334
from contextlib import contextmanager
335
336
@contextmanager
337
def temporary_format(format_name, extensions, reader, writer_map):
338
"""
339
Context manager for temporary format registration.
340
341
Useful for testing or temporary format overrides.
342
"""
343
# Store original state
344
original_extensions = {}
345
for ext in extensions:
346
if ext in meshio.extension_to_filetypes:
347
original_extensions[ext] = meshio.extension_to_filetypes[ext].copy()
348
349
try:
350
# Register temporary format
351
meshio.register_format(format_name, extensions, reader, writer_map)
352
yield
353
finally:
354
# Restore original state
355
meshio.deregister_format(format_name)
356
357
# Restore original extension mappings
358
for ext, original_formats in original_extensions.items():
359
meshio.extension_to_filetypes[ext] = original_formats
360
361
# Usage
362
def temp_reader(filename):
363
return meshio.Mesh(np.array([[0, 0, 0]]), [])
364
365
with temporary_format("temp_fmt", [".tmp"], temp_reader, {}):
366
# Format is available only within this block
367
mesh = meshio.read("test.tmp") # Uses temporary reader
368
369
# Outside the block, .tmp files are no longer recognized
370
```
371
372
### Format Validation
373
374
```python
375
def validate_custom_format(reader_func, writer_func=None):
376
"""
377
Validate that custom format functions have correct signatures.
378
"""
379
import inspect
380
381
# Validate reader signature
382
if reader_func is not None:
383
sig = inspect.signature(reader_func)
384
if len(sig.parameters) < 1:
385
raise ValueError("Reader function must accept at least one parameter (filename)")
386
387
# Validate writer signature
388
if writer_func is not None:
389
sig = inspect.signature(writer_func)
390
if len(sig.parameters) < 2:
391
raise ValueError("Writer function must accept at least two parameters (filename, mesh)")
392
393
return True
394
395
# Example validation before registration
396
def my_reader(filename):
397
return meshio.Mesh(np.array([[0, 0, 0]]), [])
398
399
def my_writer(filename, mesh, **kwargs):
400
pass
401
402
# Validate before registering
403
if validate_custom_format(my_reader, my_writer):
404
meshio.register_format("validated_format", [".val"], my_reader, {"validated_format": my_writer})
405
```
406
407
## Error Handling
408
409
### Common Registration Errors
410
411
```python
412
# Handle registration errors gracefully
413
try:
414
meshio.register_format(
415
"problematic_format",
416
[".bad"],
417
lambda x: None, # Bad reader - doesn't return Mesh
418
{"problematic_format": lambda x, y: None}
419
)
420
except Exception as e:
421
print(f"Registration failed: {e}")
422
423
# Check for conflicts
424
def register_format_safely(format_name, extensions, reader, writer_map):
425
"""Register format with conflict detection."""
426
# Check for extension conflicts
427
conflicts = []
428
for ext in extensions:
429
if ext in meshio.extension_to_filetypes:
430
existing = meshio.extension_to_filetypes[ext]
431
conflicts.extend(existing)
432
433
if conflicts:
434
print(f"Warning: Extensions {extensions} conflict with formats: {conflicts}")
435
response = input("Continue? (y/n): ")
436
if response.lower() != 'y':
437
return False
438
439
# Register format
440
meshio.register_format(format_name, extensions, reader, writer_map)
441
return True
442
```
443
444
## Best Practices
445
446
### Format Design Guidelines
447
448
1. **Reader Functions**: Should be robust and handle various edge cases
449
2. **Writer Functions**: Should preserve as much mesh data as possible
450
3. **Extension Choice**: Use descriptive, unique extensions
451
4. **Error Messages**: Provide clear error messages for invalid files
452
5. **Documentation**: Document format specifications and limitations
453
454
### Performance Considerations
455
456
```python
457
# Efficient readers for large files
458
def efficient_reader(filename):
459
"""Reader optimized for large files."""
460
# Use memory mapping for large files
461
if os.path.getsize(filename) > 100_000_000: # 100MB
462
# Use memory-mapped reading
463
pass
464
else:
465
# Standard reading
466
pass
467
return mesh
468
469
# Lazy loading for complex formats
470
def lazy_reader(filename):
471
"""Reader that supports lazy loading of data."""
472
# Read only metadata first, load data on demand
473
return mesh
474
```
475
476
The 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.