# How Modly's `/export` Router Converts Mesh Data to GLB/GLTF Formats

> Discover how Modly's /export router streams GLB/GLTF files by delegating conversion to the mesh exporter workflow node, utilizing Three.js for efficient mesh data export.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: internals
- Published: 2026-08-15

---

**Modly's `/export` router streams GLB/GLTF files by delegating format conversion to the `mesh-exporter` workflow node, which uses Three.js exporters and returns a file path that the HTTP endpoint serves back to the renderer.**

In the **Modly** 3D workflow application, exporting a generated mesh to industry-standard formats follows a clean separation: heavy processing runs in the Electron main process while file delivery happens through a thin HTTP layer. This article breaks down the complete data flow from workflow node to downloadable file.

## The mesh-exporter Node Architecture

All export logic originates in [`src/areas/workflows/nodes/mesh-exporter/processor.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/nodes/mesh-exporter/processor.ts). This processor implements Modly's standard node interface and runs exclusively on the Electron main thread to avoid blocking the UI.

### Input Validation and Format Selection

The processor begins by enforcing strict contract requirements:

```typescript
if (!input.filePath) throw new Error('mesh-exporter: input.filePath is required');

```

It then normalizes the `export_format` parameter, defaulting to `glb` when unspecified:

```typescript
const format = String(params['export_format'] ?? 'glb').toLowerCase();
const extMap = { glb: '.glb', gltf: '.gltf' };
const ext = extMap[format];
if (!ext) throw new Error(`mesh-exporter: unsupported format "${format}"`);

```

This validation gate ensures only **GLB** (binary) or **GLTF** (JSON) outputs proceed.

## File System Preparation and Geometry Export

### Workspace Directory Structure

The processor creates an isolated `Exports` folder within the active workspace to contain all generated files:

```typescript
const exportsDir = path.join(context.workspaceDir, 'Exports');
fs.mkdirSync(exportsDir, { recursive: true });
const outPath = path.join(exportsDir, `export-${Date.now()}${ext}`);

```

The **timestamp-based naming** prevents collisions during rapid successive exports.

### Three.js Export Pipeline

The processor loads the source mesh through an internal `readMesh` helper—shared with the `mesh-optimizer` node—then delegates format serialization to **Three.js** exporters:

| Format | Three.js Class | Output Mode |
|--------|---------------|-------------|
| GLB | `GLTFExporter` | `{ binary: true }` → `Uint8Array` |
| GLTF | `GLTFExporter` | Default → JSON string |

The exporter receives a `BufferGeometry` object containing vertex data, indices, and any computed vertex normals from upstream processing.

```typescript
const exporter = new GLTFExporter();
const data = await new Promise((resolve) => 
  exporter.parse(geometry, resolve, { binary: format === 'glb' })
);
fs.writeFileSync(outPath, format === 'glb' ? Buffer.from(data) : data);

```

Materials and textures flow through automatically if the workflow attached them during mesh generation.

## The /export HTTP Router

File delivery is handled separately in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts). The `mesh-exporter` processor returns `{ outputPath: outPath }`, which the renderer converts into a download URL:

```typescript
const downloadUrl = `${API_BASE_URL}/export/${format}?path=${encodeURIComponent(meshPath)}`;

```

### Route Implementation

The Express route validates the format parameter, streams the file with correct MIME typing, and relies on Node.js `fs.createReadStream` for memory-efficient transfer:

```typescript
app.get('/export/:format', async (req, res) => {
  const { format } = req.params;
  const meshPath = decodeURIComponent(req.query.path as string);
  
  if (!['glb', 'gltf'].includes(format)) {
    return res.status(400).send('Invalid format');
  }

  const stream = fs.createReadStream(meshPath);
  const mimeType = format === 'glb' 
    ? 'model/gltf-binary' 
    : 'model/gltf+json';
  
  res.setHeader('Content-Type', mimeType);
  stream.pipe(res);
});

```

This **two-stage architecture**—processor for computation, route for I/O—keeps export logic testable and the HTTP surface minimal.

## Complete Workflow Example

Triggering an export from the renderer requires only workflow composition:

```typescript
// Renderer process
const result = await window.electron.workflows.execute({
  nodes: [
    {
      id: 'optimize',
      type: 'mesh-optimizer',
      params: { decimate_ratio: 0.5 }
    },
    {
      id: 'export1',
      type: 'mesh-exporter',
      params: { export_format: 'glb' },
      inputs: { filePath: '${optimize.outputPath}' }
    }
  ],
  inputs: { sourceMesh: '/path/to/scene.ply' }
});

// Download via the export router
const blob = await fetch(`/export/glb?path=${encodeURIComponent(result.export1.outputPath)}`);

```

## Asset Registry Integration

Before export, Modly's [`artifact-registry-service.ts`](https://github.com/lightningpixel/modly/blob/main/artifact-registry-service.ts) validates mesh assets through an `isGlbOrGltf` helper. This ensures workspace consistency and prevents invalid files from reaching the export pipeline.

## Summary

- The **`mesh-exporter` processor** in [`src/areas/workflows/nodes/mesh-exporter/processor.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/nodes/mesh-exporter/processor.ts) handles all format conversion using Three.js `GLTFExporter`
- **Input validation** requires `input.filePath` and rejects unsupported formats early
- The **`Exports` workspace directory** isolates generated files with timestamped names
- **GLB/GLTF selection** toggles the exporter's `binary` option and file extension
- The **`/export/:format` route** in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts) streams files without reprocessing
- The **renderer-to-main IPC boundary** keeps heavy mesh operations off the UI thread

## Frequently Asked Questions

### What file formats can the mesh-exporter node output?

The node supports **GLB** and **GLTF** exclusively. The `export_format` parameter accepts these values case-insensitively, defaulting to `glb`. Any other value triggers an explicit error at [`processor.ts`](https://github.com/lightningpixel/modly/blob/main/processor.ts) line 192.

### Where does the exported file get saved?

Files write to an **`Exports` subdirectory** inside the active workspace root, created automatically if absent. The filename format is `export-${timestamp}.${ext}` to guarantee uniqueness across rapid successive runs.

### Can I export meshes with textures and materials?

Yes. The Three.js `GLTFExporter` serializes any **materials, textures, and UV coordinates** present on the `BufferGeometry`. Ensure upstream nodes—such as `mesh-optimizer` or material attachers—populate these attributes before the export step.

### How does the renderer download the exported file?

The processor returns an absolute file path, which the renderer encodes into a query string for the **`/export/:format` endpoint**. The Express route streams the file directly, setting the appropriate `Content-Type` header (`model/gltf-binary` or `model/gltf+json`) for browser handling.