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

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. 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:

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

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

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:

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.

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. The mesh-exporter processor returns { outputPath: outPath }, which the renderer converts into a download URL:

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:

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:

// 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 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 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →