GLTFExporter issue

Hi, I’m trying to export a scene with GLTFExporter, but the exported file is not being downloaded. The idea is that you can drag&drop any bitmap onto a mesh, adapt the scaling and position, and finally download the result as a glb file. The code is available here: three.js webgl - materials - matcap

Chrome inspector is reporting this error:
ER_webgl_materials_matcap_RepeatGUI_10.html:52
Uncaught ReferenceError: gltfExporter is not defined at exportGLTF (ER_webgl_materials_matcap_RepeatGUI_10.html:52:13)
at HTMLButtonElement. (ER_webgl_materials_matcap_RepeatGUI_10.html:72:21)

Any help much appreciated

This the code based on the matcap example:
`

three.js webgl - materials - matcap
<body>
	<div id="info">
		<a href="https://everyrealm.com/" target="_blank" rel="noopener">EveryRealm</a> Personae Texture Tool<br />
		Drag-and-drop JPG, PNG image files<br/>
        <button id="export_scene">Export Scene1</button>
        <label><input id="option_binary" name="visible" type="checkbox">Binary (<code>.glb</code>)</label>
	</div>

	<script type="module">

		import * as THREE from '../build/three.module.js';

		import { GUI } from './jsm/libs/lil-gui.module.min.js';
		import { OrbitControls } from './jsm/controls/OrbitControls.js';
		import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
        import { GLTFExporter } from './jsm/exporters/GLTFExporter.js';
		import { EXRLoader } from './jsm/loaders/EXRLoader.js';

		let mesh, renderer, scene, camera;

		const API = {
			color: 0xffffff, // sRGB
			exposure: 0.65, //brightness
            offsetX: 0,
			offsetY: 0,
			repeatX: 1,
			repeatY: 1,
			rotation: Math.PI / 4, // positive is counter-clockwise
			centerX: 0.5,
			centerY: 0.5
		};


        function exportGLTF( input ) {
            const exporter = new GLTFExporter();
            const options = {
            binary: true,
            maxTextureSize: 4096,
            //animations: [animationClip],
            includeCustomExtensions: true
        };
        console.log(options);
        gltfExporter.parse(input, function (result) {

            if (result instanceof ArrayBuffer) {

                saveArrayBuffer(result, 'scene.glb');

            } else {

                const output = JSON.stringify(result, null, 2);
                console.log(output);
                saveString(output, 'scene.gltf');

            }

        }, options);

    }
            
            document.getElementById( 'export_scene' ).addEventListener( 'click', function () {

                exportGLTF( scene );
                console.log( 'Button pressed' );

            } );

            const link = document.createElement( 'a' );
		link.style.display = 'none';
		document.body.appendChild( link ); // Firefox workaround, see #6594

		function save( blob, filename ) {

			link.href = URL.createObjectURL( blob );
			link.download = filename;
			link.click();

			// URL.revokeObjectURL( url ); breaks Firefox...

		}

		function saveString( text, filename ) {

			save( new Blob( [ text ], { type: 'text/plain' } ), filename );

		}

        function saveArrayBuffer( buffer, filename ) {

        save( new Blob( [ buffer ], { type: 'application/octet-stream' } ), filename );

        }
        //end ofExportCode
		init();

		function init() {
            
			// renderer
			renderer = new THREE.WebGLRenderer( { antialias: true } );
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			document.body.appendChild( renderer.domElement );

			// tone mapping
			renderer.toneMapping = THREE.ACESFilmicToneMapping;
		 	renderer.toneMappingExposure = API.exposure;

			renderer.outputEncoding = THREE.sRGBEncoding;

			// scene
			scene = new THREE.Scene();
            scene.name = 'scene';
			// camera
			camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
			camera.position.set( 0, 0, 13 );

			// controls
			const controls = new OrbitControls( camera, renderer.domElement );
			controls.addEventListener( 'change', render );
			controls.enableZoom = true;
			controls.enablePan = false;

			// manager
			const manager = new THREE.LoadingManager( render );

			// matcap
			const loaderEXR = new EXRLoader( manager );
			const matcap = loaderEXR.load( 'textures/matcaps/040full.exr' );

			// normalmap2
			const loader = new THREE.TextureLoader( manager );
           
			const normalmap2 = loader.load( 'models/gltf/LeePerrySmith/Suit_Normal_OpenGLFlipV.jpg' );
            const normalmap = loader.load( 'models/gltf/LeePerrySmith/grid.png' );
            normalmap.wrapS = normalmap.wrapT = THREE.RepeatWrapping;
			

			// model
			new GLTFLoader( manager ).load( 'models/gltf/LeePerrySmith/jacket4.glb', function ( gltf ) {

				mesh = gltf.scene.children[ 0 ];
				mesh.position.y = - 0.25;
                mesh.name = 'myMesh'; // <- set a name
				mesh.material = new THREE.MeshBasicMaterial( {

					color: new THREE.Color().setHex( API.color ).convertSRGBToLinear(),
					//matcap: matcap,
					map: normalmap,
                    normalmap: normalmap2,

				} );

				scene.add( mesh );

			} );



			// gui
			const gui = new GUI();

			gui.addColor( API, 'color' )
				.listen()
				.onChange( function () {

					mesh.material.color.set( API.color ).convertSRGBToLinear();
					render();

				} );

			gui.add( API, 'exposure', 0, 2 )
				.onChange( function () {

					renderer.toneMappingExposure = API.exposure;
					render();

				} );

            gui.add( API, 'offsetX', 0.0, 1.0 )
                .name( 'offset.x' )
                .onChange( updateUvTransform );
			gui.add( API, 'offsetY', 0.0, 1.0 ).name( 'offset.y' ).onChange( updateUvTransform );
			gui.add( API, 'repeatX', 0.25, 20.0 ).name( 'repeat.x' ).onChange( updateUvTransform );
			gui.add( API, 'repeatY', 0.25, 20.0 ).name( 'repeat.y' ).onChange( updateUvTransform );
			gui.add( API, 'rotation', - 3.5, 3.5 ).name( 'rotation' ).onChange( updateUvTransform );
			gui.add( API, 'centerX', 0.0, 1.0 ).name( 'center.x' ).onChange( updateUvTransform );
			gui.add( API, 'centerY', 0.0, 1.0 ).name( 'center.y' ).onChange( updateUvTransform );


			gui.domElement.style.webkitUserSelect = 'none';

			// drag 'n drop
			initDragAndDrop();
            //
            updateUvTransform();
			window.addEventListener( 'resize', onWindowResize );

		}

		function onWindowResize() {

			renderer.setSize( window.innerWidth, window.innerHeight );

			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();

			render();

		}

		function render() {

			renderer.render( scene, camera );

		}

		//
        function updateUvTransform() {

        const texture = mesh.material.map;
        texture.wrapS = texture.wrapT = THREE.RepeatWrapping;

            if ( texture.matrixAutoUpdate === true ) {

             texture.offset.set( API.offsetX, API.offsetY );
             texture.repeat.set( API.repeatX, API.repeatY );
             //texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
                    //texture.wrapS = THREE.RepeatWrapping;
                    //texture.wrapT = THREE.RepeatWrapping;
          
             texture.center.set( API.centerX, API.centerY );
             texture.rotation = API.rotation; // rotation is around [ 0.5, 0.5 ]

        } else {

             // one way...
            //texture.matrix.setUvTransform( API.offsetX, API.offsetY, API.repeatX, API.repeatY, API.rotation, API.centerX, API.centerY );

              // another way...
               texture.matrix
           .identity()
          .translate( - API.centerX, - API.centerY )
          .rotate( API.rotation )					// I don't understand how rotation can preceed scale, but it seems to be required...
          .scale( API.repeatX, API.repeatY )
          .translate( API.centerX, API.centerY )
          .translate( API.offsetX, API.offsetY );

                }

        render();

        }

		// drag and drop anywhere in document
		//

		function updateBasicMaterial( texture ) {

			if ( mesh.material.map ) {

				mesh.material.map.dispose();

			}

			mesh.material.map = texture;
            

            
			texture.needsUpdate = true;
            
			

			mesh.material.needsUpdate = true; // because the encoding can change

			render();

		}


		function handleJPG( event ) { // PNG, too

			function imgCallback( event ) {

				const texture = new THREE.Texture( event.target );
                texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
				texture.encoding = THREE.sRGBEncoding;
                
				updateBasicMaterial( texture );

			}

			const img = new Image();

			img.onload = imgCallback;

			img.src = event.target.result;

		}

		function handleEXR( event ) {

			const contents = event.target.result;

			const loader = new EXRLoader();

			loader.setDataType( THREE.HalfFloatType );

			const texData = loader.parse( contents );

			const texture = new THREE.DataTexture();

			texture.image.width = texData.width;
			texture.image.height = texData.height;
			texture.image.data = texData.data;

			texture.format = texData.format;
			texture.type = texData.type;
			texture.encoding = THREE.LinearEncoding;
			texture.minFilter = THREE.LinearFilter;
			texture.magFilter = THREE.LinearFilter;
            
			texture.generateMipmaps = false;
			texture.flipY = false;

			updatemap( texture );

		}

		function loadFile( file ) {

			const filename = file.name;
			const extension = filename.split( '.' ).pop().toLowerCase();

			if ( extension === 'exr' ) {

				const reader = new FileReader();

				reader.addEventListener( 'load', function ( event ) {

					handleEXR( event );

				} );

				reader.readAsArrayBuffer( file );

			} else { // 'jpg', 'png'

				const reader = new FileReader();

				reader.addEventListener( 'load', function ( event ) {

					handleJPG( event );

				} );

				reader.readAsDataURL( file );

			}

		}

		function initDragAndDrop() {

			document.addEventListener( 'dragover', function ( event ) {

				event.preventDefault();
				event.dataTransfer.dropEffect = 'copy';

			} );

			document.addEventListener( 'drop', function ( event ) {

				event.preventDefault();

				loadFile( event.dataTransfer.files[ 0 ] );

			} );

		}

	</script>

</body>
`

Write this line like so:

exporter.parse(input, function (result) {
1 Like

…and just like that it works. Thank you so much