You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
467 lines
10 KiB
JavaScript
467 lines
10 KiB
JavaScript
import { UIPanel, UIRow, UIHorizontalRule } from './libs/ui.js';
|
|
|
|
function MenubarFile( editor ) {
|
|
|
|
const strings = editor.strings;
|
|
|
|
const saveArrayBuffer = editor.utils.saveArrayBuffer;
|
|
const saveString = editor.utils.saveString;
|
|
|
|
const container = new UIPanel();
|
|
container.setClass( 'menu' );
|
|
|
|
const title = new UIPanel();
|
|
title.setClass( 'title' );
|
|
title.setTextContent( strings.getKey( 'menubar/file' ) );
|
|
container.add( title );
|
|
|
|
const options = new UIPanel();
|
|
options.setClass( 'options' );
|
|
container.add( options );
|
|
|
|
// New Project
|
|
|
|
const newProjectSubmenuTitle = new UIRow().setTextContent( strings.getKey( 'menubar/file/new' ) ).addClass( 'option' ).addClass( 'submenu-title' );
|
|
newProjectSubmenuTitle.onMouseOver( function () {
|
|
|
|
const { top, right } = this.dom.getBoundingClientRect();
|
|
const { paddingTop } = getComputedStyle( this.dom );
|
|
newProjectSubmenu.setLeft( right + 'px' );
|
|
newProjectSubmenu.setTop( top - parseFloat( paddingTop ) + 'px' );
|
|
newProjectSubmenu.setDisplay( 'block' );
|
|
|
|
} );
|
|
newProjectSubmenuTitle.onMouseOut( function () {
|
|
|
|
newProjectSubmenu.setDisplay( 'none' );
|
|
|
|
} );
|
|
options.add( newProjectSubmenuTitle );
|
|
|
|
const newProjectSubmenu = new UIPanel().setPosition( 'fixed' ).addClass( 'options' ).setDisplay( 'none' );
|
|
newProjectSubmenuTitle.add( newProjectSubmenu );
|
|
|
|
// New Project / Empty
|
|
|
|
let option = new UIRow().setTextContent( strings.getKey( 'menubar/file/new/empty' ) ).setClass( 'option' );
|
|
option.onClick( function () {
|
|
|
|
if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {
|
|
|
|
editor.clear();
|
|
|
|
}
|
|
|
|
} );
|
|
newProjectSubmenu.add( option );
|
|
|
|
//
|
|
|
|
newProjectSubmenu.add( new UIHorizontalRule() );
|
|
|
|
// New Project / ...
|
|
|
|
const examples = [
|
|
{ title: 'menubar/file/new/Arkanoid', file: 'arkanoid.app.json' },
|
|
{ title: 'menubar/file/new/Camera', file: 'camera.app.json' },
|
|
{ title: 'menubar/file/new/Particles', file: 'particles.app.json' },
|
|
{ title: 'menubar/file/new/Pong', file: 'pong.app.json' },
|
|
{ title: 'menubar/file/new/Shaders', file: 'shaders.app.json' }
|
|
];
|
|
|
|
const loader = new THREE.FileLoader();
|
|
|
|
for ( let i = 0; i < examples.length; i ++ ) {
|
|
|
|
( function ( i ) {
|
|
|
|
const example = examples[ i ];
|
|
|
|
const option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( strings.getKey( example.title ) );
|
|
option.onClick( function () {
|
|
|
|
if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {
|
|
|
|
loader.load( 'examples/' + example.file, function ( text ) {
|
|
|
|
editor.clear();
|
|
editor.fromJSON( JSON.parse( text ) );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
} );
|
|
newProjectSubmenu.add( option );
|
|
|
|
} )( i );
|
|
|
|
}
|
|
|
|
// Open
|
|
|
|
const openProjectForm = document.createElement( 'form' );
|
|
openProjectForm.style.display = 'none';
|
|
document.body.appendChild( openProjectForm );
|
|
|
|
const openProjectInput = document.createElement( 'input' );
|
|
openProjectInput.multiple = false;
|
|
openProjectInput.type = 'file';
|
|
openProjectInput.accept = '.json';
|
|
openProjectInput.addEventListener( 'change', async function () {
|
|
|
|
const file = openProjectInput.files[ 0 ];
|
|
|
|
if ( file === undefined ) return;
|
|
|
|
try {
|
|
|
|
const json = JSON.parse( await file.text() );
|
|
|
|
async function onEditorCleared() {
|
|
|
|
await editor.fromJSON( json );
|
|
|
|
editor.signals.editorCleared.remove( onEditorCleared );
|
|
|
|
}
|
|
|
|
editor.signals.editorCleared.add( onEditorCleared );
|
|
|
|
editor.clear();
|
|
|
|
} catch ( e ) {
|
|
|
|
alert( strings.getKey( 'prompt/file/failedToOpenProject' ) );
|
|
console.error( e );
|
|
|
|
} finally {
|
|
|
|
form.reset();
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
openProjectForm.appendChild( openProjectInput );
|
|
|
|
option = new UIRow()
|
|
.addClass( 'option' )
|
|
.setTextContent( strings.getKey( 'menubar/file/open' ) )
|
|
.onClick( function () {
|
|
|
|
if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {
|
|
|
|
openProjectInput.click();
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
options.add( option );
|
|
|
|
// Save
|
|
|
|
option = new UIRow()
|
|
.addClass( 'option' )
|
|
.setTextContent( strings.getKey( 'menubar/file/save' ) )
|
|
.onClick( function () {
|
|
|
|
const json = editor.toJSON();
|
|
const blob = new Blob( [ JSON.stringify( json ) ], { type: 'application/json' } );
|
|
editor.utils.save( blob, 'project.json' );
|
|
|
|
} );
|
|
|
|
options.add( option );
|
|
|
|
//
|
|
|
|
options.add( new UIHorizontalRule() );
|
|
|
|
// Import
|
|
|
|
const form = document.createElement( 'form' );
|
|
form.style.display = 'none';
|
|
document.body.appendChild( form );
|
|
|
|
const fileInput = document.createElement( 'input' );
|
|
fileInput.multiple = true;
|
|
fileInput.type = 'file';
|
|
fileInput.addEventListener( 'change', function () {
|
|
|
|
editor.loader.loadFiles( fileInput.files );
|
|
form.reset();
|
|
|
|
} );
|
|
form.appendChild( fileInput );
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( strings.getKey( 'menubar/file/import' ) );
|
|
option.onClick( function () {
|
|
|
|
fileInput.click();
|
|
|
|
} );
|
|
options.add( option );
|
|
|
|
// Export
|
|
|
|
const fileExportSubmenuTitle = new UIRow().setTextContent( strings.getKey( 'menubar/file/export' ) ).addClass( 'option' ).addClass( 'submenu-title' );
|
|
fileExportSubmenuTitle.onMouseOver( function () {
|
|
|
|
const { top, right } = this.dom.getBoundingClientRect();
|
|
const { paddingTop } = getComputedStyle( this.dom );
|
|
fileExportSubmenu.setLeft( right + 'px' );
|
|
fileExportSubmenu.setTop( top - parseFloat( paddingTop ) + 'px' );
|
|
fileExportSubmenu.setDisplay( 'block' );
|
|
|
|
} );
|
|
fileExportSubmenuTitle.onMouseOut( function () {
|
|
|
|
fileExportSubmenu.setDisplay( 'none' );
|
|
|
|
} );
|
|
options.add( fileExportSubmenuTitle );
|
|
|
|
const fileExportSubmenu = new UIPanel().setPosition( 'fixed' ).addClass( 'options' ).setDisplay( 'none' );
|
|
fileExportSubmenuTitle.add( fileExportSubmenu );
|
|
|
|
// Export DRC
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'DRC' );
|
|
option.onClick( async function () {
|
|
|
|
const object = editor.selected;
|
|
|
|
if ( object === null || object.isMesh === undefined ) {
|
|
|
|
alert( strings.getKey( 'prompt/file/export/noMeshSelected' ) );
|
|
return;
|
|
|
|
}
|
|
|
|
const { DRACOExporter } = await import( 'three/addons/exporters/DRACOExporter.js' );
|
|
|
|
const exporter = new DRACOExporter();
|
|
|
|
const options = {
|
|
decodeSpeed: 5,
|
|
encodeSpeed: 5,
|
|
encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
|
|
quantization: [ 16, 8, 8, 8, 8 ],
|
|
exportUvs: true,
|
|
exportNormals: true,
|
|
exportColor: object.geometry.hasAttribute( 'color' )
|
|
};
|
|
|
|
// TODO: Change to DRACOExporter's parse( geometry, onParse )?
|
|
const result = exporter.parse( object, options );
|
|
saveArrayBuffer( result, 'model.drc' );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export GLB
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'GLB' );
|
|
option.onClick( async function () {
|
|
|
|
const scene = editor.scene;
|
|
const animations = getAnimations( scene );
|
|
|
|
const optimizedAnimations = [];
|
|
|
|
for ( const animation of animations ) {
|
|
|
|
optimizedAnimations.push( animation.clone().optimize() );
|
|
|
|
}
|
|
|
|
const { GLTFExporter } = await import( 'three/addons/exporters/GLTFExporter.js' );
|
|
|
|
const exporter = new GLTFExporter();
|
|
|
|
exporter.parse( scene, function ( result ) {
|
|
|
|
saveArrayBuffer( result, 'scene.glb' );
|
|
|
|
}, undefined, { binary: true, animations: optimizedAnimations } );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export GLTF
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'GLTF' );
|
|
option.onClick( async function () {
|
|
|
|
const scene = editor.scene;
|
|
const animations = getAnimations( scene );
|
|
|
|
const optimizedAnimations = [];
|
|
|
|
for ( const animation of animations ) {
|
|
|
|
optimizedAnimations.push( animation.clone().optimize() );
|
|
|
|
}
|
|
|
|
const { GLTFExporter } = await import( 'three/addons/exporters/GLTFExporter.js' );
|
|
|
|
const exporter = new GLTFExporter();
|
|
|
|
exporter.parse( scene, function ( result ) {
|
|
|
|
saveString( JSON.stringify( result, null, 2 ), 'scene.gltf' );
|
|
|
|
}, undefined, { animations: optimizedAnimations } );
|
|
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export OBJ
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'OBJ' );
|
|
option.onClick( async function () {
|
|
|
|
const object = editor.selected;
|
|
|
|
if ( object === null ) {
|
|
|
|
alert( strings.getKey( 'prompt/file/export/noObjectSelected' ) );
|
|
return;
|
|
|
|
}
|
|
|
|
const { OBJExporter } = await import( 'three/addons/exporters/OBJExporter.js' );
|
|
|
|
const exporter = new OBJExporter();
|
|
|
|
saveString( exporter.parse( object ), 'model.obj' );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export PLY (ASCII)
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'PLY' );
|
|
option.onClick( async function () {
|
|
|
|
const { PLYExporter } = await import( 'three/addons/exporters/PLYExporter.js' );
|
|
|
|
const exporter = new PLYExporter();
|
|
|
|
exporter.parse( editor.scene, function ( result ) {
|
|
|
|
saveArrayBuffer( result, 'model.ply' );
|
|
|
|
} );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export PLY (BINARY)
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'PLY (BINARY)' );
|
|
option.onClick( async function () {
|
|
|
|
const { PLYExporter } = await import( 'three/addons/exporters/PLYExporter.js' );
|
|
|
|
const exporter = new PLYExporter();
|
|
|
|
exporter.parse( editor.scene, function ( result ) {
|
|
|
|
saveArrayBuffer( result, 'model-binary.ply' );
|
|
|
|
}, { binary: true } );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export STL (ASCII)
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'STL' );
|
|
option.onClick( async function () {
|
|
|
|
const { STLExporter } = await import( 'three/addons/exporters/STLExporter.js' );
|
|
|
|
const exporter = new STLExporter();
|
|
|
|
saveString( exporter.parse( editor.scene ), 'model.stl' );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export STL (BINARY)
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'STL (BINARY)' );
|
|
option.onClick( async function () {
|
|
|
|
const { STLExporter } = await import( 'three/addons/exporters/STLExporter.js' );
|
|
|
|
const exporter = new STLExporter();
|
|
|
|
saveArrayBuffer( exporter.parse( editor.scene, { binary: true } ), 'model-binary.stl' );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
// Export USDZ
|
|
|
|
option = new UIRow();
|
|
option.setClass( 'option' );
|
|
option.setTextContent( 'USDZ' );
|
|
option.onClick( async function () {
|
|
|
|
const { USDZExporter } = await import( 'three/addons/exporters/USDZExporter.js' );
|
|
|
|
const exporter = new USDZExporter();
|
|
|
|
saveArrayBuffer( await exporter.parseAsync( editor.scene ), 'model.usdz' );
|
|
|
|
} );
|
|
fileExportSubmenu.add( option );
|
|
|
|
//
|
|
|
|
function getAnimations( scene ) {
|
|
|
|
const animations = [];
|
|
|
|
scene.traverse( function ( object ) {
|
|
|
|
animations.push( ... object.animations );
|
|
|
|
} );
|
|
|
|
return animations;
|
|
|
|
}
|
|
|
|
return container;
|
|
|
|
}
|
|
|
|
export { MenubarFile };
|