import * as THREE from 'three'; import { TGALoader } from 'three/addons/loaders/TGALoader.js'; import { AddObjectCommand } from './commands/AddObjectCommand.js'; import { SetSceneCommand } from './commands/SetSceneCommand.js'; import { LoaderUtils } from './LoaderUtils.js'; import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js'; function Loader( editor ) { const scope = this; this.texturePath = ''; this.loadItemList = function ( items ) { LoaderUtils.getFilesFromItemList( items, function ( files, filesMap ) { scope.loadFiles( files, filesMap ); } ); }; this.loadFiles = function ( files, filesMap ) { debugger; if ( files.length > 0 ) { filesMap = filesMap || LoaderUtils.createFilesMap( files ); const manager = new THREE.LoadingManager(); manager.setURLModifier( function ( url ) { url = url.replace( /^(\.?\/)/, '' ); // remove './' const file = filesMap[ url ]; if ( file ) { console.log( 'Loading', url ); return URL.createObjectURL( file ); } return url; } ); manager.addHandler( /\.tga$/i, new TGALoader() ); for ( let i = 0; i < files.length; i ++ ) { scope.loadFile( files[ i ], manager ); } } }; this.loadFile = function ( file, manager ) { const filename = file.name; const extension = filename.split( '.' ).pop().toLowerCase(); const reader = new FileReader(); reader.addEventListener( 'progress', function ( event ) { const size = '(' + Math.floor( event.total / 1000 ).format() + ' KB)'; const progress = Math.floor( ( event.loaded / event.total ) * 100 ) + '%'; console.log( 'Loading', filename, size, progress ); } ); switch ( extension ) { case '3dm': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { Rhino3dmLoader } = await import( 'three/addons/loaders/3DMLoader.js' ); const loader = new Rhino3dmLoader(); loader.setLibraryPath( '../examples/jsm/libs/rhino3dm/' ); loader.parse( contents, function ( object ) { editor.execute( new AddObjectCommand( editor, object ) ); } ); }, false ); reader.readAsArrayBuffer( file ); break; } case '3ds': { reader.addEventListener( 'load', async function ( event ) { const { TDSLoader } = await import( 'three/addons/loaders/TDSLoader.js' ); const loader = new TDSLoader(); const object = loader.parse( event.target.result ); editor.execute( new AddObjectCommand( editor, object ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case '3mf': { reader.addEventListener( 'load', async function ( event ) { const { ThreeMFLoader } = await import( 'three/addons/loaders/3MFLoader.js' ); const loader = new ThreeMFLoader(); const object = loader.parse( event.target.result ); editor.execute( new AddObjectCommand( editor, object ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'amf': { reader.addEventListener( 'load', async function ( event ) { const { AMFLoader } = await import( 'three/addons/loaders/AMFLoader.js' ); const loader = new AMFLoader(); const amfobject = loader.parse( event.target.result ); editor.execute( new AddObjectCommand( editor, amfobject ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'dae': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { ColladaLoader } = await import( 'three/addons/loaders/ColladaLoader.js' ); const loader = new ColladaLoader( manager ); const collada = loader.parse( contents ); collada.scene.name = filename; editor.execute( new AddObjectCommand( editor, collada.scene ) ); }, false ); reader.readAsText( file ); break; } case 'drc': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' ); const loader = new DRACOLoader(); loader.setDecoderPath( '../examples/js/libs/draco/' ); loader.decodeDracoFile( contents, function ( geometry ) { let object; if ( geometry.index !== null ) { const material = new THREE.MeshStandardMaterial(); object = new THREE.Mesh( geometry, material ); object.name = filename; } else { const material = new THREE.PointsMaterial( { size: 0.01 } ); material.vertexColors = geometry.hasAttribute( 'color' ); object = new THREE.Points( geometry, material ); object.name = filename; } loader.dispose(); editor.execute( new AddObjectCommand( editor, object ) ); } ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'fbx': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' ); const loader = new FBXLoader( manager ); const object = loader.parse( contents ); editor.execute( new AddObjectCommand( editor, object ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'glb': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' ); const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' ); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( '../examples/js/libs/draco/gltf/' ); const loader = new GLTFLoader(); loader.setDRACOLoader( dracoLoader ); loader.parse( contents, '', function ( result ) { const scene = result.scene; scene.name = filename; scene.animations.push( ...result.animations ); editor.execute( new AddObjectCommand( editor, scene ) ); } ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'gltf': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; let loader; if ( isGLTF1( contents ) ) { alert( 'Import of glTF asset not possible. Only versions >= 2.0 are supported. Please try to upgrade the file to glTF 2.0 using glTF-Pipeline.' ); } else { const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' ); const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' ); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( '../examples/js/libs/draco/gltf/' ); loader = new GLTFLoader( manager ); loader.setDRACOLoader( dracoLoader ); } loader.parse( contents, '', function ( result ) { const scene = result.scene; scene.name = filename; scene.animations.push( ...result.animations ); editor.execute( new AddObjectCommand( editor, scene ) ); } ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'js': case 'json': { reader.addEventListener( 'load', function ( event ) { const contents = event.target.result; // 2.0 if ( contents.indexOf( 'postMessage' ) !== - 1 ) { const blob = new Blob( [ contents ], { type: 'text/javascript' } ); const url = URL.createObjectURL( blob ); const worker = new Worker( url ); worker.onmessage = function ( event ) { event.data.metadata = { version: 2 }; handleJSON( event.data ); }; worker.postMessage( Date.now() ); return; } // >= 3.0 let data; try { data = JSON.parse( contents ); } catch ( error ) { alert( error ); return; } handleJSON( data ); }, false ); reader.readAsText( file ); break; } case 'ifc': { reader.addEventListener( 'load', async function ( event ) { const { IFCLoader } = await import( 'three/addons/loaders/IFCLoader.js' ); var loader = new IFCLoader(); loader.ifcManager.setWasmPath( 'three/addons/loaders/ifc/' ); const model = await loader.parse( event.target.result ); model.mesh.name = filename; editor.execute( new AddObjectCommand( editor, model.mesh ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'kmz': { reader.addEventListener( 'load', async function ( event ) { const { KMZLoader } = await import( 'three/addons/loaders/KMZLoader.js' ); const loader = new KMZLoader(); const collada = loader.parse( event.target.result ); collada.scene.name = filename; editor.execute( new AddObjectCommand( editor, collada.scene ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'ldr': case 'mpd': { reader.addEventListener( 'load', async function ( event ) { const { LDrawLoader } = await import( 'three/addons/loaders/LDrawLoader.js' ); const loader = new LDrawLoader(); loader.setPath( '../../examples/models/ldraw/officialLibrary/' ); loader.parse( event.target.result, undefined, function ( group ) { group.name = filename; // Convert from LDraw coordinates: rotate 180 degrees around OX group.rotation.x = Math.PI; editor.execute( new AddObjectCommand( editor, group ) ); } ); }, false ); reader.readAsText( file ); break; } case 'md2': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { MD2Loader } = await import( 'three/addons/loaders/MD2Loader.js' ); const geometry = new MD2Loader().parse( contents ); const material = new THREE.MeshStandardMaterial(); const mesh = new THREE.Mesh( geometry, material ); mesh.mixer = new THREE.AnimationMixer( mesh ); mesh.name = filename; mesh.animations.push( ...geometry.animations ); editor.execute( new AddObjectCommand( editor, mesh ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'obj': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' ); const object = new OBJLoader().parse( contents ); object.name = filename; editor.execute( new AddObjectCommand( editor, object ) ); }, false ); reader.readAsText( file ); break; } case 'pcd': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { PCDLoader } = await import( '../../examples/jsm/loaders/PCDLoader.js' ); const points = new PCDLoader().parse( contents ); points.name = filename; editor.execute( new AddObjectCommand( editor, points ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'ply': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { PLYLoader } = await import( 'three/addons/loaders/PLYLoader.js' ); const geometry = new PLYLoader().parse( contents ); let object; if ( geometry.index !== null ) { const material = new THREE.MeshStandardMaterial(); object = new THREE.Mesh( geometry, material ); object.name = filename; } else { const material = new THREE.PointsMaterial( { size: 0.01 } ); material.vertexColors = geometry.hasAttribute( 'color' ); object = new THREE.Points( geometry, material ); object.name = filename; } editor.execute( new AddObjectCommand( editor, object ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'stl': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { STLLoader } = await import( 'three/addons/loaders/STLLoader.js' ); const geometry = new STLLoader().parse( contents ); const material = new THREE.MeshStandardMaterial(); const mesh = new THREE.Mesh( geometry, material ); mesh.name = filename; editor.execute( new AddObjectCommand( editor, mesh ) ); }, false ); if ( reader.readAsBinaryString !== undefined ) { reader.readAsBinaryString( file ); } else { reader.readAsArrayBuffer( file ); } break; } case 'svg': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { SVGLoader } = await import( 'three/addons/loaders/SVGLoader.js' ); const loader = new SVGLoader(); const paths = loader.parse( contents ).paths; // const group = new THREE.Group(); group.scale.multiplyScalar( 0.1 ); group.scale.y *= - 1; for ( let i = 0; i < paths.length; i ++ ) { const path = paths[ i ]; const material = new THREE.MeshBasicMaterial( { color: path.color, depthWrite: false } ); const shapes = SVGLoader.createShapes( path ); for ( let j = 0; j < shapes.length; j ++ ) { const shape = shapes[ j ]; const geometry = new THREE.ShapeGeometry( shape ); const mesh = new THREE.Mesh( geometry, material ); group.add( mesh ); } } editor.execute( new AddObjectCommand( editor, group ) ); }, false ); reader.readAsText( file ); break; } case 'usdz': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { USDZLoader } = await import( '../../examples/jsm/loaders/USDZLoader.js' ); const group = new USDZLoader().parse( contents ); group.name = filename; editor.execute( new AddObjectCommand( editor, group ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'vox': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { VOXLoader, VOXMesh } = await import( 'three/addons/loaders/VOXLoader.js' ); const chunks = new VOXLoader().parse( contents ); const group = new THREE.Group(); group.name = filename; for ( let i = 0; i < chunks.length; i ++ ) { const chunk = chunks[ i ]; const mesh = new VOXMesh( chunk ); group.add( mesh ); } editor.execute( new AddObjectCommand( editor, group ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'vtk': case 'vtp': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { VTKLoader } = await import( 'three/addons/loaders/VTKLoader.js' ); const geometry = new VTKLoader().parse( contents ); const material = new THREE.MeshStandardMaterial(); const mesh = new THREE.Mesh( geometry, material ); mesh.name = filename; editor.execute( new AddObjectCommand( editor, mesh ) ); }, false ); reader.readAsArrayBuffer( file ); break; } case 'wrl': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { VRMLLoader } = await import( 'three/addons/loaders/VRMLLoader.js' ); const result = new VRMLLoader().parse( contents ); editor.execute( new SetSceneCommand( editor, result ) ); }, false ); reader.readAsText( file ); break; } case 'xyz': { reader.addEventListener( 'load', async function ( event ) { const contents = event.target.result; const { XYZLoader } = await import( 'three/addons/loaders/XYZLoader.js' ); const geometry = new XYZLoader().parse( contents ); const material = new THREE.PointsMaterial(); material.vertexColors = geometry.hasAttribute( 'color' ); const points = new THREE.Points( geometry, material ); points.name = filename; editor.execute( new AddObjectCommand( editor, points ) ); }, false ); reader.readAsText( file ); break; } case 'zip': { reader.addEventListener( 'load', function ( event ) { handleZIP( event.target.result ); }, false ); reader.readAsArrayBuffer( file ); break; } default: console.error( 'Unsupported file format (' + extension + ').' ); break; } }; function handleJSON( data ) { if ( data.metadata === undefined ) { // 2.0 data.metadata = { type: 'Geometry' }; } if ( data.metadata.type === undefined ) { // 3.0 data.metadata.type = 'Geometry'; } if ( data.metadata.formatVersion !== undefined ) { data.metadata.version = data.metadata.formatVersion; } switch ( data.metadata.type.toLowerCase() ) { case 'buffergeometry': { const loader = new THREE.BufferGeometryLoader(); const result = loader.parse( data ); const mesh = new THREE.Mesh( result ); editor.execute( new AddObjectCommand( editor, mesh ) ); break; } case 'geometry': console.error( 'Loader: "Geometry" is no longer supported.' ); break; case 'object': { const loader = new THREE.ObjectLoader(); loader.setResourcePath( scope.texturePath ); loader.parse( data, function ( result ) { if ( result.isScene ) { editor.execute( new SetSceneCommand( editor, result ) ); } else { editor.execute( new AddObjectCommand( editor, result ) ); } } ); break; } case 'app': editor.fromJSON( data ); break; } } async function handleZIP( contents ) { const zip = unzipSync( new Uint8Array( contents ) ); // Poly if ( zip[ 'model.obj' ] && zip[ 'materials.mtl' ] ) { const { MTLLoader } = await import( 'three/addons/loaders/MTLLoader.js' ); const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' ); const materials = new MTLLoader().parse( strFromU8( zip[ 'materials.mtl' ] ) ); const object = new OBJLoader().setMaterials( materials ).parse( strFromU8( zip[ 'model.obj' ] ) ); editor.execute( new AddObjectCommand( editor, object ) ); } // for ( const path in zip ) { const file = zip[ path ]; const manager = new THREE.LoadingManager(); manager.setURLModifier( function ( url ) { const file = zip[ url ]; if ( file ) { console.log( 'Loading', url ); const blob = new Blob( [ file.buffer ], { type: 'application/octet-stream' } ); return URL.createObjectURL( blob ); } return url; } ); const extension = path.split( '.' ).pop().toLowerCase(); switch ( extension ) { case 'fbx': { const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' ); const loader = new FBXLoader( manager ); const object = loader.parse( file.buffer ); editor.execute( new AddObjectCommand( editor, object ) ); break; } case 'glb': { const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' ); const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' ); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( '../examples/js/libs/draco/gltf/' ); const loader = new GLTFLoader(); loader.setDRACOLoader( dracoLoader ); loader.parse( file.buffer, '', function ( result ) { const scene = result.scene; scene.animations.push( ...result.animations ); editor.execute( new AddObjectCommand( editor, scene ) ); } ); break; } case 'gltf': { const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' ); const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' ); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( '../examples/js/libs/draco/gltf/' ); const loader = new GLTFLoader( manager ); loader.setDRACOLoader( dracoLoader ); loader.parse( strFromU8( file ), '', function ( result ) { const scene = result.scene; scene.animations.push( ...result.animations ); editor.execute( new AddObjectCommand( editor, scene ) ); } ); break; } } } } function isGLTF1( contents ) { let resultContent; if ( typeof contents === 'string' ) { // contents is a JSON string resultContent = contents; } else { const magic = THREE.LoaderUtils.decodeText( new Uint8Array( contents, 0, 4 ) ); if ( magic === 'glTF' ) { // contents is a .glb file; extract the version const version = new DataView( contents ).getUint32( 4, true ); return version < 2; } else { // contents is a .gltf file resultContent = THREE.LoaderUtils.decodeText( new Uint8Array( contents ) ); } } const json = JSON.parse( resultContent ); return ( json.asset != undefined && json.asset.version[ 0 ] < 2 ); } } export { Loader };