Package Exports
- r3f-glb-exporter
- r3f-glb-exporter/dist/index.esm.js
- r3f-glb-exporter/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (r3f-glb-exporter) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
R3f-to-glb
A drop-in GLB/GLTF export button and utility library for React Three Fiber scenes.
It clones your scene, cleans up internal R3F/Three.js objects (lights, cameras, CSG duplicates, wireframes, invisible meshes, helpers, empty groups), deduplicates materials, assigns readable names, and exports a clean .glb file ready for Blender, game engines, or any 3D tool.
Installation
npm install r3f-glb-exporterPeer Dependencies
Your project must already have these installed:
npm install react react-dom three @react-three/fiberNote:
@react-three/dreiis not required. The export button works as a standard HTML element outside the Canvas.
Quick Start
Wrap your app with <SceneProvider>, place <SceneCapture /> inside the Canvas, and put <ExportButton /> anywhere you want — sidebar, toolbar, header, etc.
import React from 'react';
import { Canvas } from '@react-three/fiber';
import { SceneProvider, SceneCapture, ExportButton } from 'r3f-glb-exporter';
function App() {
return (
<SceneProvider>
<div style={{ display: 'flex' }}>
<aside style={{ padding: 20 }}>
<h2>Controls</h2>
<ExportButton filename="my-model" className="my-btn">
Export GLB
</ExportButton>
</aside>
<main style={{ flex: 1 }}>
<Canvas>
<SceneCapture />
<ambientLight />
<mesh>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
</Canvas>
</main>
</div>
</SceneProvider>
);
}ExportButton
<ExportButton> is a completely unstyled <button> element — no default colors, padding, or shadows. You own the styling entirely. Every standard HTML button attribute is forwarded directly to the underlying <button> tag.
Styling
Use whatever approach your project already uses:
{/* Tailwind */}
<ExportButton filename="model" className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50">
Export GLB
</ExportButton>
{/* Inline style */}
<ExportButton filename="model" style={{ background: 'navy', color: '#fff', padding: '8px 16px' }}>
Export GLB
</ExportButton>
{/* CSS module */}
<ExportButton filename="model" className={styles.exportBtn}>
Export GLB
</ExportButton>The button is automatically disabled while the export is running. Style the disabled state however you want (disabled:opacity-50, CSS :disabled, etc.).
Native button attributes
Any valid <button> attribute passes through: id, name, className, style, aria-label, data-*, onMouseEnter, onFocus, tabIndex, title, form, and so on.
<ExportButton
filename="model"
id="export-btn"
name="export"
aria-label="Download 3D model"
title="Export to GLB"
tabIndex={0}
data-testid="export-button"
className={styles.btn}
onClick={(e) => console.log('clicked', e)}
>
Download
</ExportButton>Export & cleanup props
| Prop | Type | Default | Description |
|---|---|---|---|
filename |
string |
"scene" |
Output filename without .glb extension |
showStats |
boolean |
true |
Log scene stats (vertices, triangles, meshes, textures) to the console |
Scene cleanup props
All default to true for the cleanest possible output. Set any to false to keep those objects in the export.
| Prop | Type | Default | What it does |
|---|---|---|---|
removeHelpers |
boolean |
true |
Removes GridHelper, AxesHelper, BoxHelper, and other helper objects |
removeCameras |
boolean |
true |
Removes camera objects |
removeLights |
boolean |
true |
Removes lights and their targets (fixes sun / sun.001 nodes in Blender) |
removeCSGChildren |
boolean |
true |
Removes duplicate meshes from @react-three/csg (operands stored as mesh children) |
removeInvisibleMeshes |
boolean |
true |
Removes meshes with visible={false}, fully transparent materials, or hidden materials |
removeLineObjects |
boolean |
true |
Removes LineSegments, Line2, and Line objects (selection outlines, highlights) |
removeWireframeMeshes |
boolean |
true |
Removes meshes where all materials have wireframe: true (GLTF does not support wireframe) |
assignReadableNames |
boolean |
true |
Auto-names unnamed objects based on geometry type and material color (e.g. Box_#ff6600) |
mergeMeshesInGroups |
boolean |
true |
Merges all meshes inside each group into a single mesh (protects IP — clients can't inspect individual components) |
removeEmptyGroups |
boolean |
true |
Removes Group / Object3D nodes that contain no mesh descendants. Empty groups create useless nodes in Blender |
deduplicateMaterials |
boolean |
true |
When multiple meshes share a material with the same name, they are all reassigned to one shared material instance. Prevents _2 / _3 postfix duplicates in Blender |
Example — opt out of specific cleanup
<ExportButton
filename="my-scene"
removeLights={false} // keep lights in the export
removeCSGChildren={false} // keep CSG child meshes
assignReadableNames={false} // keep original Three.js names
mergeMeshesInGroups={false} // export individual meshes (don't merge)
removeEmptyGroups={false} // keep empty group nodes
deduplicateMaterials={false} // allow duplicate material instances
/>Using the Hook Directly
Build your own UI with the useGLBExport hook — it works anywhere inside <SceneProvider>:
import { useGLBExport } from 'r3f-glb-exporter';
function CustomDownloadButton() {
const { exportScene, isExporting, error } = useGLBExport();
return (
<button
onClick={() => exportScene({ filename: 'my-door' })}
disabled={isExporting}
>
{isExporting ? 'Exporting...' : 'Download GLB'}
</button>
);
}Using an External Scene Ref
import React, { useRef } from 'react';
import * as THREE from 'three';
import { SceneProvider, SceneCapture, ExportButton } from 'r3f-glb-exporter';
function App() {
const sceneRef = useRef<THREE.Scene | null>(null);
return (
<SceneProvider sceneRef={sceneRef}>
<ExportButton filename="dashboard-model" className={styles.btn}>
Export
</ExportButton>
<Canvas>
<SceneCapture />
{/* your scene */}
</Canvas>
</SceneProvider>
);
}Advanced: Utility Functions
prepareSceneForExport
Clones and cleans the scene without exporting. Returns the cleaned clone.
import { useThree } from '@react-three/fiber';
import { prepareSceneForExport } from 'r3f-glb-exporter';
function MyComponent() {
const { scene } = useThree();
const handleCleanup = () => {
const cleanScene = prepareSceneForExport(scene, {
removeLights: false,
assignReadableNames: true,
removeEmptyGroups: true,
deduplicateMaterials: true,
});
console.log(cleanScene);
};
}exportToGLB
Exports and triggers a browser file download.
import { prepareSceneForExport, exportToGLB } from 'r3f-glb-exporter';
const cleanScene = prepareSceneForExport(scene);
await exportToGLB(cleanScene, {
filename: 'my-model',
binary: true,
maxTextureSize: 4096,
onStart: () => console.log('Export started...'),
onComplete: (blob) => console.log(`Done! ${blob.size} bytes`),
onError: (err) => console.error('Failed:', err),
});exportToGLBBlob
Returns the blob without triggering a download. Useful for server uploads.
import { prepareSceneForExport, exportToGLBBlob } from 'r3f-glb-exporter';
const cleanScene = prepareSceneForExport(scene);
const blob = await exportToGLBBlob(cleanScene);
await fetch('/api/upload-model', {
method: 'POST',
body: blob,
});getSceneStats
Returns vertex, triangle, mesh, and texture counts.
import { getSceneStats } from 'r3f-glb-exporter';
const stats = getSceneStats(scene);
// { vertices: 12450, triangles: 8300, meshes: 42, textures: 5 }GLBExportOptions
Advanced options for exportToGLB / exportToGLBBlob:
| Option | Type | Default | Description |
|---|---|---|---|
filename |
string |
"scene" |
Output filename (without extension) |
binary |
boolean |
true |
true → .glb binary, false → .gltf JSON |
maxTextureSize |
number |
4096 |
Maximum texture dimension in pixels |
onlyVisible |
boolean |
false |
Only export visible objects |
animations |
AnimationClip[] |
[] |
Animation clips to include |
trs |
boolean |
false |
Export TRS instead of matrix |
includeCustomExtensions |
boolean |
false |
Include custom glTF extensions |
onStart |
() => void |
— | Called when export begins |
onComplete |
(blob: Blob) => void |
— | Called with result blob on success |
onError |
(error: Error) => void |
— | Called if export fails |
What Gets Cleaned Up
| Problem | Cleanup Option | Why it matters |
|---|---|---|
sun / sun.001 nodes in Blender |
removeLights |
Three.js DirectionalLight creates a target Object3D that exports as a separate node |
| Duplicate meshes (mesh inside mesh) | removeCSGChildren |
@react-three/csg stores operand meshes as children even though the parent already has the computed result |
| Solid boxes covering the model | removeWireframeMeshes |
GLTF doesn't support wireframe rendering — wireframe boxes export as solid filled geometry |
| Invisible geometry in export | removeInvisibleMeshes |
Click targets and interaction planes with visible={false} still get exported |
| GridHelper, AxesHelper in export | removeHelpers |
Development helpers shouldn't appear in production exports |
| Selection outlines in export | removeLineObjects |
Line-based highlights from the editor |
Generic Node_0, Obj_1 names |
assignReadableNames |
Three.js doesn't name objects by default |
| Clients can inspect mesh details | mergeMeshesInGroups |
Merges all meshes per group so internal structure is hidden |
| Empty group nodes in Blender | removeEmptyGroups |
Groups left empty after cleanup produce useless nodes |
Material_2, Material_3 duplicates |
deduplicateMaterials |
Identical materials get deduplicated to one shared instance, eliminating _2 / _3 postfixes |
License
MIT