public void TestSerializableSlotCanSerialize() { var toSerialize = new List <MaterialSlot>() { new TestSlot(0, "InSlot", SlotType.Input, 0), new TestSlot(1, "OutSlot", SlotType.Output, 5), }; DummyJsonHolder dummyJsonHolder = new DummyJsonHolder(toSerialize); var serialized = MultiJson.Serialize(dummyJsonHolder); DummyJsonHolder dummyJsonHolder1 = new DummyJsonHolder(); MultiJson.Deserialize(dummyJsonHolder1, serialized); Assert.AreEqual(2, dummyJsonHolder1.testSlots.Count); var loaded = new List <MaterialSlot>(dummyJsonHolder1.testSlots.SelectValue()); Assert.IsInstanceOf <MaterialSlot>(loaded[0]); Assert.IsInstanceOf <MaterialSlot>(loaded[1]); Assert.AreEqual(0, loaded[0].id); Assert.AreEqual("InSlot(4)", loaded[0].displayName); Assert.IsTrue(loaded[0].isInputSlot); Assert.AreEqual(1, loaded[1].id); Assert.AreEqual("OutSlot(4)", loaded[1].displayName); Assert.IsTrue(loaded[1].isOutputSlot); }
// returns true only when the graph in this window would serialize different from the last time we loaded or saved it internal bool GraphHasChangedSinceLastSerialization() { Assert.IsTrue(graphObject?.graph != null); // this should be checked by calling code var currentGraphJson = MultiJson.Serialize(graphObject.graph); return(!string.Equals(currentGraphJson, m_LastSerializedFileContents, StringComparison.Ordinal)); }
// returns true only when saving the graph in this window would serialize different from the file on disk internal bool GraphIsDifferentFromFileOnDisk() { Assert.IsTrue(graphObject?.graph != null); // this should be checked by calling code var currentGraphJson = MultiJson.Serialize(graphObject.graph); var currentFileJson = ReadAssetFile(); return(!string.Equals(currentGraphJson, currentFileJson, StringComparison.Ordinal)); }
public static bool WriteShaderGraphToDisk(string path, GraphData data) { if (data == null) { throw new ArgumentNullException(nameof(data)); } return(WriteToDisk(path, MultiJson.Serialize(data))); }
bool IsDirty() { if (m_Deleted || graphObject.graph == null) { return(false); // Not dirty; it's gone. } var currentJson = MultiJson.Serialize(graphObject.graph); var fileJson = File.ReadAllText(AssetDatabase.GUIDToAssetPath(selectedGuid)); return(!string.Equals(currentJson, fileJson, StringComparison.Ordinal)); }
public void OnBeforeSerialize() { if (graph != null) { var json = MultiJson.Serialize(graph); m_SerializedGraph = new SerializationHelper.JSONSerializedElement { JSONnodeData = json }; m_IsSubGraph = graph.isSubGraph; m_AssetGuid = graph.assetGuid; } }
public void WriteData(IEnumerable <AbstractShaderProperty> inputs, IEnumerable <ShaderKeyword> keywords, IEnumerable <ShaderDropdown> dropdowns, IEnumerable <AbstractShaderProperty> nodeProperties, IEnumerable <MaterialSlot> outputs, IEnumerable <Target> unsupportedTargets) { if (m_SubGraphData == null) { m_SubGraphData = new SubGraphData(); m_SubGraphData.OverrideObjectId(assetGuid, "_subGraphData"); } m_SubGraphData.inputs.Clear(); m_SubGraphData.keywords.Clear(); m_SubGraphData.dropdowns.Clear(); m_SubGraphData.nodeProperties.Clear(); m_SubGraphData.outputs.Clear(); m_SubGraphData.unsupportedTargets.Clear(); foreach (var input in inputs) { m_SubGraphData.inputs.Add(input); } foreach (var keyword in keywords) { m_SubGraphData.keywords.Add(keyword); } foreach (var dropdown in dropdowns) { m_SubGraphData.dropdowns.Add(dropdown); } foreach (var nodeProperty in nodeProperties) { m_SubGraphData.nodeProperties.Add(nodeProperty); } foreach (var output in outputs) { m_SubGraphData.outputs.Add(output); } foreach (var unsupportedTarget in unsupportedTargets) { m_SubGraphData.unsupportedTargets.Add(unsupportedTarget); } var json = MultiJson.Serialize(m_SubGraphData); m_SerializedSubGraphData = new SerializationHelper.JSONSerializedElement() { JSONnodeData = json }; m_SubGraphData = null; }
// if successfully written to disk, returns the serialized file contents as a string // on failure, returns null public static string WriteShaderGraphToDisk(string path, GraphData data) { if (data == null) { // Returning false may be better than throwing this exception, in terms of preserving data. // But if GraphData is null, it's likely we don't have any data to preserve anyways. // So this exception seems fine for now. throw new ArgumentNullException(nameof(data)); } var text = MultiJson.Serialize(data); if (WriteToDisk(path, text)) { return(text); } else { return(null); } }
public void WriteData(IEnumerable <AbstractShaderProperty> inputs, IEnumerable <ShaderKeyword> keywords, IEnumerable <AbstractShaderProperty> nodeProperties, IEnumerable <MaterialSlot> outputs) { if (m_SubGraphData == null) { m_SubGraphData = new SubGraphData(); } m_SubGraphData.inputs.Clear(); m_SubGraphData.keywords.Clear(); m_SubGraphData.nodeProperties.Clear(); m_SubGraphData.outputs.Clear(); foreach (var input in inputs) { m_SubGraphData.inputs.Add(input); } foreach (var keyword in keywords) { m_SubGraphData.keywords.Add(keyword); } foreach (var nodeProperty in nodeProperties) { m_SubGraphData.nodeProperties.Add(nodeProperty); } foreach (var output in outputs) { m_SubGraphData.outputs.Add(output); } var json = MultiJson.Serialize(m_SubGraphData); m_SerializedSubGraphData = new SerializationHelper.JSONSerializedElement() { JSONnodeData = json }; m_SubGraphData = null; }
public void ToSubGraph() { var graphView = graphEditorView.graphView; string path; string sessionStateResult = SessionState.GetString(k_PrevSubGraphPathKey, k_PrevSubGraphPathDefaultValue); string pathToOriginSG = Path.GetDirectoryName(AssetDatabase.GUIDToAssetPath(selectedGuid)); if (!sessionStateResult.Equals(k_PrevSubGraphPathDefaultValue)) { path = sessionStateResult; } else { path = pathToOriginSG; } path = EditorUtility.SaveFilePanelInProject("Save Sub Graph", "New Shader Sub Graph", ShaderSubGraphImporter.Extension, "", path); path = path.Replace(Application.dataPath, "Assets"); if (path.Length == 0) { return; } graphObject.RegisterCompleteObjectUndo("Convert To Subgraph"); var nodes = graphView.selection.OfType <IShaderNodeView>().Where(x => !(x.node is PropertyNode || x.node is SubGraphOutputNode)).Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(); var bounds = Rect.MinMaxRect(float.PositiveInfinity, float.PositiveInfinity, float.NegativeInfinity, float.NegativeInfinity); foreach (var node in nodes) { var center = node.drawState.position.center; bounds = Rect.MinMaxRect( Mathf.Min(bounds.xMin, center.x), Mathf.Min(bounds.yMin, center.y), Mathf.Max(bounds.xMax, center.x), Mathf.Max(bounds.yMax, center.y)); } var middle = bounds.center; bounds.center = Vector2.zero; // Collect graph inputs var graphInputs = graphView.selection.OfType <BlackboardField>().Select(x => x.userData as ShaderInput); // Collect the property nodes and get the corresponding properties var propertyNodes = graphView.selection.OfType <IShaderNodeView>().Where(x => (x.node is PropertyNode)).Select(x => ((PropertyNode)x.node).property); var metaProperties = graphView.graph.properties.Where(x => propertyNodes.Contains(x)); // Collect the keyword nodes and get the corresponding keywords var keywordNodes = graphView.selection.OfType <IShaderNodeView>().Where(x => (x.node is KeywordNode)).Select(x => ((KeywordNode)x.node).keyword); var metaKeywords = graphView.graph.keywords.Where(x => keywordNodes.Contains(x)); var copyPasteGraph = new CopyPasteGraph(graphView.selection.OfType <ShaderGroup>().Select(x => x.userData), graphView.selection.OfType <IShaderNodeView>().Where(x => !(x.node is PropertyNode || x.node is SubGraphOutputNode)).Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(), graphView.selection.OfType <Edge>().Select(x => x.userData as Graphing.Edge), graphInputs, metaProperties, metaKeywords, graphView.selection.OfType <StickyNote>().Select(x => x.userData), true); // why do we serialize and deserialize only to make copies of everything in the steps below? // is this just to clear out all non-serialized data? var deserialized = CopyPasteGraph.FromJson(MultiJson.Serialize(copyPasteGraph), graphView.graph); if (deserialized == null) { return; } var subGraph = new GraphData { isSubGraph = true, path = "Sub Graphs" }; var subGraphOutputNode = new SubGraphOutputNode(); { var drawState = subGraphOutputNode.drawState; drawState.position = new Rect(new Vector2(bounds.xMax + 200f, 0f), drawState.position.size); subGraphOutputNode.drawState = drawState; } subGraph.AddNode(subGraphOutputNode); subGraph.outputNode = subGraphOutputNode; // Always copy deserialized keyword inputs foreach (ShaderKeyword keyword in deserialized.metaKeywords) { var copiedInput = (ShaderKeyword)keyword.Copy(); subGraph.SanitizeGraphInputName(copiedInput); subGraph.SanitizeGraphInputReferenceName(copiedInput, keyword.overrideReferenceName); subGraph.AddGraphInput(copiedInput); // Update the keyword nodes that depends on the copied keyword var dependentKeywordNodes = deserialized.GetNodes <KeywordNode>().Where(x => x.keyword == keyword); foreach (var node in dependentKeywordNodes) { node.owner = graphView.graph; node.keyword = copiedInput; } } foreach (GroupData groupData in deserialized.groups) { subGraph.CreateGroup(groupData); } foreach (var node in deserialized.GetNodes <AbstractMaterialNode>()) { var drawState = node.drawState; drawState.position = new Rect(drawState.position.position - middle, drawState.position.size); node.drawState = drawState; // Checking if the group guid is also being copied. // If not then nullify that guid if (node.group != null && !subGraph.groups.Contains(node.group)) { node.group = null; } subGraph.AddNode(node); } foreach (var note in deserialized.stickyNotes) { if (note.group != null && !subGraph.groups.Contains(note.group)) { note.group = null; } subGraph.AddStickyNote(note); } // figure out what needs remapping var externalOutputSlots = new List <Graphing.Edge>(); var externalInputSlots = new List <Graphing.Edge>(); foreach (var edge in deserialized.edges) { var outputSlot = edge.outputSlot; var inputSlot = edge.inputSlot; var outputSlotExistsInSubgraph = subGraph.ContainsNode(outputSlot.node); var inputSlotExistsInSubgraph = subGraph.ContainsNode(inputSlot.node); // pasting nice internal links! if (outputSlotExistsInSubgraph && inputSlotExistsInSubgraph) { subGraph.Connect(outputSlot, inputSlot); } // one edge needs to go to outside world else if (outputSlotExistsInSubgraph) { externalInputSlots.Add(edge); } else if (inputSlotExistsInSubgraph) { externalOutputSlots.Add(edge); } } // Find the unique edges coming INTO the graph var uniqueIncomingEdges = externalOutputSlots.GroupBy( edge => edge.outputSlot, edge => edge, (key, edges) => new { slotRef = key, edges = edges.ToList() }); var externalInputNeedingConnection = new List <KeyValuePair <IEdge, AbstractShaderProperty> >(); var amountOfProps = uniqueIncomingEdges.Count(); const int height = 40; const int subtractHeight = 20; var propPos = new Vector2(0, -((amountOfProps / 2) + height) - subtractHeight); foreach (var group in uniqueIncomingEdges) { var sr = group.slotRef; var fromNode = sr.node; var fromSlot = sr.slot; var materialGraph = graphObject.graph; var fromProperty = fromNode is PropertyNode fromPropertyNode ? materialGraph.properties.FirstOrDefault(p => p == fromPropertyNode.property) : null; AbstractShaderProperty prop; switch (fromSlot.concreteValueType) { case ConcreteSlotValueType.Texture2D: prop = new Texture2DShaderProperty(); break; case ConcreteSlotValueType.Texture2DArray: prop = new Texture2DArrayShaderProperty(); break; case ConcreteSlotValueType.Texture3D: prop = new Texture3DShaderProperty(); break; case ConcreteSlotValueType.Cubemap: prop = new CubemapShaderProperty(); break; case ConcreteSlotValueType.Vector4: prop = new Vector4ShaderProperty(); break; case ConcreteSlotValueType.Vector3: prop = new Vector3ShaderProperty(); break; case ConcreteSlotValueType.Vector2: prop = new Vector2ShaderProperty(); break; case ConcreteSlotValueType.Vector1: prop = new Vector1ShaderProperty(); break; case ConcreteSlotValueType.Boolean: prop = new BooleanShaderProperty(); break; case ConcreteSlotValueType.Matrix2: prop = new Matrix2ShaderProperty(); break; case ConcreteSlotValueType.Matrix3: prop = new Matrix3ShaderProperty(); break; case ConcreteSlotValueType.Matrix4: prop = new Matrix4ShaderProperty(); break; case ConcreteSlotValueType.SamplerState: prop = new SamplerStateShaderProperty(); break; case ConcreteSlotValueType.Gradient: prop = new GradientShaderProperty(); break; case ConcreteSlotValueType.VirtualTexture: prop = new VirtualTextureShaderProperty() { // also copy the VT settings over from the original property (if there is one) value = (fromProperty as VirtualTextureShaderProperty)?.value ?? new SerializableVirtualTexture() }; break; default: throw new ArgumentOutOfRangeException(); } prop.displayName = fromProperty != null ? fromProperty.displayName : fromSlot.concreteValueType.ToString(); prop.displayName = GraphUtil.SanitizeName(subGraph.addedInputs.Select(p => p.displayName), "{0} ({1})", prop.displayName); subGraph.AddGraphInput(prop); var propNode = new PropertyNode(); { var drawState = propNode.drawState; drawState.position = new Rect(new Vector2(bounds.xMin - 300f, 0f) + propPos, drawState.position.size); propPos += new Vector2(0, height); propNode.drawState = drawState; } subGraph.AddNode(propNode); propNode.property = prop; foreach (var edge in group.edges) { subGraph.Connect( new SlotReference(propNode, PropertyNode.OutputSlotId), edge.inputSlot); externalInputNeedingConnection.Add(new KeyValuePair <IEdge, AbstractShaderProperty>(edge, prop)); } } var uniqueOutgoingEdges = externalInputSlots.GroupBy( edge => edge.outputSlot, edge => edge, (key, edges) => new { slot = key, edges = edges.ToList() }); var externalOutputsNeedingConnection = new List <KeyValuePair <IEdge, IEdge> >(); foreach (var group in uniqueOutgoingEdges) { var outputNode = subGraph.outputNode as SubGraphOutputNode; AbstractMaterialNode node = group.edges[0].outputSlot.node; MaterialSlot slot = node.FindSlot <MaterialSlot>(group.edges[0].outputSlot.slotId); var slotId = outputNode.AddSlot(slot.concreteValueType); var inputSlotRef = new SlotReference(outputNode, slotId); foreach (var edge in group.edges) { var newEdge = subGraph.Connect(edge.outputSlot, inputSlotRef); externalOutputsNeedingConnection.Add(new KeyValuePair <IEdge, IEdge>(edge, newEdge)); } } if (FileUtilities.WriteShaderGraphToDisk(path, subGraph)) { AssetDatabase.ImportAsset(path); } // Store path for next time if (!pathToOriginSG.Equals(Path.GetDirectoryName(path))) { SessionState.SetString(k_PrevSubGraphPathKey, Path.GetDirectoryName(path)); } else { // Or continue to make it so that next time it will open up in the converted-from SG's directory SessionState.EraseString(k_PrevSubGraphPathKey); } var loadedSubGraph = AssetDatabase.LoadAssetAtPath(path, typeof(SubGraphAsset)) as SubGraphAsset; if (loadedSubGraph == null) { return; } var subGraphNode = new SubGraphNode(); var ds = subGraphNode.drawState; ds.position = new Rect(middle - new Vector2(100f, 150f), Vector2.zero); subGraphNode.drawState = ds; // Add the subgraph into the group if the nodes was all in the same group group var firstNode = copyPasteGraph.GetNodes <AbstractMaterialNode>().FirstOrDefault(); if (firstNode != null && copyPasteGraph.GetNodes <AbstractMaterialNode>().All(x => x.group == firstNode.group)) { subGraphNode.group = firstNode.group; } subGraphNode.asset = loadedSubGraph; graphObject.graph.AddNode(subGraphNode); foreach (var edgeMap in externalInputNeedingConnection) { graphObject.graph.Connect(edgeMap.Key.outputSlot, new SlotReference(subGraphNode, edgeMap.Value.guid.GetHashCode())); } foreach (var edgeMap in externalOutputsNeedingConnection) { graphObject.graph.Connect(new SlotReference(subGraphNode, edgeMap.Value.inputSlot.slotId), edgeMap.Key.inputSlot); } graphObject.graph.RemoveElements( graphView.selection.OfType <IShaderNodeView>().Select(x => x.node).Where(x => x.allowedInSubGraph).ToArray(), new IEdge[] {}, new GroupData[] {}, graphView.selection.OfType <StickyNote>().Select(x => x.userData).ToArray()); graphObject.graph.ValidateGraph(); }