Skip to content

Instantly share code, notes, and snippets.

@donmccurdy
Last active January 15, 2025 22:26
Show Gist options
  • Save donmccurdy/9f094575c1f1a48a2ddda513898f6496 to your computer and use it in GitHub Desktop.
Save donmccurdy/9f094575c1f1a48a2ddda513898f6496 to your computer and use it in GitHub Desktop.
Example Node.js glTF conversion script, using three.js.
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const THREE = require('three');
const program = require('commander');
const Canvas = require('canvas');
const { Blob, FileReader } = require('vblob');
// Patch global scope to imitate browser environment.
global.window = global;
global.Blob = Blob;
global.FileReader = FileReader;
global.THREE = THREE;
global.document = {
createElement: (nodeName) => {
if (nodeName !== 'canvas') throw new Error(`Cannot create node ${nodeName}`);
const canvas = new Canvas(256, 256);
// This isn't working — currently need to avoid toBlob(), so export to embedded .gltf not .glb.
// canvas.toBlob = function () {
// return new Blob([this.toBuffer()]);
// };
return canvas;
}
};
// https://github.com/mrdoob/three.js/issues/9562
require('three/examples/js/exporters/GLTFExporter');
program
.version('0.0.1')
.usage('[options] <file>')
.option('-o, --output <file>', 'output filename', String)
// .option('-b, --binary', 'use binary (.glb) format (default false)')
.option('-m, --myoption', 'my custom option (default 1.0)', Number)
.parse(process.argv);
program.binary = !!program.binary;
program.myoption = program.myoption || 1;
const inputPath = program.args[0];
if (!inputPath) { program.help(); }
if (!program.output) {
program.output = path.basename(inputPath, '.foo');
program.output += program.binary ? '.glb' : '.gltf';
}
console.log(' → input: %j', program.args);
console.log(' → output: %j', program.output);
console.log(' → binary: %j', program.binary);
console.log(' → my custom option: %j', program.myoption);
console.log(`Loading "${inputPath}"`);
let mesh = new THREE.Mesh(...); // (load or create mesh here)
console.log('Converting to glTF');
const exporter = new THREE.GLTFExporter();
exporter.parse(mesh, (content) => {
console.log(`Writing to ${program.output}`);
if (typeof content === 'object') content = JSON.stringify(content);
fs.writeFileSync(program.output, content);
}, {binary: program.binary});
@donmccurdy
Copy link
Author

@sanghiad if you mean a three.js THREE.Mesh instance, then, sort of. A glTF file may have multiple meshes in it, or a combination of meshes and other things (materials, animations, lights, etc.). The typical way to turn all of that into three.js objects, including THREE.Mesh where appropriate, is THREE.GLTFLoader.

If you mean turning a glTF file into some other file type, I would recommend using Blender or other 3D software to do conversions.

@apederse
Copy link

apederse commented Mar 5, 2023

@donmccurdy @mrdoob Really useful to work with Three outside of the browser. To get the above gist working in Node 18 a couple of tweaks are needed. Node 14 introduced a native Blob object and vblob exports global.Blob if present. However, the vblob FileReader expects a VBlob class to work properly. Workaround to force use of VBlob is to set global.Blob=null before importing the vblob module. But I guess this workaround may introduce issues if you rely on the Node Blob class for other purposes. Issue filed here: karikera/vblob#1

@dirkk0
Copy link

dirkk0 commented Jan 7, 2025

I am working on a 3D editor with nw.js. The editor part is 'normal' JS with THREE.js and right now I am using nw.js only to write the file to a specific directory. From that, I can report that

const exporter = new GLTFExporter()
exporter.parse(
  scene,
  function (gltf) {
    const destinationFolder = path.join(require("os").homedir(), "Documents")
    const filepath = path.join(destinationFolder, "example.gltf")
    fs.writeFileSync(filepath, JSON.stringify(gltf))
  },
  { binary: true }
)

properly exports gltf including textures.

@DanielAtCosmicDNA
Copy link

Hi. I was able to reduce it a little bit further:

Foo2Gltf.mjs:

import { writeFileSync } from 'fs';
import { basename } from 'path';
import { GLTFExporter }  from 'three/examples/jsm/exporters/GLTFExporter.js'
import { FileReader } from 'vblob';

// Patch global scope to imitate browser environment.
global.FileReader = FileReader;

const exportMeshToGLTF = (mesh, outputPath, options = { binary: false }) => {
  // Create an instance of the GLTFExporter
  const exporter = new GLTFExporter();

  // Define the output file name
  let output = outputPath;
  if (!output) {
    output = basename('output', '.foo');
    output += options.binary ? '.glb' : '.gltf';
  }

  console.log('Converting to glTF');
  exporter.parse(
    mesh,
    (content) => {
      console.log(`Writing to ${output}`);
      if (typeof content === 'object') content = JSON.stringify(content);
      writeFileSync(output, content);
      console.log('GLTF file has been saved.');
    },
    options
  );
};

export default exportMeshToGLTF;

and usage is:

index.mjs:

import { MeshBasicMaterial, BoxGeometry, Mesh } from 'three'
import exportMeshToGLTF from './Foo2Gltf.mjs';

// Create a geometry, for example, a box geometry
const geometry = new BoxGeometry();
const material = new MeshBasicMaterial({ color: 0x00ff00 });
const mesh = new Mesh(geometry, material);

exportMeshToGLTF(mesh, "cube.gltf")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment