import { useRef, useState } from 'react' import { MeshView } from './MeshView' import type { MeshData } from './mesh/types' import { faceCount, vertexCount } from './mesh/types' import { acceptedExtensions, conversionLosses, formatForFilename, formats } from './mesh/formats' import { makeSampleMesh } from './mesh/sample' import './App.css' function App() { const [mesh, setMesh] = useState(null) const [sourceLabel, setSourceLabel] = useState('') const [error, setError] = useState(null) const [exportFormatId, setExportFormatId] = useState(formats[0].id) const fileInputRef = useRef(null) const exportFormat = formats.find((f) => f.id === exportFormatId) ?? formats[0] const losses = mesh ? conversionLosses(mesh, exportFormat) : [] const handleFile = async (file: File) => { setError(null) const format = formatForFilename(file.name) if (!format) { setError( `Unrecognized extension on "${file.name}". Supported: ${acceptedExtensions.join(', ')}`, ) return } try { const text = await file.text() setMesh(format.parse(text)) setSourceLabel(`${file.name} (${format.label})`) } catch (e) { setError(e instanceof Error ? e.message : String(e)) } } const loadSample = () => { setError(null) setMesh(makeSampleMesh()) setSourceLabel('built-in sample') } const downloadExport = () => { if (!mesh) return const text = exportFormat.serialize(mesh) const base = (mesh.name ?? 'mesh').replace(/[^\w-]+/g, '_').toLowerCase() || 'mesh' const blob = new Blob([text], { type: 'text/plain' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = base + exportFormat.extension a.click() URL.revokeObjectURL(url) } return (

Mesh Converter

Load a mesh, inspect it in 3D, export to another format. Formats differ in what they can store — anything the target can't hold is dropped.

Load

{ const file = e.target.files?.[0] if (file) handleFile(file) e.target.value = '' }} /> {error &&
{error}
}
{mesh && (

Loaded mesh

{sourceLabel}
{mesh.name ?? '(unnamed)'} — {vertexCount(mesh)} vertices,{' '} {faceCount(mesh)} faces
positions faces normals colors name
)} {mesh && (

Export

{exportFormat.blurb}

{losses.length > 0 ? (
Exporting to {exportFormat.extension} will drop:{' '} {losses.join(', ')}
) : (
Lossless — this format keeps everything in the loaded mesh.
)}
)}

Formats

{formats.map((f) => ( ))}
Format normals colors name
{f.id.toUpperCase()} {f.extension} {f.capabilities.normals ? '✓' : '—'} {f.capabilities.colors ? '✓' : '—'} {f.capabilities.name ? '✓' : '—'}

These are invented text formats for illustration; real formats come later. All store positions and triangle faces.

{mesh ? ( ) : (

No mesh loaded.

Open a .mopf, .tricol, or .bmsh file — or load the sample.

)}
) } export default App