1- import { Node , SceneSerializer } from "babylonjs" ;
2-
3- import { saveSingleFileDialog } from "../../../tools/dialog" ;
1+ import filenamify from "filenamify" ;
42import { writeJSON } from "fs-extra" ;
3+
54import { toast } from "sonner" ;
65
6+ import { Node , SceneSerializer , Scene , Material } from "babylonjs" ;
7+
8+ import { isAbstractMesh } from "../../../tools/guards/nodes" ;
9+ import { saveSingleFileDialog } from "../../../tools/dialog" ;
10+ import { isMultiMaterial } from "../../../tools/guards/material" ;
11+
712import { Editor } from "../../main" ;
8- import filenamify from "filenamify/filenamify" ;
913
10- const JSON_CONFIG = {
11- spaces : 4 ,
12- encoding : "utf8" ,
13- } ;
1414/**
1515 * Exports the entire scene to a .babylon file.
1616 * @param editor defines the reference to the editor used to get the scene.
@@ -28,9 +28,11 @@ export async function exportScene(editor: Editor): Promise<void> {
2828
2929 try {
3030 const scene = editor . layout . preview . scene ;
31+
32+ SceneSerializer . ClearCache ( ) ;
3133 const data = await SceneSerializer . SerializeAsync ( scene ) ;
3234
33- await writeJSON ( filePath , data , JSON_CONFIG ) ;
35+ await writeJSON ( filePath , data ) ;
3436
3537 editor . layout . console . log ( `Scene exported successfully to ${ filePath } ` ) ;
3638 toast . success ( `Scene exported successfully to ${ filePath } ` ) ;
@@ -54,10 +56,17 @@ export async function exportNode(editor: Editor, node: Node): Promise<void> {
5456 defaultPath : `${ filenamify ( node . name ) } .babylon` ,
5557 } ) ;
5658
57- if ( ! filePath ) {
58- return ;
59+ if ( filePath ) {
60+ return exportNodeToPath ( editor , node , filePath ) ;
5961 }
62+ }
6063
64+ /**
65+ * Exports a specific node and all its children to a .babylon file.
66+ * @param editor defines the reference to the editor used to get the scene.
67+ * @param node defines the node to export along with its descendants.
68+ */
69+ export async function exportNodeToPath ( editor : Editor , node : Node , filePath : string ) : Promise < void > {
6170 try {
6271 const scene = editor . layout . preview . scene ;
6372
@@ -70,26 +79,29 @@ export async function exportNode(editor: Editor, node: Node): Promise<void> {
7079 descendants . forEach ( ( descendant ) => nodesToInclude . add ( descendant ) ) ;
7180
7281 // Store original doNotSerialize values
73- const originalDoNotSerialize = new Map < Node , boolean > ( ) ;
82+ const originalNodesDoNotSerialize = new Map < Node , boolean > ( ) ;
83+ const originalMaterialsDoNotSerialize = new Map < Material , boolean > ( ) ;
7484
75- exportMeshes ( scene , nodesToInclude , originalDoNotSerialize ) ;
76- exportLights ( scene , nodesToInclude , originalDoNotSerialize ) ;
77- exportCameras ( scene , nodesToInclude , originalDoNotSerialize ) ;
78- exportTransformNodes ( scene , nodesToInclude , originalDoNotSerialize ) ;
85+ exportMeshes ( scene , nodesToInclude , originalNodesDoNotSerialize ) ;
86+ exportLights ( scene , nodesToInclude , originalNodesDoNotSerialize ) ;
87+ exportCameras ( scene , nodesToInclude , originalNodesDoNotSerialize ) ;
88+ exportTransformNodes ( scene , nodesToInclude , originalNodesDoNotSerialize ) ;
89+ exportMaterials ( scene , nodesToInclude , originalMaterialsDoNotSerialize ) ;
7990
8091 const originalParticleSystems = exportParticleSystems ( scene , nodesToInclude ) ;
8192 const originalSoundTracks = exportSounds ( scene , nodesToInclude ) ;
8293
8394 // Serialize the filtered scene
95+ // SceneSerializer.ClearCache();
8496 const data = await SceneSerializer . SerializeAsync ( scene ) ;
8597
8698 // Restore original scene state
87- restoreSceneState ( scene , originalDoNotSerialize , {
99+ restoreSceneState ( scene , originalNodesDoNotSerialize , originalMaterialsDoNotSerialize , {
100+ originalSoundTracks,
88101 originalParticleSystems,
89- originalSoundTracks
90102 } ) ;
91103
92- await writeJSON ( filePath , data , JSON_CONFIG ) ;
104+ await writeJSON ( filePath , data ) ;
93105
94106 editor . layout . console . log ( `Node exported successfully to ${ filePath } ` ) ;
95107 toast . success ( `Node exported successfully to ${ filePath } ` ) ;
@@ -108,21 +120,24 @@ export async function exportNode(editor: Editor, node: Node): Promise<void> {
108120 * @param exportData Data needed for restoration
109121 */
110122function restoreSceneState (
111- scene : any ,
123+ scene : Scene ,
112124 originalDoNotSerialize : Map < Node , boolean > ,
125+ originalMaterialsDoNotSerialize : Map < Material , boolean > ,
113126 exportData : { originalParticleSystems : any [ ] ; originalSoundTracks : any [ ] }
114127) : void {
115128 // Restore original doNotSerialize values
116129 originalDoNotSerialize . forEach ( ( value , node ) => {
117130 node . doNotSerialize = value ;
118131 } ) ;
119-
132+
133+ originalMaterialsDoNotSerialize . forEach ( ( value , material ) => {
134+ material . doNotSerialize = value ;
135+ } ) ;
136+
120137 // Restore particle systems
121138 scene . particleSystems . length = 0 ;
122- exportData . originalParticleSystems . forEach ( ( ps : any ) =>
123- scene . particleSystems . push ( ps )
124- ) ;
125-
139+ exportData . originalParticleSystems . forEach ( ( ps : any ) => scene . particleSystems . push ( ps ) ) ;
140+
126141 // Restore soundtracks
127142 scene . soundTracks = exportData . originalSoundTracks ;
128143}
@@ -133,29 +148,50 @@ function restoreSceneState(
133148 * @param nodesToInclude Set of nodes to include in the export
134149 * @param originalDoNotSerialize Map to store original serialization state
135150 */
136- function exportMeshes (
137- scene : any ,
138- nodesToInclude : Set < Node > ,
139- originalDoNotSerialize : Map < Node , boolean >
140- ) : void {
141- scene . meshes . forEach ( ( mesh : Node ) => {
151+ function exportMeshes ( scene : Scene , nodesToInclude : Set < Node > , originalDoNotSerialize : Map < Node , boolean > ) : void {
152+ scene . meshes . forEach ( ( mesh ) => {
142153 originalDoNotSerialize . set ( mesh , mesh . doNotSerialize ) ;
143154 mesh . doNotSerialize = ! nodesToInclude . has ( mesh ) ;
144155 } ) ;
145156}
146157
158+ function exportMaterials ( scene : Scene , nodesToInclude : Set < Node > , originalMaterialsDoNotSerialize : Map < Material , boolean > ) : void {
159+ scene . materials . forEach ( ( material ) => {
160+ let foundUsedMaterial = false ;
161+
162+ for ( const node of nodesToInclude ) {
163+ if ( ! isAbstractMesh ( node ) || ! node . material ) {
164+ continue ;
165+ }
166+
167+ if ( node . material === material ) {
168+ foundUsedMaterial = true ;
169+ break ;
170+ }
171+
172+ if ( isMultiMaterial ( node . material ) ) {
173+ if ( node . material . subMaterials . includes ( material ) ) {
174+ foundUsedMaterial = true ;
175+ break ;
176+ }
177+ }
178+ }
179+
180+ if ( ! foundUsedMaterial ) {
181+ originalMaterialsDoNotSerialize . set ( material , material . doNotSerialize ) ;
182+ material . doNotSerialize = true ;
183+ }
184+ } ) ;
185+ }
186+
147187/**
148188 * Configures light nodes for export.
149189 * @param scene The scene containing the lights
150190 * @param nodesToInclude Set of nodes to include in the export
151191 * @param originalDoNotSerialize Map to store original serialization state
152192 */
153- function exportLights (
154- scene : any ,
155- nodesToInclude : Set < Node > ,
156- originalDoNotSerialize : Map < Node , boolean >
157- ) : void {
158- scene . lights . forEach ( ( light : Node ) => {
193+ function exportLights ( scene : Scene , nodesToInclude : Set < Node > , originalDoNotSerialize : Map < Node , boolean > ) : void {
194+ scene . lights . forEach ( ( light ) => {
159195 originalDoNotSerialize . set ( light , light . doNotSerialize ) ;
160196 light . doNotSerialize = ! nodesToInclude . has ( light ) ;
161197 } ) ;
@@ -167,12 +203,8 @@ function exportLights(
167203 * @param nodesToInclude Set of nodes to include in the export
168204 * @param originalDoNotSerialize Map to store original serialization state
169205 */
170- function exportCameras (
171- scene : any ,
172- nodesToInclude : Set < Node > ,
173- originalDoNotSerialize : Map < Node , boolean >
174- ) : void {
175- scene . cameras . forEach ( ( camera : Node ) => {
206+ function exportCameras ( scene : Scene , nodesToInclude : Set < Node > , originalDoNotSerialize : Map < Node , boolean > ) : void {
207+ scene . cameras . forEach ( ( camera ) => {
176208 originalDoNotSerialize . set ( camera , camera . doNotSerialize ) ;
177209 camera . doNotSerialize = ! nodesToInclude . has ( camera ) ;
178210 } ) ;
@@ -184,12 +216,8 @@ function exportCameras(
184216 * @param nodesToInclude Set of nodes to include in the export
185217 * @param originalDoNotSerialize Map to store original serialization state
186218 */
187- function exportTransformNodes (
188- scene : any ,
189- nodesToInclude : Set < Node > ,
190- originalDoNotSerialize : Map < Node , boolean >
191- ) : void {
192- scene . transformNodes . forEach ( ( transformNode : Node ) => {
219+ function exportTransformNodes ( scene : Scene , nodesToInclude : Set < Node > , originalDoNotSerialize : Map < Node , boolean > ) : void {
220+ scene . transformNodes . forEach ( ( transformNode ) => {
193221 originalDoNotSerialize . set ( transformNode , transformNode . doNotSerialize ) ;
194222 transformNode . doNotSerialize = ! nodesToInclude . has ( transformNode ) ;
195223 } ) ;
@@ -201,20 +229,22 @@ function exportTransformNodes(
201229 * @param nodesToInclude Set of nodes to include in the export
202230 * @returns The original array of particle systems (for restoration)
203231 */
204- function exportParticleSystems ( scene : any , nodesToInclude : Set < Node > ) : Array < any > {
232+ function exportParticleSystems ( scene : Scene , nodesToInclude : Set < Node > ) : Array < any > {
205233 // Save original particle systems
206234 const originalParticleSystems = scene . particleSystems . slice ( ) ;
207-
235+
208236 // Filter particle systems to only include those attached to our nodes
209237 const particlesToKeep = originalParticleSystems . filter ( ( ps : any ) => {
210238 const emitter = ps . emitter ;
211239 return emitter && nodesToInclude . has ( emitter as Node ) ;
212240 } ) ;
213-
241+
214242 // Replace the scene's particle systems with only those we want to include
215243 scene . particleSystems . length = 0 ;
216- particlesToKeep . forEach ( ( ps : any ) => scene . particleSystems . push ( ps ) ) ;
217-
244+ particlesToKeep . forEach ( ( ps : any ) => {
245+ scene . particleSystems . push ( ps ) ;
246+ } ) ;
247+
218248 return originalParticleSystems ;
219249}
220250
@@ -224,37 +254,39 @@ function exportParticleSystems(scene: any, nodesToInclude: Set<Node>): Array<any
224254 * @param nodesToInclude Set of nodes to include in the export
225255 * @returns The original array of soundtracks (for restoration)
226256 */
227- function exportSounds ( scene : any , nodesToInclude : Set < Node > ) : Array < any > {
257+ function exportSounds ( scene : Scene , nodesToInclude : Set < Node > ) : Array < any > {
228258 // Handle sounds - filter out sounds not attached to our nodes
229259 let originalSoundTracks : any [ ] = [ ] ;
230-
260+
231261 if ( scene . soundTracks ) {
232262 // Store original soundtracks to restore later
233263 originalSoundTracks = scene . soundTracks . slice ( ) ;
234-
264+
235265 // Filter each soundtrack to only include sounds attached to our nodes
236- const filteredSoundTracks = scene . soundTracks . map ( ( soundtrack : any ) => {
237- // Create a new sound collection with only the sounds attached to our nodes
238- const filteredSoundCollection = soundtrack . soundCollection . filter ( ( sound : any ) => {
239- if ( sound . spatialSound && sound . metadata && sound . metadata . connectedMeshName ) {
240- // Check if the connected mesh name matches any of our nodes
241- for ( const meshNode of nodesToInclude ) {
242- if ( meshNode . name === sound . metadata . connectedMeshName ) {
243- return true ;
266+ const filteredSoundTracks = scene . soundTracks
267+ . map ( ( soundtrack : any ) => {
268+ // Create a new sound collection with only the sounds attached to our nodes
269+ const filteredSoundCollection = soundtrack . soundCollection . filter ( ( sound : any ) => {
270+ if ( sound . spatialSound && sound . metadata && sound . metadata . connectedMeshName ) {
271+ // Check if the connected mesh name matches any of our nodes
272+ for ( const meshNode of nodesToInclude ) {
273+ if ( meshNode . name === sound . metadata . connectedMeshName ) {
274+ return true ;
275+ }
244276 }
245277 }
246- }
247- return false ;
248- } ) ;
249-
250- // Replace the original sound collection with our filtered one
251- soundtrack . soundCollection = filteredSoundCollection ;
252- return soundtrack ;
253- } ) . filter ( ( st : any ) => st . soundCollection . length > 0 ) ; // Remove empty soundtracks
254-
278+ return false ;
279+ } ) ;
280+
281+ // Replace the original sound collection with our filtered one
282+ soundtrack . soundCollection = filteredSoundCollection ;
283+ return soundtrack ;
284+ } )
285+ . filter ( ( st : any ) => st . soundCollection . length > 0 ) ; // Remove empty soundtracks
286+
255287 // Replace scene soundtracks with our filtered ones
256288 scene . soundTracks = filteredSoundTracks ;
257289 }
258-
290+
259291 return originalSoundTracks ;
260292}
0 commit comments