Skip to content

Instantly share code, notes, and snippets.

@donmccurdy
Last active August 1, 2023 06:53
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • 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});
@fraguada
Copy link

Thanks for this gist. I've been able to adapt it to a node.js experiment. I'm trying the same with GLTFLoader, specifically with .parse, but I'm running into issues with ImageLoader needing a document, and specifically with .createElementNS().

@donmccurdy
Copy link
Author

I haven't really used GLTFLoader in node.js – you might need to swap out the texture loader unfortunately. Maybe some helpful bits in mrdoob/three.js#15552 too.

@fraguada
Copy link

Thanks for the hints. I got a good ways of the way there, but it seemed pretty hacky. I had to patch stuff with JSDOM. After a few patches I decided to stop. Maybe in the future three.js will be friendlier for node.js! I'll clean up what I did to harvest the GLTFExporter and leave it as an example.

@loadpixels
Copy link

loadpixels commented Aug 30, 2019

@donmccurdy - thanks for posting this code. @fraguada - I ran into the same problem as you, it's because the JSDOM library populates the global.Blob and window.FileReader spaces with it's own implementation. To fix this, I just copied the GLTFExporter.js file into my local source folder and then added the Vblob library to the top of the file like so;

/**
 * @author fernandojsg / http://fernandojsg.com
 * @author Don McCurdy / https://www.donmccurdy.com
 * @author Takahiro / https://github.com/takahirox
 */
const {
  Blob,
  FileReader
} = require('vblob')
global.Blob = Blob
window.FileReader = FileReader
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
... etc

Then I just require('./GLTFExporter.js') in my main script instead of the three/examples/js... version

Hope that helps!

@fraguada
Copy link

fraguada commented Sep 6, 2019

@loadpixels thanks for the tip!

@mrdoob
Copy link

mrdoob commented Jan 21, 2020

I had to modify the code to get binary to work:

exporter.parse(mesh, (content) => {

  console.log(`Writing to ${program.output}`);
  if (program.binary) {
    fs.writeFileSync(program.output, Buffer.from(content), 'binary');
  } else {
    fs.writeFileSync(program.output, JSON.stringify(content), 'utf8');
  }

}, {binary: program.binary});

@donmccurdy
Copy link
Author

Thanks! The binary option is commented out at the moment, because I wasn't able to get models with textures working with it. 😕

@mrdoob
Copy link

mrdoob commented Jan 24, 2020

Ah, the models I'm dealing with only use vertex colors 😇

@ashconnell
Copy link

ashconnell commented Feb 4, 2020

This also falls over if you use any other libraries that look for window to determine if it's running in the browser. In my case there are a few, one of them is an emscripten output.

@forsnowlove
Copy link

I had to modify the code to get binary to work:

exporter.parse(mesh, (content) => {

  console.log(`Writing to ${program.output}`);
  if (program.binary) {
    fs.writeFileSync(program.output, Buffer.from(content), 'binary');
  } else {
    fs.writeFileSync(program.output, JSON.stringify(content), 'utf8');
  }

}, {binary: program.binary});

hey,I tried this code,but got err when run Buffer.form(content) :The first argument must be one of type string,Buffer,Array... Can you help me how to deal with it ?

@hcwiley
Copy link

hcwiley commented Mar 19, 2020

thanks for this great starter! i made an "everything to everything" script. currently supports stl, ply, obj, and glb/gltf: https://github.com/Scandy-co/vr-playground/blob/master/asset-mgmt/assetConverter.js

@hcwiley
Copy link

hcwiley commented Mar 20, 2020

also thanks for this gist: https://gist.github.com/donmccurdy/323c6363ac7ca8a7de6a3362d7fdddb4
I've included some of the patch on DracoLoader in my "everything to everything" assetConverter, but still struggling with some conversions back and forth.

@sanghiad
Copy link

I was wondering if there is a way to convert gltf file into a mesh?

@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

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