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
Flat array of vertex positions (3 floats per vertex: x, y, z)
Flat array of triangle vertex indices (3 indices per triangle)
Functions
manifoldToTriangleMesh
Converts a Manifold object to TriangleMesh format.
function manifoldToTriangleMesh(manifoldObj: any): TriangleMesh
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
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:
<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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
Source: src/gridfinity/export3mf.ts:27-29
Example:
const safeName = escapeXml('Bin <Small> & "Test"');
// Result: 'Bin <Small> & "Test"'
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');
}
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.