# How the Mesh Repair Node Processes 3D Models in Modly

> Learn how the Modly mesh repair node processes 3D models. It validates files, loads geometry, welds vertices, reduces polygon count with meshoptimizer, and outputs an optimized GLB file.

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

---

**The mesh repair node validates the input file, loads the geometry with `@gltf-transform`, optionally welds duplicate vertices, reduces polygon count using the `meshoptimizer` WASM library, and writes an optimized GLB to the workspace.**

The **mesh repair node** (internally named `mesh-optimizer`) is a critical processor in the [lightningpixel/modly](https://github.com/lightningpixel/modly) workflow engine that prepares 3D assets for downstream consumption. According to the source code in [`src/areas/workflows/nodes/mesh-optimizer/processor.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/nodes/mesh-optimizer/processor.ts), the node executes a nine-stage pipeline to repair and optimize mesh geometry while preserving visual fidelity.

## Input Validation and Mesh Loading

The processor begins by enforcing strict input requirements. At lines 17–18 of [`processor.ts`](https://github.com/lightningpixel/modly/blob/main/processor.ts), the code validates that the `filePath` parameter exists:

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

```

Once validated, the node initializes the **@gltf-transform** `NodeIO` class to parse the source file. The `NodeIO` instance reads the GLB or GLTF data and constructs a document scene graph that serves as the foundation for all subsequent operations (lines 22–34).

## Triangle Analysis and Early Exit Optimization

Before performing expensive computations, the processor calculates the current polygon budget. It iterates through all meshes in the document root, summing the indices or positions of every primitive to determine the total triangle count (lines 35–47).

If the current face count is already below the target threshold—defaulting to **10,000 triangles**—the node triggers an early exit at lines 50–54. It returns the original file path unchanged, avoiding unnecessary processing overhead:

```typescript
if (currentFaces <= targetFaces) {
  return { filePath: input.filePath }
}

```

## Geometry Repair and Simplification Pipeline

When reduction is required, the node calculates a simplification ratio and error tolerance. The ratio equals `targetFaces / currentFaces` (capped at 1.0), while the error tolerance derives from `Math.max(0.001, 1 - ratio)` (lines 56–61).

### Vertex Welding for Duplicate Removal

For meshes containing fewer than **500,000 faces**, the processor executes a **weld** operation via `@gltf-transform/functions`. This step deduplicates vertices to repair topological inconsistencies. The 500k threshold prevents O(N²) performance degradation on large assets (lines 62–66):

```typescript
if (currentFaces < 500_000) await doc.transform(weld())

```

### Polygon Reduction with Meshoptimizer

The actual simplification leverages the **meshoptimizer** library compiled to WebAssembly. The code first awaits `MeshoptSimplifier.ready` to ensure the WASM binary is loaded, then applies the `simplify` transform with the calculated ratio and error parameters (lines 28–33 and 70–73):

```typescript
await MeshoptSimplifier.ready
await doc.transform(simplify({
  simplifier: MeshoptSimplifier,
  ratio,
  error,
  lockBorder: false
}))

```

This reduces triangle count while maintaining edge boundaries, effectively "repairing" the mesh density for real-time rendering workflows.

## Output Generation and Progress Tracking

After optimization, the node writes the repaired document to the workspace directory at `Workflows/mesh-optimizer-<timestamp>.glb` using `io.write()` (lines 75–85). Throughout the pipeline, the processor reports incremental progress (10% → 100%) via `context.progress()` and emits diagnostic logs via `context.log()`, providing real-time feedback in the Modly UI.

## Implementation Example

You can configure the mesh repair node in a workflow JSON definition by specifying the `mesh-optimizer` extension ID and desired triangle target:

```json
{
  "type": "meshNode",
  "id": "repair1",
  "extensionId": "mesh-optimizer",
  "params": {
    "target_faces": 8000
  },
  "inputs": [{ "source": "importNode", "port": "mesh" }]
}

```

For testing or custom integrations, invoke the processor directly with the required context interface:

```typescript
import processor from './src/areas/workflows/nodes/mesh-optimizer/processor'

const result = await processor(
  { filePath: '/assets/source.glb' },
  { target_faces: 5000 },
  {
    workspaceDir: '/my/workspace',
    tempDir: '/tmp',
    log: console.log,
    progress: (pct, label) => console.log(`${pct}% – ${label}`)
  }
)

console.log('Optimized output:', result.filePath)

```

## Summary

- The mesh repair node requires a valid `filePath` input and throws immediately if missing.
- It uses `@gltf-transform/core` (`NodeIO`) to parse GLB/GLTF files into a manipulable scene graph.
- Meshes with fewer than 10,000 triangles bypass processing to prevent over-optimization.
- Vertex welding repairs duplicate geometry only on meshes under 500,000 faces to avoid performance penalties.
- The `meshoptimizer` WASM library performs the final polygon reduction using calculated ratio and error tolerance values.
- Optimized files are written to the workspace with timestamped filenames and full progress telemetry.

## Frequently Asked Questions

### What file formats does the mesh repair node support?

The node supports GLB and GLTF formats through the `@gltf-transform/core` library. The `NodeIO` class handles the parsing and serialization of these formats, ensuring compatibility with standard glTF 2.0 assets commonly used in web-based 3D workflows.

### How does the node decide when to skip simplification?

The processor compares the current triangle count against the `target_faces` parameter (defaulting to 10,000). If the mesh already meets this budget, the code returns the original file path at line 50 without invoking the meshoptimizer pipeline, preserving disk I/O and computation time.

### Why does vertex welding skip on large meshes?

The weld operation has O(N²) complexity, making it prohibitively expensive for high-poly assets. The processor explicitly checks `if (currentFaces < 500_000)` before calling `weld()`, ensuring that large production meshes do not trigger performance bottlenecks during the repair process.

### Can I adjust the target triangle count?

Yes. Pass the `target_faces` parameter in the node configuration or processor arguments. The system calculates the reduction ratio as `Math.min(1, targetFaces / currentFaces)`, allowing you to specify precise polygon budgets for different target platforms like mobile VR or web viewers.