Browse Source

Add support to import frames (frame nodes unconnected)

Kyle Belfort 5 years ago
parent
commit
a74fdfcc16

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

@@ -49,6 +49,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">

+ 19 - 12
nodeEditor/src/diagram/graphCanvas.tsx

@@ -841,23 +841,30 @@ export class GraphCanvasComponent extends React.Component<IGraphCanvasComponentP
         this.props.globalState.onRebuildRequiredObservable.notifyObservers();
     }
 
-    processEditorData(editorData: IEditorData) {
-        const frames = this._frames.splice(0);
-        for (var frame of frames) {
-            frame.dispose();
-        }
-
-        this._frames = [];
+    processEditorData(editorData: IEditorData, isImportingAFrame = false) {
+        if (!isImportingAFrame) {
+            const frames = this._frames.splice(0);
+            for (var frame of frames) {
+                frame.dispose();
+            }
 
-        this.x = editorData.x || 0;
-        this.y = editorData.y || 0;
-        this.zoom = editorData.zoom || 1;
+            this._frames = [];
+            this.x = editorData.x || 0;
+            this.y = editorData.y || 0;
+            this.zoom = editorData.zoom || 1;
+        }
 
         // Frames
         if (editorData.frames) {
-            for (var frameData of editorData.frames) {
-                var frame = GraphFrame.Parse(frameData, this, editorData.map);
+            if (isImportingAFrame) {
+                var frame = GraphFrame.Parse(editorData.frames[editorData.frames.length - 1], this, editorData.map);
                 this._frames.push(frame);
+                this.globalState.onSelectionChangedObservable.notifyObservers(frame);
+            } else {
+                for (var frameData of editorData.frames) {
+                    var frame = GraphFrame.Parse(frameData, this, editorData.map);
+                    this._frames.push(frame);
+                }
             }
         }
     }

+ 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<void>();
     onGraphNodeRemovalObservable = new Observable<GraphNode>();
     onGetNodeFromBlock: (block: NodeMaterialBlock) => GraphNode;
     onGridSizeChanged = new Observable<void>();

+ 32 - 23
nodeEditor/src/graphEditor.tsx

@@ -157,6 +157,11 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
             }
         });
 
+        this.props.globalState.onImportFrameObservable.add(() => {
+            this.loadGraph();
+            this.reOrganize(this.props.globalState.nodeMaterial.editorData, true);
+        })
+
         this.props.globalState.onZoomToFitRequiredObservable.add(() => {
             this.zoomToFit();
         });
@@ -409,31 +414,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 +453,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
 
@@ -464,7 +473,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
                     }
                 }
 
-                this._graphCanvas.processEditorData(editorData);
+                this._graphCanvas.processEditorData(editorData, isImportingAFrame);
             }
 
             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!.addFrameNodesFromeSerialization(serializationObject, "");
+        globalState.onImportFrameObservable.notifyObservers();
+    }
 }

+ 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>
         );
     }

+ 47 - 0
src/Materials/Node/nodeMaterial.ts

@@ -1678,6 +1678,53 @@ export class NodeMaterial extends PushMaterial {
     }
 
     /**
+     * Load new blocks and frame from a frame serialization object
+     * @param source defines the JSON representation of the frame
+     * @param rootUrl defines the root URL to use to load textures and relative dependencies
+     */
+    public addFrameNodesFromeSerialization(source: any, rootUrl: string = "") {
+        let map: {[key: number]: NodeMaterialBlock} = {};
+
+        // Create blocks
+        for (var parsedBlock of source.blocks) {
+            let blockType = _TypeStore.GetClass(parsedBlock.customType);
+            if (blockType) {
+                let block: NodeMaterialBlock = new blockType();
+                block._deserialize(parsedBlock, this.getScene(), rootUrl);
+                map[parsedBlock.id] = block;
+
+                this.attachedBlocks.push(block);
+            }
+        }
+
+        if (source.locations || source.editorData && source.editorData.locations) {
+            let locations: {
+                blockId: number;
+                x: number;
+                y: number;
+            }[] = source.locations || source.editorData.locations;
+
+            for (var location of locations) {
+                if (map[location.blockId]) {
+                    location.blockId = map[location.blockId].uniqueId;
+                }
+            }
+
+            this.editorData.locations = this.editorData.locations.concat(locations);
+            this.editorData.frames = this.editorData.frames.concat(source.editorData.frames);
+
+            let blockMap: number[] = [];
+
+            for (var key in map) {
+                blockMap[key] = map[key].uniqueId;
+            }
+
+            this.editorData.map = blockMap;
+
+        }
+    }
+
+    /**
      * Makes a duplicate of the current material.
      * @param name - name to use for the new material.
      */