1import type { MeshData, MeshCapabilities } from './types'
3/**
4 * A real mesh format handled by meshio (id = meshio's file_format name).
5 * `capabilities` declares which optional attributes this app preserves when
6 * writing the format — it drives the loss warnings in the UI and which
7 * point-data arrays the bridge attaches on export.
8 */
9export interface MeshFormat {
10 id: string
11 label: string
12 /** File extension including the dot, e.g. ".ply" */
13 extension: string
14 blurb: string
15 capabilities: MeshCapabilities
16 /**
17 * Prebuilt Pyodide packages this format needs (e.g. h5py). Loaded lazily on
18 * first use so the default footprint stays small.
19 */
20 pyodidePackages?: string[]
21}
23export const formats: MeshFormat[] = [
24 {
25 id: 'ply',
26 label: 'PLY (Polygon File Format)',
27 extension: '.ply',
28 blurb:
29 'The Stanford scanner format, widely understood by mesh tools. Stores vertex ' +
30 'normals (nx/ny/nz) and colors (red/green/blue) as standard vertex properties.',
31 capabilities: { normals: true, colors: true },
32 },
33 {
34 id: 'obj',
35 label: 'Wavefront OBJ',
36 extension: '.obj',
37 blurb:
38 'Ubiquitous text format from the graphics world. Carries vertex normals (vn lines) ' +
39 'but has no standard slot for per-vertex colors.',
40 capabilities: { normals: true, colors: false },
41 },
42 {
43 id: 'stl',
44 label: 'STL',
45 extension: '.stl',
46 blurb:
47 'The 3D-printing staple: a bare triangle soup (written binary). Facet normals are ' +
48 'recomputed from geometry; vertex normals and colors cannot be stored.',
49 capabilities: { normals: false, colors: false },
50 },
51 {
52 id: 'off',
53 label: 'OFF (Object File Format)',
54 extension: '.off',
55 blurb: 'Minimal academic text format: vertex coordinates and faces, nothing else.',
56 capabilities: { normals: false, colors: false },
57 },
58 {
59 id: 'vtk',
60 label: 'VTK legacy',
61 extension: '.vtk',
62 blurb:
63 'Legacy VTK unstructured grid. Normals and colors travel as named point-data ' +
64 'arrays (Normals, RGB), visible in ParaView.',
65 capabilities: { normals: true, colors: true },
66 },
67 {
68 id: 'vtu',
69 label: 'VTU (VTK XML)',
70 extension: '.vtu',
71 blurb:
72 'Modern XML VTK unstructured grid with compressed binary data. Normals and colors ' +
73 'travel as point-data arrays.',
74 capabilities: { normals: true, colors: true },
75 },
76 {
77 id: 'gmsh',
78 label: 'Gmsh MSH',
79 extension: '.msh',
80 blurb:
81 'Native format of the Gmsh mesh generator (v4.1 binary). This app writes geometry ' +
82 'only — normals and colors are dropped.',
83 capabilities: { normals: false, colors: false },
84 },
85 {
86 id: 'xdmf',
87 label: 'XDMF',
88 extension: '.xdmf',
89 blurb:
90 'XML metadata with HDF5-backed heavy data, common in HPC simulation. Normals and ' +
91 'colors travel as point-data arrays. Loads the h5py package on first use.',
92 capabilities: { normals: true, colors: true },
93 pyodidePackages: ['h5py'],
94 },
95 {
96 id: 'med',
97 label: 'MED (Salome)',
98 extension: '.med',
99 blurb:
100 'HDF5-based format of the Salome platform and code_aster. Normals and colors ' +
101 'travel as point-data fields. Loads the h5py package on first use.',
102 capabilities: { normals: true, colors: true },
103 pyodidePackages: ['h5py'],
104 },
105 {
106 id: 'h5m',
107 label: 'H5M (MOAB)',
108 extension: '.h5m',
109 blurb:
110 'HDF5-based format of the MOAB mesh library. Normals and colors travel as tags. ' +
111 'Loads the h5py package on first use.',
112 capabilities: { normals: true, colors: true },
113 pyodidePackages: ['h5py'],
114 },
115 {
116 id: 'avsucd',
117 label: 'AVS-UCD',
118 extension: '.avs',
119 blurb:
120 'AVS unstructured cell data, a classic visualization text format. Normals and ' +
121 'colors travel as node data.',
122 capabilities: { normals: true, colors: true },
123 },
124 {
125 id: 'abaqus',
126 label: 'Abaqus',
127 extension: '.inp',
128 blurb: 'Abaqus FEA input deck (text). Geometry only.',
129 capabilities: { normals: false, colors: false },
130 },
131 {
132 id: 'nastran',
133 label: 'Nastran',
134 extension: '.bdf',
135 blurb: 'Nastran bulk data file, widespread in structural analysis. Geometry only.',
136 capabilities: { normals: false, colors: false },
137 },
138 {
139 id: 'medit',
140 label: 'Medit',
141 extension: '.mesh',
142 blurb: 'Text format of the Medit/INRIA meshing tools (also used by mmg). Geometry only.',
143 capabilities: { normals: false, colors: false },
144 },
145 {
146 id: 'netgen',
147 label: 'Netgen',
148 extension: '.vol',
149 blurb: 'Native format of the Netgen mesh generator. Geometry only.',
150 capabilities: { normals: false, colors: false },
151 },
152 {
153 id: 'mdpa',
154 label: 'MDPA (Kratos)',
155 extension: '.mdpa',
156 blurb: 'Input format of the Kratos multiphysics framework. Geometry only.',
157 capabilities: { normals: false, colors: false },
158 },
159 {
160 id: 'tecplot',
161 label: 'Tecplot',
162 extension: '.dat',
163 blurb: 'Tecplot ASCII data format. Geometry only.',
164 capabilities: { normals: false, colors: false },
165 },
166 {
167 id: 'dolfin-xml',
168 label: 'DOLFIN XML',
169 extension: '.xml',
170 blurb: 'Legacy XML format of the FEniCS/DOLFIN project. Geometry only.',
171 capabilities: { normals: false, colors: false },
172 },
173 {
174 id: 'permas',
175 label: 'PERMAS',
176 extension: '.post',
177 blurb: 'PERMAS FEA text format. Geometry only.',
178 capabilities: { normals: false, colors: false },
179 },
180]
182export function formatForFilename(filename: string): MeshFormat | null {
183 const lower = filename.toLowerCase()
184 return formats.find((f) => lower.endsWith(f.extension)) ?? null
185}
187export const acceptedExtensions = formats.map((f) => f.extension)
189/**
190 * Human-readable list of attributes of `mesh` that `target` cannot store
191 * (empty array means the conversion is lossless).
192 */
193export function conversionLosses(mesh: MeshData, target: MeshFormat): string[] {
194 const losses: string[] = []
195 if (mesh.normals && !target.capabilities.normals) losses.push('vertex normals')
196 if (mesh.colors && !target.capabilities.colors) losses.push('vertex colors')
197 return losses
198}