@thanhthbm/scene-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -0
- package/dist/index.css +921 -0
- package/dist/index.js +14076 -0
- package/package.json +49 -0
- package/src/editor/css/main.css +1030 -0
- package/src/editor/js/Animation.js +618 -0
- package/src/editor/js/AnimationResizer.js +73 -0
- package/src/editor/js/Command.js +40 -0
- package/src/editor/js/Config.js +86 -0
- package/src/editor/js/Editor.js +1006 -0
- package/src/editor/js/EditorControls.js +489 -0
- package/src/editor/js/GLTFImportDialog.js +112 -0
- package/src/editor/js/History.js +321 -0
- package/src/editor/js/Loader.js +1162 -0
- package/src/editor/js/LoaderUtils.js +90 -0
- package/src/editor/js/Menubar.Add.js +559 -0
- package/src/editor/js/Menubar.Edit.js +155 -0
- package/src/editor/js/Menubar.File.js +585 -0
- package/src/editor/js/Menubar.Help.js +73 -0
- package/src/editor/js/Menubar.Render.js +858 -0
- package/src/editor/js/Menubar.Status.js +51 -0
- package/src/editor/js/Menubar.View.js +193 -0
- package/src/editor/js/Menubar.js +29 -0
- package/src/editor/js/Player.js +53 -0
- package/src/editor/js/Resizer.js +65 -0
- package/src/editor/js/Script.js +532 -0
- package/src/editor/js/Selector.js +129 -0
- package/src/editor/js/Sidebar.Geometry.BoxGeometry.js +121 -0
- package/src/editor/js/Sidebar.Geometry.BufferGeometry.js +124 -0
- package/src/editor/js/Sidebar.Geometry.CapsuleGeometry.js +109 -0
- package/src/editor/js/Sidebar.Geometry.CircleGeometry.js +97 -0
- package/src/editor/js/Sidebar.Geometry.CylinderGeometry.js +121 -0
- package/src/editor/js/Sidebar.Geometry.DodecahedronGeometry.js +73 -0
- package/src/editor/js/Sidebar.Geometry.ExtrudeGeometry.js +196 -0
- package/src/editor/js/Sidebar.Geometry.IcosahedronGeometry.js +73 -0
- package/src/editor/js/Sidebar.Geometry.LatheGeometry.js +98 -0
- package/src/editor/js/Sidebar.Geometry.Modifiers.js +73 -0
- package/src/editor/js/Sidebar.Geometry.OctahedronGeometry.js +74 -0
- package/src/editor/js/Sidebar.Geometry.PlaneGeometry.js +97 -0
- package/src/editor/js/Sidebar.Geometry.RingGeometry.js +121 -0
- package/src/editor/js/Sidebar.Geometry.ShapeGeometry.js +76 -0
- package/src/editor/js/Sidebar.Geometry.SphereGeometry.js +133 -0
- package/src/editor/js/Sidebar.Geometry.TetrahedronGeometry.js +74 -0
- package/src/editor/js/Sidebar.Geometry.TextGeometry.js +136 -0
- package/src/editor/js/Sidebar.Geometry.TorusGeometry.js +109 -0
- package/src/editor/js/Sidebar.Geometry.TorusKnotGeometry.js +121 -0
- package/src/editor/js/Sidebar.Geometry.TubeGeometry.js +135 -0
- package/src/editor/js/Sidebar.Geometry.js +471 -0
- package/src/editor/js/Sidebar.Material.BooleanProperty.js +60 -0
- package/src/editor/js/Sidebar.Material.ColorProperty.js +87 -0
- package/src/editor/js/Sidebar.Material.ConstantProperty.js +62 -0
- package/src/editor/js/Sidebar.Material.MapProperty.js +277 -0
- package/src/editor/js/Sidebar.Material.NumberProperty.js +60 -0
- package/src/editor/js/Sidebar.Material.Program.js +73 -0
- package/src/editor/js/Sidebar.Material.RangeValueProperty.js +63 -0
- package/src/editor/js/Sidebar.Material.js +751 -0
- package/src/editor/js/Sidebar.Object.js +892 -0
- package/src/editor/js/Sidebar.Project.App.js +218 -0
- package/src/editor/js/Sidebar.Project.Materials.js +82 -0
- package/src/editor/js/Sidebar.Project.Renderer.js +193 -0
- package/src/editor/js/Sidebar.Project.Resources.js +242 -0
- package/src/editor/js/Sidebar.Project.js +21 -0
- package/src/editor/js/Sidebar.Properties.js +73 -0
- package/src/editor/js/Sidebar.Scene.js +643 -0
- package/src/editor/js/Sidebar.Script.js +129 -0
- package/src/editor/js/Sidebar.Settings.History.js +146 -0
- package/src/editor/js/Sidebar.Settings.Shortcuts.js +198 -0
- package/src/editor/js/Sidebar.Settings.js +58 -0
- package/src/editor/js/Sidebar.js +41 -0
- package/src/editor/js/Storage.js +98 -0
- package/src/editor/js/Strings.js +2718 -0
- package/src/editor/js/TextureParametersDialog.js +293 -0
- package/src/editor/js/Toolbar.js +75 -0
- package/src/editor/js/Viewport.Controls.js +98 -0
- package/src/editor/js/Viewport.Info.js +144 -0
- package/src/editor/js/Viewport.Pathtracer.js +27 -0
- package/src/editor/js/Viewport.ViewHelper.js +41 -0
- package/src/editor/js/Viewport.XR.js +7 -0
- package/src/editor/js/Viewport.js +982 -0
- package/src/editor/js/commands/AddObjectCommand.js +68 -0
- package/src/editor/js/commands/AddScriptCommand.js +75 -0
- package/src/editor/js/commands/Commands.js +24 -0
- package/src/editor/js/commands/MoveObjectCommand.js +117 -0
- package/src/editor/js/commands/MultiCmdsCommand.js +85 -0
- package/src/editor/js/commands/RemoveObjectCommand.js +88 -0
- package/src/editor/js/commands/RemoveScriptCommand.js +81 -0
- package/src/editor/js/commands/SetColorCommand.js +73 -0
- package/src/editor/js/commands/SetGeometryCommand.js +86 -0
- package/src/editor/js/commands/SetGeometryValueCommand.js +70 -0
- package/src/editor/js/commands/SetMaterialColorCommand.js +87 -0
- package/src/editor/js/commands/SetMaterialCommand.js +80 -0
- package/src/editor/js/commands/SetMaterialMapCommand.js +144 -0
- package/src/editor/js/commands/SetMaterialRangeCommand.js +92 -0
- package/src/editor/js/commands/SetMaterialValueCommand.js +91 -0
- package/src/editor/js/commands/SetMaterialVectorCommand.js +88 -0
- package/src/editor/js/commands/SetPositionCommand.js +84 -0
- package/src/editor/js/commands/SetRotationCommand.js +84 -0
- package/src/editor/js/commands/SetScaleCommand.js +84 -0
- package/src/editor/js/commands/SetSceneCommand.js +104 -0
- package/src/editor/js/commands/SetScriptValueCommand.js +80 -0
- package/src/editor/js/commands/SetShadowValueCommand.js +73 -0
- package/src/editor/js/commands/SetTextureParametersCommand.js +143 -0
- package/src/editor/js/commands/SetUuidCommand.js +70 -0
- package/src/editor/js/commands/SetValueCommand.js +75 -0
- package/src/editor/js/libs/acorn/acorn.js +3236 -0
- package/src/editor/js/libs/acorn/acorn_loose.js +1299 -0
- package/src/editor/js/libs/acorn/walk.js +344 -0
- package/src/editor/js/libs/app/index.html +51 -0
- package/src/editor/js/libs/app.js +14 -0
- package/src/editor/js/libs/codemirror/addon/dialog.css +32 -0
- package/src/editor/js/libs/codemirror/addon/dialog.js +163 -0
- package/src/editor/js/libs/codemirror/addon/show-hint.css +36 -0
- package/src/editor/js/libs/codemirror/addon/show-hint.js +529 -0
- package/src/editor/js/libs/codemirror/addon/tern.css +87 -0
- package/src/editor/js/libs/codemirror/addon/tern.js +750 -0
- package/src/editor/js/libs/codemirror/codemirror.css +344 -0
- package/src/editor/js/libs/codemirror/codemirror.js +9849 -0
- package/src/editor/js/libs/codemirror/mode/glsl.js +233 -0
- package/src/editor/js/libs/codemirror/mode/javascript.js +959 -0
- package/src/editor/js/libs/codemirror/theme/monokai.css +41 -0
- package/src/editor/js/libs/esprima.js +6401 -0
- package/src/editor/js/libs/jsonlint.js +453 -0
- package/src/editor/js/libs/signals.min.js +14 -0
- package/src/editor/js/libs/tern-threejs/build-defs.js +233 -0
- package/src/editor/js/libs/ternjs/comment.js +87 -0
- package/src/editor/js/libs/ternjs/def.js +588 -0
- package/src/editor/js/libs/ternjs/doc_comment.js +401 -0
- package/src/editor/js/libs/ternjs/infer.js +1635 -0
- package/src/editor/js/libs/ternjs/polyfill.js +80 -0
- package/src/editor/js/libs/ternjs/signal.js +26 -0
- package/src/editor/js/libs/ternjs/tern.js +993 -0
- package/src/editor/js/libs/ui.js +1305 -0
- package/src/editor/js/libs/ui.three.js +946 -0
- package/src/events.js +273 -0
- package/src/index.js +284 -0
- package/src/presets/machines.js +239 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import { UIPanel, UIRow, UIHorizontalRule } from './libs/ui.js';
|
|
2
|
+
import { FileLoader, PropertyBinding } from 'three';
|
|
3
|
+
|
|
4
|
+
function MenubarFile( editor ) {
|
|
5
|
+
|
|
6
|
+
const strings = editor.strings;
|
|
7
|
+
|
|
8
|
+
const saveArrayBuffer = editor.utils.saveArrayBuffer;
|
|
9
|
+
const saveString = editor.utils.saveString;
|
|
10
|
+
|
|
11
|
+
const container = new UIPanel();
|
|
12
|
+
container.setClass( 'menu' );
|
|
13
|
+
|
|
14
|
+
const title = new UIPanel();
|
|
15
|
+
title.setClass( 'title' );
|
|
16
|
+
title.setTextContent( strings.getKey( 'menubar/file' ) );
|
|
17
|
+
container.add( title );
|
|
18
|
+
|
|
19
|
+
const options = new UIPanel();
|
|
20
|
+
options.setClass( 'options' );
|
|
21
|
+
container.add( options );
|
|
22
|
+
|
|
23
|
+
// New Project
|
|
24
|
+
|
|
25
|
+
const newProjectSubmenuTitle = new UIRow().setTextContent( strings.getKey( 'menubar/file/new' ) ).addClass( 'option' ).addClass( 'submenu-title' );
|
|
26
|
+
newProjectSubmenuTitle.onMouseOver( function () {
|
|
27
|
+
|
|
28
|
+
const { top, right } = this.dom.getBoundingClientRect();
|
|
29
|
+
const { paddingTop } = getComputedStyle( this.dom );
|
|
30
|
+
newProjectSubmenu.setLeft( right + 'px' );
|
|
31
|
+
newProjectSubmenu.setTop( top - parseFloat( paddingTop ) + 'px' );
|
|
32
|
+
newProjectSubmenu.setDisplay( 'block' );
|
|
33
|
+
|
|
34
|
+
} );
|
|
35
|
+
newProjectSubmenuTitle.onMouseOut( function () {
|
|
36
|
+
|
|
37
|
+
newProjectSubmenu.setDisplay( 'none' );
|
|
38
|
+
|
|
39
|
+
} );
|
|
40
|
+
options.add( newProjectSubmenuTitle );
|
|
41
|
+
|
|
42
|
+
const newProjectSubmenu = new UIPanel().setPosition( 'fixed' ).addClass( 'options' ).setDisplay( 'none' );
|
|
43
|
+
newProjectSubmenuTitle.add( newProjectSubmenu );
|
|
44
|
+
|
|
45
|
+
// New Project / Empty
|
|
46
|
+
|
|
47
|
+
let option = new UIRow().setTextContent( strings.getKey( 'menubar/file/new/empty' ) ).setClass( 'option' );
|
|
48
|
+
option.onClick( function () {
|
|
49
|
+
|
|
50
|
+
if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {
|
|
51
|
+
|
|
52
|
+
editor.clear();
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
} );
|
|
57
|
+
newProjectSubmenu.add( option );
|
|
58
|
+
|
|
59
|
+
//
|
|
60
|
+
|
|
61
|
+
newProjectSubmenu.add( new UIHorizontalRule() );
|
|
62
|
+
|
|
63
|
+
// New Project / ...
|
|
64
|
+
|
|
65
|
+
const examples = [
|
|
66
|
+
{ title: 'menubar/file/new/Arkanoid', file: 'arkanoid.app.json' },
|
|
67
|
+
{ title: 'menubar/file/new/Camera', file: 'camera.app.json' },
|
|
68
|
+
{ title: 'menubar/file/new/Particles', file: 'particles.app.json' },
|
|
69
|
+
{ title: 'menubar/file/new/Pong', file: 'pong.app.json' },
|
|
70
|
+
{ title: 'menubar/file/new/Shaders', file: 'shaders.app.json' }
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const loader = new FileLoader();
|
|
74
|
+
|
|
75
|
+
for ( let i = 0; i < examples.length; i ++ ) {
|
|
76
|
+
|
|
77
|
+
( function ( i ) {
|
|
78
|
+
|
|
79
|
+
const example = examples[ i ];
|
|
80
|
+
|
|
81
|
+
const option = new UIRow();
|
|
82
|
+
option.setClass( 'option' );
|
|
83
|
+
option.setTextContent( strings.getKey( example.title ) );
|
|
84
|
+
option.onClick( function () {
|
|
85
|
+
|
|
86
|
+
if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {
|
|
87
|
+
|
|
88
|
+
loader.load( 'examples/' + example.file, function ( text ) {
|
|
89
|
+
|
|
90
|
+
editor.clear();
|
|
91
|
+
editor.fromJSON( JSON.parse( text ) );
|
|
92
|
+
|
|
93
|
+
} );
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
} );
|
|
98
|
+
newProjectSubmenu.add( option );
|
|
99
|
+
|
|
100
|
+
} )( i );
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Open
|
|
105
|
+
|
|
106
|
+
const openProjectForm = document.createElement( 'form' );
|
|
107
|
+
openProjectForm.style.display = 'none';
|
|
108
|
+
document.body.appendChild( openProjectForm );
|
|
109
|
+
|
|
110
|
+
const openProjectInput = document.createElement( 'input' );
|
|
111
|
+
openProjectInput.multiple = false;
|
|
112
|
+
openProjectInput.type = 'file';
|
|
113
|
+
openProjectInput.accept = '.json';
|
|
114
|
+
openProjectInput.addEventListener( 'change', async function () {
|
|
115
|
+
|
|
116
|
+
const file = openProjectInput.files[ 0 ];
|
|
117
|
+
|
|
118
|
+
if ( file === undefined ) return;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
|
|
122
|
+
const json = JSON.parse( await file.text() );
|
|
123
|
+
|
|
124
|
+
async function onEditorCleared() {
|
|
125
|
+
|
|
126
|
+
await editor.fromJSON( json );
|
|
127
|
+
|
|
128
|
+
editor.signals.editorCleared.remove( onEditorCleared );
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
editor.signals.editorCleared.add( onEditorCleared );
|
|
133
|
+
|
|
134
|
+
editor.clear();
|
|
135
|
+
|
|
136
|
+
} catch ( e ) {
|
|
137
|
+
|
|
138
|
+
alert( strings.getKey( 'prompt/file/failedToOpenProject' ) );
|
|
139
|
+
console.error( e );
|
|
140
|
+
|
|
141
|
+
} finally {
|
|
142
|
+
|
|
143
|
+
form.reset();
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
} );
|
|
148
|
+
|
|
149
|
+
openProjectForm.appendChild( openProjectInput );
|
|
150
|
+
|
|
151
|
+
option = new UIRow()
|
|
152
|
+
.addClass( 'option' )
|
|
153
|
+
.setTextContent( strings.getKey( 'menubar/file/open' ) )
|
|
154
|
+
.onClick( function () {
|
|
155
|
+
|
|
156
|
+
if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {
|
|
157
|
+
|
|
158
|
+
openProjectInput.click();
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
} );
|
|
163
|
+
|
|
164
|
+
options.add( option );
|
|
165
|
+
|
|
166
|
+
// Save
|
|
167
|
+
|
|
168
|
+
option = new UIRow()
|
|
169
|
+
.addClass( 'option' )
|
|
170
|
+
.setTextContent( strings.getKey( 'menubar/file/save' ) )
|
|
171
|
+
.onClick( function () {
|
|
172
|
+
|
|
173
|
+
const json = editor.toJSON();
|
|
174
|
+
const blob = new Blob( [ JSON.stringify( json ) ], { type: 'application/json' } );
|
|
175
|
+
editor.utils.save( blob, 'project.json' );
|
|
176
|
+
|
|
177
|
+
} );
|
|
178
|
+
|
|
179
|
+
options.add( option );
|
|
180
|
+
|
|
181
|
+
//
|
|
182
|
+
|
|
183
|
+
options.add( new UIHorizontalRule() );
|
|
184
|
+
|
|
185
|
+
// Import
|
|
186
|
+
|
|
187
|
+
const form = document.createElement( 'form' );
|
|
188
|
+
form.style.display = 'none';
|
|
189
|
+
document.body.appendChild( form );
|
|
190
|
+
|
|
191
|
+
const fileInput = document.createElement( 'input' );
|
|
192
|
+
fileInput.multiple = true;
|
|
193
|
+
fileInput.type = 'file';
|
|
194
|
+
fileInput.addEventListener( 'change', function () {
|
|
195
|
+
|
|
196
|
+
editor.loader.loadFiles( fileInput.files );
|
|
197
|
+
form.reset();
|
|
198
|
+
|
|
199
|
+
} );
|
|
200
|
+
form.appendChild( fileInput );
|
|
201
|
+
|
|
202
|
+
option = new UIRow();
|
|
203
|
+
option.setClass( 'option' );
|
|
204
|
+
option.setTextContent( strings.getKey( 'menubar/file/import' ) );
|
|
205
|
+
option.onClick( function () {
|
|
206
|
+
|
|
207
|
+
fileInput.click();
|
|
208
|
+
|
|
209
|
+
} );
|
|
210
|
+
options.add( option );
|
|
211
|
+
|
|
212
|
+
// Export
|
|
213
|
+
|
|
214
|
+
const fileExportSubmenuTitle = new UIRow().setTextContent( strings.getKey( 'menubar/file/export' ) ).addClass( 'option' ).addClass( 'submenu-title' );
|
|
215
|
+
fileExportSubmenuTitle.onMouseOver( function () {
|
|
216
|
+
|
|
217
|
+
const { top, right } = this.dom.getBoundingClientRect();
|
|
218
|
+
const { paddingTop } = getComputedStyle( this.dom );
|
|
219
|
+
fileExportSubmenu.setLeft( right + 'px' );
|
|
220
|
+
fileExportSubmenu.setTop( top - parseFloat( paddingTop ) + 'px' );
|
|
221
|
+
fileExportSubmenu.setDisplay( 'block' );
|
|
222
|
+
|
|
223
|
+
} );
|
|
224
|
+
fileExportSubmenuTitle.onMouseOut( function () {
|
|
225
|
+
|
|
226
|
+
fileExportSubmenu.setDisplay( 'none' );
|
|
227
|
+
|
|
228
|
+
} );
|
|
229
|
+
options.add( fileExportSubmenuTitle );
|
|
230
|
+
|
|
231
|
+
const fileExportSubmenu = new UIPanel().setPosition( 'fixed' ).addClass( 'options' ).setDisplay( 'none' );
|
|
232
|
+
fileExportSubmenuTitle.add( fileExportSubmenu );
|
|
233
|
+
|
|
234
|
+
// Export DRC
|
|
235
|
+
|
|
236
|
+
option = new UIRow();
|
|
237
|
+
option.setClass( 'option' );
|
|
238
|
+
option.setTextContent( 'DRC' );
|
|
239
|
+
option.onClick( async function () {
|
|
240
|
+
|
|
241
|
+
const object = editor.selected;
|
|
242
|
+
|
|
243
|
+
if ( object === null || object.isMesh === undefined ) {
|
|
244
|
+
|
|
245
|
+
alert( strings.getKey( 'prompt/file/export/noMeshSelected' ) );
|
|
246
|
+
return;
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const { DRACOExporter } = await import( 'three/addons/exporters/DRACOExporter.js' );
|
|
251
|
+
|
|
252
|
+
const exporter = new DRACOExporter();
|
|
253
|
+
|
|
254
|
+
const options = {
|
|
255
|
+
decodeSpeed: 5,
|
|
256
|
+
encodeSpeed: 5,
|
|
257
|
+
encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
|
|
258
|
+
quantization: [ 16, 8, 8, 8, 8 ],
|
|
259
|
+
exportUvs: true,
|
|
260
|
+
exportNormals: true,
|
|
261
|
+
exportColor: object.geometry.hasAttribute( 'color' )
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const result = await exporter.parseAsync( object, options );
|
|
265
|
+
saveArrayBuffer( result, 'model.drc' );
|
|
266
|
+
|
|
267
|
+
} );
|
|
268
|
+
fileExportSubmenu.add( option );
|
|
269
|
+
|
|
270
|
+
// Export GLB
|
|
271
|
+
|
|
272
|
+
option = new UIRow();
|
|
273
|
+
option.setClass( 'option' );
|
|
274
|
+
option.setTextContent( 'GLB' );
|
|
275
|
+
option.onClick( async function () {
|
|
276
|
+
|
|
277
|
+
const scene = editor.scene;
|
|
278
|
+
|
|
279
|
+
if ( needsUniqueNames( scene ) ) { // see #25179
|
|
280
|
+
|
|
281
|
+
if ( confirm( strings.getKey( 'prompt/file/export/duplicateNames' ) ) === false ) return;
|
|
282
|
+
|
|
283
|
+
ensureUniqueNames( scene );
|
|
284
|
+
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const animations = getAnimations( scene );
|
|
288
|
+
|
|
289
|
+
const optimizedAnimations = [];
|
|
290
|
+
|
|
291
|
+
for ( const animation of animations ) {
|
|
292
|
+
|
|
293
|
+
optimizedAnimations.push( animation.clone().optimize() );
|
|
294
|
+
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { GLTFExporter } = await import( 'three/addons/exporters/GLTFExporter.js' );
|
|
298
|
+
|
|
299
|
+
const exporter = new GLTFExporter();
|
|
300
|
+
|
|
301
|
+
exporter.parse( scene, function ( result ) {
|
|
302
|
+
|
|
303
|
+
saveArrayBuffer( result, 'scene.glb' );
|
|
304
|
+
|
|
305
|
+
}, undefined, { binary: true, animations: optimizedAnimations } );
|
|
306
|
+
|
|
307
|
+
} );
|
|
308
|
+
fileExportSubmenu.add( option );
|
|
309
|
+
|
|
310
|
+
// Export GLTF
|
|
311
|
+
|
|
312
|
+
option = new UIRow();
|
|
313
|
+
option.setClass( 'option' );
|
|
314
|
+
option.setTextContent( 'GLTF' );
|
|
315
|
+
option.onClick( async function () {
|
|
316
|
+
|
|
317
|
+
const scene = editor.scene;
|
|
318
|
+
|
|
319
|
+
if ( needsUniqueNames( scene ) ) { // see #25179
|
|
320
|
+
|
|
321
|
+
if ( confirm( strings.getKey( 'prompt/file/export/duplicateNames' ) ) === false ) return;
|
|
322
|
+
|
|
323
|
+
ensureUniqueNames( scene );
|
|
324
|
+
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const animations = getAnimations( scene );
|
|
328
|
+
|
|
329
|
+
const optimizedAnimations = [];
|
|
330
|
+
|
|
331
|
+
for ( const animation of animations ) {
|
|
332
|
+
|
|
333
|
+
optimizedAnimations.push( animation.clone().optimize() );
|
|
334
|
+
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const { GLTFExporter } = await import( 'three/addons/exporters/GLTFExporter.js' );
|
|
338
|
+
|
|
339
|
+
const exporter = new GLTFExporter();
|
|
340
|
+
|
|
341
|
+
exporter.parse( scene, function ( result ) {
|
|
342
|
+
|
|
343
|
+
saveString( JSON.stringify( result, null, 2 ), 'scene.gltf' );
|
|
344
|
+
|
|
345
|
+
}, undefined, { animations: optimizedAnimations } );
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
} );
|
|
349
|
+
fileExportSubmenu.add( option );
|
|
350
|
+
|
|
351
|
+
// Export OBJ
|
|
352
|
+
|
|
353
|
+
option = new UIRow();
|
|
354
|
+
option.setClass( 'option' );
|
|
355
|
+
option.setTextContent( 'OBJ' );
|
|
356
|
+
option.onClick( async function () {
|
|
357
|
+
|
|
358
|
+
const object = editor.selected;
|
|
359
|
+
|
|
360
|
+
if ( object === null ) {
|
|
361
|
+
|
|
362
|
+
alert( strings.getKey( 'prompt/file/export/noObjectSelected' ) );
|
|
363
|
+
return;
|
|
364
|
+
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const { OBJExporter } = await import( 'three/addons/exporters/OBJExporter.js' );
|
|
368
|
+
|
|
369
|
+
const exporter = new OBJExporter();
|
|
370
|
+
|
|
371
|
+
saveString( exporter.parse( object ), 'model.obj' );
|
|
372
|
+
|
|
373
|
+
} );
|
|
374
|
+
fileExportSubmenu.add( option );
|
|
375
|
+
|
|
376
|
+
// Export PLY (ASCII)
|
|
377
|
+
|
|
378
|
+
option = new UIRow();
|
|
379
|
+
option.setClass( 'option' );
|
|
380
|
+
option.setTextContent( 'PLY' );
|
|
381
|
+
option.onClick( async function () {
|
|
382
|
+
|
|
383
|
+
const { PLYExporter } = await import( 'three/addons/exporters/PLYExporter.js' );
|
|
384
|
+
|
|
385
|
+
const exporter = new PLYExporter();
|
|
386
|
+
|
|
387
|
+
exporter.parse( editor.scene, function ( result ) {
|
|
388
|
+
|
|
389
|
+
saveArrayBuffer( result, 'model.ply' );
|
|
390
|
+
|
|
391
|
+
} );
|
|
392
|
+
|
|
393
|
+
} );
|
|
394
|
+
fileExportSubmenu.add( option );
|
|
395
|
+
|
|
396
|
+
// Export PLY (BINARY)
|
|
397
|
+
|
|
398
|
+
option = new UIRow();
|
|
399
|
+
option.setClass( 'option' );
|
|
400
|
+
option.setTextContent( 'PLY (BINARY)' );
|
|
401
|
+
option.onClick( async function () {
|
|
402
|
+
|
|
403
|
+
const { PLYExporter } = await import( 'three/addons/exporters/PLYExporter.js' );
|
|
404
|
+
|
|
405
|
+
const exporter = new PLYExporter();
|
|
406
|
+
|
|
407
|
+
exporter.parse( editor.scene, function ( result ) {
|
|
408
|
+
|
|
409
|
+
saveArrayBuffer( result, 'model-binary.ply' );
|
|
410
|
+
|
|
411
|
+
}, { binary: true } );
|
|
412
|
+
|
|
413
|
+
} );
|
|
414
|
+
fileExportSubmenu.add( option );
|
|
415
|
+
|
|
416
|
+
// Export STL (ASCII)
|
|
417
|
+
|
|
418
|
+
option = new UIRow();
|
|
419
|
+
option.setClass( 'option' );
|
|
420
|
+
option.setTextContent( 'STL' );
|
|
421
|
+
option.onClick( async function () {
|
|
422
|
+
|
|
423
|
+
const { STLExporter } = await import( 'three/addons/exporters/STLExporter.js' );
|
|
424
|
+
|
|
425
|
+
const exporter = new STLExporter();
|
|
426
|
+
|
|
427
|
+
saveString( exporter.parse( editor.scene ), 'model.stl' );
|
|
428
|
+
|
|
429
|
+
} );
|
|
430
|
+
fileExportSubmenu.add( option );
|
|
431
|
+
|
|
432
|
+
// Export STL (BINARY)
|
|
433
|
+
|
|
434
|
+
option = new UIRow();
|
|
435
|
+
option.setClass( 'option' );
|
|
436
|
+
option.setTextContent( 'STL (BINARY)' );
|
|
437
|
+
option.onClick( async function () {
|
|
438
|
+
|
|
439
|
+
const { STLExporter } = await import( 'three/addons/exporters/STLExporter.js' );
|
|
440
|
+
|
|
441
|
+
const exporter = new STLExporter();
|
|
442
|
+
|
|
443
|
+
saveArrayBuffer( exporter.parse( editor.scene, { binary: true } ), 'model-binary.stl' );
|
|
444
|
+
|
|
445
|
+
} );
|
|
446
|
+
fileExportSubmenu.add( option );
|
|
447
|
+
|
|
448
|
+
// Export USDZ
|
|
449
|
+
|
|
450
|
+
option = new UIRow();
|
|
451
|
+
option.setClass( 'option' );
|
|
452
|
+
option.setTextContent( 'USDZ' );
|
|
453
|
+
option.onClick( async function () {
|
|
454
|
+
|
|
455
|
+
const { USDZExporter } = await import( 'three/addons/exporters/USDZExporter.js' );
|
|
456
|
+
|
|
457
|
+
const exporter = new USDZExporter();
|
|
458
|
+
|
|
459
|
+
saveArrayBuffer( await exporter.parseAsync( editor.scene ), 'model.usdz' );
|
|
460
|
+
|
|
461
|
+
} );
|
|
462
|
+
fileExportSubmenu.add( option );
|
|
463
|
+
|
|
464
|
+
//
|
|
465
|
+
|
|
466
|
+
function getAnimations( scene ) {
|
|
467
|
+
|
|
468
|
+
const animations = [];
|
|
469
|
+
|
|
470
|
+
scene.traverse( function ( object ) {
|
|
471
|
+
|
|
472
|
+
animations.push( ... object.animations );
|
|
473
|
+
|
|
474
|
+
} );
|
|
475
|
+
|
|
476
|
+
return animations;
|
|
477
|
+
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function needsUniqueNames( scene ) {
|
|
481
|
+
|
|
482
|
+
const usedNames = new Set();
|
|
483
|
+
let duplicate = false;
|
|
484
|
+
let animated = false;
|
|
485
|
+
|
|
486
|
+
scene.traverse( function ( object ) {
|
|
487
|
+
|
|
488
|
+
if ( object.animations.length > 0 ) animated = true;
|
|
489
|
+
|
|
490
|
+
if ( object.name === '' ) return;
|
|
491
|
+
|
|
492
|
+
if ( usedNames.has( object.name ) ) duplicate = true;
|
|
493
|
+
|
|
494
|
+
usedNames.add( object.name );
|
|
495
|
+
|
|
496
|
+
} );
|
|
497
|
+
|
|
498
|
+
return duplicate && animated;
|
|
499
|
+
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Gives every object a unique name and keeps the animation tracks that
|
|
503
|
+
// reference them by name in sync. The renamed scene mirrors the result of a
|
|
504
|
+
// glTF round-trip, where the loader makes all names unique, too.
|
|
505
|
+
|
|
506
|
+
function ensureUniqueNames( scene ) {
|
|
507
|
+
|
|
508
|
+
// Resolve each track's target object up front, scoped to the object that
|
|
509
|
+
// owns the clip. This disambiguates colliding names before they change.
|
|
510
|
+
|
|
511
|
+
const trackBindings = [];
|
|
512
|
+
|
|
513
|
+
scene.traverse( function ( owner ) {
|
|
514
|
+
|
|
515
|
+
for ( const clip of owner.animations ) {
|
|
516
|
+
|
|
517
|
+
for ( const track of clip.tracks ) {
|
|
518
|
+
|
|
519
|
+
const nodeName = PropertyBinding.parseTrackName( track.name ).nodeName;
|
|
520
|
+
const target = PropertyBinding.findNode( owner, nodeName );
|
|
521
|
+
|
|
522
|
+
// References by UUID stay valid, so only track name-based ones.
|
|
523
|
+
|
|
524
|
+
if ( target !== null && target.name === nodeName ) {
|
|
525
|
+
|
|
526
|
+
trackBindings.push( { track, target, nodeName } );
|
|
527
|
+
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
} );
|
|
535
|
+
|
|
536
|
+
// Assign a unique name to every named object.
|
|
537
|
+
|
|
538
|
+
let changed = false;
|
|
539
|
+
const usedNames = new Set();
|
|
540
|
+
|
|
541
|
+
scene.traverse( function ( object ) {
|
|
542
|
+
|
|
543
|
+
if ( object.name === '' ) return;
|
|
544
|
+
|
|
545
|
+
if ( usedNames.has( object.name ) ) {
|
|
546
|
+
|
|
547
|
+
let suffix = 1, name;
|
|
548
|
+
do {
|
|
549
|
+
|
|
550
|
+
name = object.name + '_' + ( suffix ++ );
|
|
551
|
+
|
|
552
|
+
} while ( usedNames.has( name ) );
|
|
553
|
+
|
|
554
|
+
object.name = name;
|
|
555
|
+
changed = true;
|
|
556
|
+
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
usedNames.add( object.name );
|
|
560
|
+
|
|
561
|
+
} );
|
|
562
|
+
|
|
563
|
+
if ( changed === false ) return;
|
|
564
|
+
|
|
565
|
+
// Point the affected tracks at their renamed targets.
|
|
566
|
+
|
|
567
|
+
for ( const { track, target, nodeName } of trackBindings ) {
|
|
568
|
+
|
|
569
|
+
if ( target.name !== nodeName ) {
|
|
570
|
+
|
|
571
|
+
track.name = target.name + track.name.slice( nodeName.length );
|
|
572
|
+
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
editor.signals.sceneGraphChanged.dispatch();
|
|
578
|
+
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return container;
|
|
582
|
+
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export { MenubarFile };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { UIPanel, UIRow } from './libs/ui.js';
|
|
2
|
+
|
|
3
|
+
function MenubarHelp( editor ) {
|
|
4
|
+
|
|
5
|
+
const strings = editor.strings;
|
|
6
|
+
|
|
7
|
+
const container = new UIPanel();
|
|
8
|
+
container.setClass( 'menu' );
|
|
9
|
+
|
|
10
|
+
const title = new UIPanel();
|
|
11
|
+
title.setClass( 'title' );
|
|
12
|
+
title.setTextContent( strings.getKey( 'menubar/help' ) );
|
|
13
|
+
container.add( title );
|
|
14
|
+
|
|
15
|
+
const options = new UIPanel();
|
|
16
|
+
options.setClass( 'options' );
|
|
17
|
+
container.add( options );
|
|
18
|
+
|
|
19
|
+
// Source code
|
|
20
|
+
|
|
21
|
+
let option = new UIRow();
|
|
22
|
+
option.setClass( 'option' );
|
|
23
|
+
option.setTextContent( strings.getKey( 'menubar/help/source_code' ) );
|
|
24
|
+
option.onClick( function () {
|
|
25
|
+
|
|
26
|
+
window.open( 'https://github.com/mrdoob/three.js/tree/master/editor', '_blank' );
|
|
27
|
+
|
|
28
|
+
} );
|
|
29
|
+
options.add( option );
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
// Icon
|
|
33
|
+
|
|
34
|
+
let option = new UIRow();
|
|
35
|
+
option.setClass( 'option' );
|
|
36
|
+
option.setTextContent( strings.getKey( 'menubar/help/icons' ) );
|
|
37
|
+
option.onClick( function () {
|
|
38
|
+
|
|
39
|
+
window.open( 'https://www.flaticon.com/packs/interface-44', '_blank' );
|
|
40
|
+
|
|
41
|
+
} );
|
|
42
|
+
options.add( option );
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
// About
|
|
46
|
+
|
|
47
|
+
option = new UIRow();
|
|
48
|
+
option.setClass( 'option' );
|
|
49
|
+
option.setTextContent( strings.getKey( 'menubar/help/about' ) );
|
|
50
|
+
option.onClick( function () {
|
|
51
|
+
|
|
52
|
+
window.open( 'https://threejs.org', '_blank' );
|
|
53
|
+
|
|
54
|
+
} );
|
|
55
|
+
options.add( option );
|
|
56
|
+
|
|
57
|
+
// Manual
|
|
58
|
+
|
|
59
|
+
option = new UIRow();
|
|
60
|
+
option.setClass( 'option' );
|
|
61
|
+
option.setTextContent( strings.getKey( 'menubar/help/manual' ) );
|
|
62
|
+
option.onClick( function () {
|
|
63
|
+
|
|
64
|
+
window.open( 'https://github.com/mrdoob/three.js/wiki/Editor-Manual', '_blank' );
|
|
65
|
+
|
|
66
|
+
} );
|
|
67
|
+
options.add( option );
|
|
68
|
+
|
|
69
|
+
return container;
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { MenubarHelp };
|