Pārlūkot izejas kodu

Merge pull request #8308 from belfortk/export-frames-v2

Add support to import frames (frame nodes unconnected)
David Catuhe 5 gadi atpakaļ
vecāks
revīzija
34bf992f1e

+ 1 - 0
dist/preview release/what's new.md

@@ -50,6 +50,7 @@
 - Particle systems: add the `ParticlePositionWorld` block ([Popov72](https://github.com/Popov72))
 - Add isExposedOnFrame property to connection points ([belfortk](https://github.com/belfortk))
 - Add support for exporting frames ([belfortk](https://github.com/belfortk))
+- Add support for importing frames and their nodes (unconnected) and exposed frame ports ([belfortk](https://github.com/belfortk))
 
 ### Inspector
 

+ 12 - 0
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -163,6 +163,15 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
         }, undefined, true);
     }
 
+    loadFrame(file: File) {
+        Tools.ReadFile(file, (data) => {
+            // get Frame Data from file
+            let decoder = new TextDecoder("utf-8");
+            const frameData = JSON.parse(decoder.decode(data));
+            SerializationTools.AddFrameToMaterial(frameData, this.props.globalState, this.props.globalState.nodeMaterial);
+        }, undefined, true);
+    }
+
     save() {
         let json = SerializationTools.Serialize(this.props.globalState.nodeMaterial, this.props.globalState);
         StringTools.DownloadAsFile(this.props.globalState.hostDocument, json, "nodeMaterial.json");
@@ -412,6 +421,9 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                         }
 
                     </LineContainerComponent>
+                    <LineContainerComponent title="FRAME">
+                        <FileButtonLineComponent label="Load Frame" uploadName={'frame-upload'} onClick={(file) => this.loadFrame(file)} accept=".json" />
+                    </LineContainerComponent>
                     {
                         !this.props.globalState.customSave &&
                         <LineContainerComponent title="SNIPPET">

+ 8 - 3
nodeEditor/src/diagram/graphCanvas.tsx

@@ -13,7 +13,7 @@ import { FragmentOutputBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fr
 import { InputBlock } from 'babylonjs/Materials/Node/Blocks/Input/inputBlock';
 import { DataStorage } from 'babylonjs/Misc/dataStorage';
 import { GraphFrame } from './graphFrame';
-import { IEditorData } from '../nodeLocationInfo';
+import { IEditorData, IFrameData } from '../nodeLocationInfo';
 import { FrameNodePort } from './frameNodePort';
 
 require("./graphCanvas.scss");
@@ -848,7 +848,6 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
         }
 
         this._frames = [];
-
         this.x = editorData.x || 0;
         this.y = editorData.y || 0;
         this.zoom = editorData.zoom || 1;
@@ -858,9 +857,15 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
             for (var frameData of editorData.frames) {
                 var frame = GraphFrame.Parse(frameData, this, editorData.map);
                 this._frames.push(frame);
-            }
+            }  
         }
     }
+
+    addFrame(frameData: IFrameData) {
+            const frame = GraphFrame.Parse(frameData, this, this.props.globalState.nodeMaterial.editorData.map);
+            this._frames.push(frame);
+            this.globalState.onSelectionChangedObservable.notifyObservers(frame);
+    }
  
     render() {
         return (

+ 1 - 0
nodeEditor/src/globalState.ts

@@ -40,6 +40,7 @@ export class GlobalState {
     onSelectionBoxMoved = new Observable<ClientRect | DOMRect>();
     onFrameCreatedObservable = new Observable<GraphFrame>();
     onCandidatePortSelectedObservable = new Observable<Nullable<NodePort | FrameNodePort>>();
+    onImportFrameObservable = new Observable<any>();
     onGraphNodeRemovalObservable = new Observable<GraphNode>();
     onGetNodeFromBlock: (block: NodeMaterialBlock) => GraphNode;
     onGridSizeChanged = new Observable<void>();

+ 42 - 24
nodeEditor/src/graphEditor.tsx

@@ -22,6 +22,7 @@ import { GraphNode } from './diagram/graphNode';
 import { GraphFrame } from './diagram/graphFrame';
 import * as ReactDOM from 'react-dom';
 import { IInspectorOptions } from "babylonjs/Debug/debugLayer";
+import { _TypeStore } from 'babylonjs/Misc/typeStore';
 
 
 require("./main.scss");
@@ -157,6 +158,17 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
             }
         });
 
+        this.props.globalState.onImportFrameObservable.add((source: any) => {
+            const frameData = source.editorData.frames[0];
+
+            // create new graph nodes for only blocks from frame (last blocks added)
+            this.props.globalState.nodeMaterial.attachedBlocks.slice(-(frameData.blocks.length)).forEach((block: NodeMaterialBlock) => {
+                this.createNodeFromObject(block);
+            });
+            this._graphCanvas.addFrame(frameData);
+            this.reOrganize(this.props.globalState.nodeMaterial.editorData, true);
+        })
+
         this.props.globalState.onZoomToFitRequiredObservable.add(() => {
             this.zoomToFit();
         });
@@ -409,31 +421,35 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
 
         // Load graph of nodes from the material
         if (this.props.globalState.nodeMaterial) {
-            var material = this.props.globalState.nodeMaterial;
-            material._vertexOutputNodes.forEach((n: any) => {
-                this.createNodeFromObject(n);
-            });
-            material._fragmentOutputNodes.forEach((n: any) => {
-                this.createNodeFromObject(n);
-            });
+            this.loadGraph()
+        }
 
-            material.attachedBlocks.forEach((n: any) => {
-                this.createNodeFromObject(n);
-            });
+        this.reOrganize(editorData);
+    }
 
-            // Links
-            material.attachedBlocks.forEach((n: any) => {
-                if (n.inputs.length) {
-                    for (var input of n.inputs) {
-                        if (input.isConnected) {
-                            this._graphCanvas.connectPorts(input.connectedPoint!, input);
-                        }
+    loadGraph() {
+        var material = this.props.globalState.nodeMaterial;
+        material._vertexOutputNodes.forEach((n: any) => {
+            this.createNodeFromObject(n, true);
+        });
+        material._fragmentOutputNodes.forEach((n: any) => {
+            this.createNodeFromObject(n, true);
+        });
+
+        material.attachedBlocks.forEach((n: any) => {
+            this.createNodeFromObject(n, true);
+        });
+
+        // Links
+        material.attachedBlocks.forEach((n: any) => {
+            if (n.inputs.length) {
+                for (var input of n.inputs) {
+                    if (input.isConnected) {
+                        this._graphCanvas.connectPorts(input.connectedPoint!, input);
                     }
                 }
-            });            
-        }
-
-        this.reOrganize(editorData);
+            }
+        });           
     }
 
     showWaitScreen() {
@@ -444,7 +460,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
         this.props.globalState.hostDocument.querySelector(".wait-screen")?.classList.add("hidden");
     }
 
-    reOrganize(editorData: Nullable<IEditorData> = null) {
+    reOrganize(editorData: Nullable<IEditorData> = null, isImportingAFrame = false) {
         this.showWaitScreen();
         this._graphCanvas._isLoading = true; // Will help loading large graphes
 
@@ -463,8 +479,10 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
                         }
                     }
                 }
-
-                this._graphCanvas.processEditorData(editorData);
+                
+                if (!isImportingAFrame){
+                    this._graphCanvas.processEditorData(editorData);
+                }
             }
 
             this._graphCanvas._isLoading = false;

+ 7 - 0
nodeEditor/src/serializationTools.ts

@@ -48,4 +48,11 @@ export class SerializationTools {
         globalState.onIsLoadingChanged.notifyObservers(true);
         globalState.nodeMaterial!.loadFromSerialization(serializationObject, "");
     }
+
+    public static AddFrameToMaterial(serializationObject: any, globalState: GlobalState, currentMaterial: NodeMaterial) {
+        globalState.onIsLoadingChanged.notifyObservers(true);
+        this.UpdateLocations(currentMaterial, globalState);
+        globalState.nodeMaterial!.loadFromSerialization(serializationObject, "", true);
+        globalState.onImportFrameObservable.notifyObservers(serializationObject);
+    }
 }

+ 3 - 2
nodeEditor/src/sharedComponents/fileButtonLineComponent.tsx

@@ -4,6 +4,7 @@ interface IFileButtonLineComponentProps {
     label: string;
     onClick: (file: File) => void;
     accept: string;
+    uploadName?: string;
 }
 
 export class FileButtonLineComponent extends React.Component<IFileButtonLineComponentProps> {
@@ -27,10 +28,10 @@ export class FileButtonLineComponent extends React.Component<IFileButtonLineComp
     render() {
         return (
             <div className="buttonLine">
-                <label htmlFor="file-upload" className="file-upload">
+                <label htmlFor={this.props.uploadName ? this.props.uploadName : "file-upload"} className="file-upload">
                     {this.props.label}
                 </label>
-                <input ref={this.uploadRef} id="file-upload" type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
+                <input ref={this.uploadRef} id={this.props.uploadName ? this.props.uploadName : "file-upload"} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
             </div>
         );
     }

+ 21 - 15
src/Materials/Node/nodeMaterial.ts

@@ -1606,9 +1606,12 @@ export class NodeMaterial extends PushMaterial {
      * Clear the current graph and load a new one from a serialization object
      * @param source defines the JSON representation of the material
      * @param rootUrl defines the root URL to use to load textures and relative dependencies
+     * @param isImportingAFrame defines whether or not the source is a single frame json
      */
-    public loadFromSerialization(source: any, rootUrl: string = "") {
-        this.clear();
+    public loadFromSerialization(source: any, rootUrl: string = "", isImportingAFrame = false) {
+        if (!isImportingAFrame) {
+            this.clear();
+        }
 
         let map: {[key: number]: NodeMaterialBlock} = {};
 
@@ -1625,21 +1628,22 @@ export class NodeMaterial extends PushMaterial {
         }
 
         // Connections
+        if (!isImportingAFrame) {
+            // Starts with input blocks only
+            for (var blockIndex = 0; blockIndex < source.blocks.length; blockIndex++) {
+                let parsedBlock = source.blocks[blockIndex];
+                let block = map[parsedBlock.id];
 
-        // Starts with input blocks only
-        for (var blockIndex = 0; blockIndex < source.blocks.length; blockIndex++) {
-            let parsedBlock = source.blocks[blockIndex];
-            let block = map[parsedBlock.id];
-
-            if (block.inputs.length) {
-                continue;
+                if (block.inputs.length) {
+                    continue;
+                }
+                this._restoreConnections(block, source, map);
             }
-            this._restoreConnections(block, source, map);
-        }
 
-        // Outputs
-        for (var outputNodeId of source.outputNodes) {
-            this.addOutputNode(map[outputNodeId]);
+            // Outputs
+            for (var outputNodeId of source.outputNodes) {
+                this.addOutputNode(map[outputNodeId]);
+            }
         }
 
         // UI related info
@@ -1674,7 +1678,9 @@ export class NodeMaterial extends PushMaterial {
             this.editorData.map = blockMap;
         }
 
-        this._mode = source.mode ?? NodeMaterialModes.Material;
+        if (!isImportingAFrame) {
+            this._mode = source.mode ?? NodeMaterialModes.Material;
+        }
     }
 
     /**