Skip to main content

Overview

The 3MF export module converts Manifold CSG geometry to the 3MF file format, optimized for BambuStudio and other modern slicers. It uses JSZip to package the 3MF archive structure.

Import

import {
  manifoldToTriangleMesh,
  exportTo3MF,
  downloadBlob,
  type TriangleMesh,
} from '@/gridfinity/export3mf';

Types

TriangleMesh

Intermediate mesh format for 3MF export.
interface TriangleMesh {
  vertices: Float32Array;  // [x0, y0, z0, x1, y1, z1, ...]
  triangles: Uint32Array;  // [v0, v1, v2, v0, v1, v2, ...]
}
Source: src/gridfinity/export3mf.ts:3-6
vertices
Float32Array
required
Flat array of vertex positions (3 floats per vertex: x, y, z)
triangles
Uint32Array
required
Flat array of triangle vertex indices (3 indices per triangle)

Functions

manifoldToTriangleMesh

Converts a Manifold object to TriangleMesh format.
function manifoldToTriangleMesh(manifoldObj: any): TriangleMesh
manifoldObj
Manifold
required
Manifold geometry object
Returns: TriangleMesh with vertices and triangles. Source: src/gridfinity/export3mf.ts:9-25 Example:
import wasm from 'manifold-3d';
import { generateBinExport } from '@/gridfinity/binGeometry';
import { manifoldToTriangleMesh } from '@/gridfinity/export3mf';

const manifold = await wasm();
const binGeometry = generateBinExport(manifold, config);
const mesh = manifoldToTriangleMesh(binGeometry);

console.log(mesh.vertices.length / 3);  // Number of vertices
console.log(mesh.triangles.length / 3); // Number of triangles

exportTo3MF

Packages triangle meshes into a 3MF file (ZIP archive).
async function exportTo3MF(
  meshes: { mesh: TriangleMesh; name: string }[],
): Promise<Blob>
meshes
Array<{mesh: TriangleMesh, name: string}>
required
Array of meshes to include in the 3MF file. Each mesh has:
  • mesh: The triangle mesh data
  • name: Display name for the object in the slicer
Returns: Promise resolving to a Blob containing the 3MF file. Source: src/gridfinity/export3mf.ts:98-168 Example:
import { exportTo3MF, downloadBlob } from '@/gridfinity/export3mf';

const blob = await exportTo3MF([
  { mesh: mesh1, name: 'Bin 2x2x3' },
  { mesh: mesh2, name: 'Bin 1x4x6' },
  { mesh: mesh3, name: 'Baseplate 6x6' },
]);

downloadBlob(blob, 'gridfinity-bins.3mf');

downloadBlob

Triggers browser download of a Blob.
function downloadBlob(blob: Blob, filename: string): void
blob
Blob
required
File data to download
filename
string
required
Suggested filename for download
Source: src/gridfinity/export3mf.ts:171-178 Example:
const blob = await exportTo3MF(meshes);
downloadBlob(blob, 'my-design.3mf');

3MF File Structure

The exported 3MF file follows the OPC (Open Packaging Conventions) ZIP structure:
my-design.3mf (ZIP archive)
├── [Content_Types].xml       # MIME type definitions
├── _rels/
│   └── .rels                 # Package relationships
├── 3D/
│   └── 3dmodel.model         # Main 3D model XML
└── Metadata/
    ├── model_settings.config  # BambuStudio detection key
    ├── project_settings.config
    └── slice_info.config

Key Files

[Content_Types].xml Defines MIME types for 3MF components:
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>
  <Default Extension="config" ContentType="text/xml"/>
</Types>
Source: src/gridfinity/export3mf.ts:105-111 3D/3dmodel.model Contains mesh geometry in 3MF XML format:
<model unit="millimeter" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02">
  <metadata name="Application">BambuStudio-02.05.00.66</metadata>
  <metadata name="Copyright">Gridfinity Builder</metadata>
  <resources>
    <object id="1" type="model">
      <mesh>
        <vertices>
          <vertex x="0.000000" y="0.000000" z="0.000000"/>
          <!-- ... more vertices ... -->
        </vertices>
        <triangles>
          <triangle v1="0" v2="1" v3="2"/>
          <!-- ... more triangles ... -->
        </triangles>
      </mesh>
    </object>
  </resources>
  <build>
    <item objectid="1"/>
  </build>
</model>
Source: src/gridfinity/export3mf.ts:144-165 Metadata/model_settings.config Critical for BambuStudio detection:
<config>
  <plate>
    <metadata key="plater_id" value="1"/>
    <metadata key="plater_name" value=""/>
    <metadata key="locked" value="false"/>
  </plate>
</config>
Source: src/gridfinity/export3mf.ts:121-129
Without model_settings.config, BambuStudio will reject the file. This file is required for proper import.

Multi-Object Export

The exporter supports multiple objects in a single 3MF file:

Object ID Scheme

Each mesh generates two objects:
  • Volume object (odd ID): Contains mesh geometry
  • Parent object (even ID): References volume as component, carries name
meshes.forEach((item, idx) => {
  const volumeId = idx * 2 + 1;  // 1, 3, 5, ...
  const objectId = idx * 2 + 2;  // 2, 4, 6, ...
  
  // Volume: mesh data
  // Parent: name and component reference
});
Source: src/gridfinity/export3mf.ts:39-86

Build Items

Only parent objects appear in the build plate:
<build>
  <item objectid="2" p:UUID="xxx"/>
  <item objectid="4" p:UUID="yyy"/>
  <item objectid="6" p:UUID="zzz"/>
</build>
Source: src/gridfinity/export3mf.ts:88-95

Usage Pattern

Complete workflow from Manifold geometry to downloaded 3MF:
import wasm from 'manifold-3d';
import { generateBinExport } from '@/gridfinity/binGeometry';
import { manifoldToTriangleMesh, exportTo3MF, downloadBlob } from '@/gridfinity/export3mf';
import { GF } from '@/gridfinity/constants';

// Initialize Manifold
const manifold = await wasm();

// Generate multiple bins
const bin1 = generateBinExport(manifold, {
  w: 2, d: 2, h: 3,
  cornerRadius: GF.BIN_CORNER_RADIUS,
  wallThickness: GF.WALL_THICKNESS,
  bottomThickness: GF.BOTTOM_THICKNESS,
});

const bin2 = generateBinExport(manifold, {
  w: 1, d: 4, h: 6,
  cornerRadius: GF.BIN_CORNER_RADIUS,
  wallThickness: GF.WALL_THICKNESS,
  bottomThickness: GF.BOTTOM_THICKNESS,
  dividersX: 0,
  dividersY: 3,
});

// Convert to meshes
const mesh1 = manifoldToTriangleMesh(bin1);
const mesh2 = manifoldToTriangleMesh(bin2);

// Clean up Manifold objects
bin1.delete();
bin2.delete();

// Export to 3MF
const blob = await exportTo3MF([
  { mesh: mesh1, name: 'Small Parts 2x2x3' },
  { mesh: mesh2, name: 'Screwdriver 1x4x6' },
]);

// Trigger download
downloadBlob(blob, 'gridfinity-bins.3mf');

BambuStudio Compatibility

The exporter includes specific metadata for BambuStudio:

Required Metadata

<metadata name="Application">BambuStudio-02.05.00.66</metadata>
<metadata name="BambuStudio:3mfVersion">2</metadata>
Source: src/gridfinity/export3mf.ts:151-152

Namespace Declarations

<model
  xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02"
  xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06"
  xmlns:BambuStudio="http://schemas.bambulab.com/package/2021"
  requiredextensions="p">
Source: src/gridfinity/export3mf.ts:145-149

UUID Generation

Each build item receives a unique UUID:
function generateUUID(): string {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}
Source: src/gridfinity/export3mf.ts:31-37

Coordinate System

The 3MF format uses millimeters with right-handed coordinates:
  • Units: millimeter (explicitly declared)
  • Origin: Bottom-left-front corner
  • Axes:
    • +X: Right
    • +Y: Back
    • +Z: Up
Manifold geometry is generated in this coordinate system, so no transformation is needed.

XML Escaping

Object names are XML-escaped to prevent parsing errors:
function escapeXml(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}
Source: src/gridfinity/export3mf.ts:27-29 Example:
const safeName = escapeXml('Bin <Small> & "Test"');
// Result: 'Bin &lt;Small&gt; &amp; &quot;Test&quot;'

Compression

The 3MF ZIP archive uses DEFLATE compression:
return await zip.generateAsync({
  type: 'blob',
  compression: 'DEFLATE',
});
Source: src/gridfinity/export3mf.ts:167 This balances file size with decompression speed.

Error Handling

The export functions do not throw errors for invalid geometry. Ensure:
  • Manifold objects are valid (no self-intersections)
  • Meshes are watertight (closed surfaces)
  • Vertex and triangle arrays are properly formed
Validation:
const isValid = manifoldObj.numVert() > 0 && manifoldObj.numTri() > 0;
if (!isValid) {
  console.error('Invalid manifold geometry');
}

Performance

Mesh Conversion

Manifold mesh extraction is fast (< 10ms for typical bins):
const mesh = manifoldObj.getMesh();
// Returns: { numVert, numProp, vertProperties, triVerts }

3MF Generation

JSZip operations are asynchronous and depend on mesh complexity:
  • Small bins (< 10k triangles): ~50ms
  • Large bins (> 50k triangles): ~200ms
  • Multiple objects: Linear scaling

Memory Usage

TriangleMesh stores raw Float32Array and Uint32Array:
const vertexBytes = mesh.vertices.byteLength;   // 4 bytes per float
const triangleBytes = mesh.triangles.byteLength; // 4 bytes per uint32
const totalMB = (vertexBytes + triangleBytes) / (1024 * 1024);
Typical bin: 2-5 MB in memory, 500 KB compressed in 3MF.

Build docs developers (and LLMs) love