using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Graphing; using UnityEngine; using UnityEditor.ShaderGraph; using UnityEditor.ShaderGraph.Internal; using UnityEditor.ShaderGraph.Serialization; namespace UnityEditor.ShaderGraph { enum CopyPasteGraphSource { Default, Duplicate } [Serializable] sealed class CopyPasteGraph : JsonObject { CopyPasteGraphSource m_CopyPasteGraphSource; [SerializeField] List<Edge> m_Edges = new List<Edge>(); [SerializeField] List<JsonData<AbstractMaterialNode>> m_Nodes = new List<JsonData<AbstractMaterialNode>>(); [SerializeField] List<JsonData<GroupData>> m_Groups = new List<JsonData<GroupData>>(); [SerializeField] List<JsonData<StickyNoteData>> m_StickyNotes = new List<JsonData<StickyNoteData>>(); [SerializeField] List<JsonRef<ShaderInput>> m_Inputs = new List<JsonRef<ShaderInput>>(); [SerializeField] List<JsonData<CategoryData>> m_Categories = new List<JsonData<CategoryData>>(); // The meta properties are properties that are not copied into the target graph // but sent along to allow property nodes to still hvae the data from the original // property present. [SerializeField] List<JsonData<AbstractShaderProperty>> m_MetaProperties = new List<JsonData<AbstractShaderProperty>>(); [SerializeField] List<string> m_MetaPropertyIds = new List<string>(); // The meta keywords are keywords that are required by keyword nodes // These are copied into the target graph when there is no collision [SerializeField] List<JsonData<ShaderKeyword>> m_MetaKeywords = new List<JsonData<ShaderKeyword>>(); [SerializeField] List<string> m_MetaKeywordIds = new List<string>(); [SerializeField] List<JsonData<ShaderDropdown>> m_MetaDropdowns = new List<JsonData<ShaderDropdown>>(); [SerializeField] List<string> m_MetaDropdownIds = new List<string>(); public CopyPasteGraph() { } public CopyPasteGraph(IEnumerable<GroupData> groups, IEnumerable<AbstractMaterialNode> nodes, IEnumerable<Edge> edges, IEnumerable<ShaderInput> inputs, IEnumerable<CategoryData> categories, IEnumerable<AbstractShaderProperty> metaProperties, IEnumerable<ShaderKeyword> metaKeywords, IEnumerable<ShaderDropdown> metaDropdowns, IEnumerable<StickyNoteData> notes, bool keepOutputEdges = false, bool removeOrphanEdges = true, CopyPasteGraphSource copyPasteGraphSource = CopyPasteGraphSource.Default) { m_CopyPasteGraphSource = copyPasteGraphSource; if (groups != null) { foreach (var groupData in groups) AddGroup(groupData); } if (notes != null) { foreach (var stickyNote in notes) AddNote(stickyNote); } var nodeSet = new HashSet<AbstractMaterialNode>(); if (nodes != null) { foreach (var node in nodes.Distinct()) { if (!node.canCopyNode) { throw new InvalidOperationException($"Cannot copy node {node.name} ({node.objectId})."); } nodeSet.Add(node); AddNode(node); foreach (var edge in NodeUtils.GetAllEdges(node)) AddEdge((Edge)edge); } } if (edges != null) { foreach (var edge in edges) AddEdge(edge); } if (inputs != null) { foreach (var input in inputs) AddInput(input); } if (categories != null) { foreach (var category in categories) AddCategory(category); } if (metaProperties != null) { foreach (var metaProperty in metaProperties.Distinct()) AddMetaProperty(metaProperty); } if (metaKeywords != null) { foreach (var metaKeyword in metaKeywords.Distinct()) AddMetaKeyword(metaKeyword); } if (metaDropdowns != null) { foreach (var metaDropdown in metaDropdowns.Distinct()) AddMetaDropdown(metaDropdown); } var distinct = m_Edges.Distinct(); if (removeOrphanEdges) { distinct = distinct.Where(edge => nodeSet.Contains(edge.inputSlot.node) || (keepOutputEdges && nodeSet.Contains(edge.outputSlot.node))); } m_Edges = distinct.ToList(); } public bool IsInputCategorized(ShaderInput shaderInput) { foreach (var category in categories) { if (category.IsItemInCategory(shaderInput)) return true; } return false; } // The only situation in which an input has an identical reference name to another input in a category, while not being the same instance, is if they are duplicates public bool IsInputDuplicatedFromCategory(ShaderInput shaderInput, CategoryData inputCategory, GraphData targetGraphData) { foreach (var child in inputCategory.Children) { if (child.referenceName.Equals(shaderInput.referenceName, StringComparison.Ordinal) && child.objectId != shaderInput.objectId) { return true; } } // Need to check if they share same graph owner as well, if not then we can early out bool inputBelongsToTargetGraph = targetGraphData.ContainsInput(shaderInput); if (inputBelongsToTargetGraph == false) return false; return false; } void AddGroup(GroupData group) { m_Groups.Add(group); } void AddNote(StickyNoteData stickyNote) { m_StickyNotes.Add(stickyNote); } void AddNode(AbstractMaterialNode node) { m_Nodes.Add(node); } void AddEdge(Edge edge) { m_Edges.Add(edge); } void AddInput(ShaderInput input) { m_Inputs.Add(input); } void AddCategory(CategoryData category) { m_Categories.Add(category); } void AddMetaProperty(AbstractShaderProperty metaProperty) { m_MetaProperties.Add(metaProperty); m_MetaPropertyIds.Add(metaProperty.objectId); } void AddMetaKeyword(ShaderKeyword metaKeyword) { m_MetaKeywords.Add(metaKeyword); m_MetaKeywordIds.Add(metaKeyword.objectId); } void AddMetaDropdown(ShaderDropdown metaDropdown) { m_MetaDropdowns.Add(metaDropdown); m_MetaDropdownIds.Add(metaDropdown.objectId); } public IEnumerable<T> GetNodes<T>() { return m_Nodes.SelectValue().OfType<T>(); } public DataValueEnumerable<GroupData> groups => m_Groups.SelectValue(); public DataValueEnumerable<StickyNoteData> stickyNotes => m_StickyNotes.SelectValue(); public IEnumerable<Edge> edges { get { return m_Edges; } } public RefValueEnumerable<ShaderInput> inputs { get { return m_Inputs.SelectValue(); } } public DataValueEnumerable<CategoryData> categories { get { return m_Categories.SelectValue(); } } public DataValueEnumerable<AbstractShaderProperty> metaProperties { get { return m_MetaProperties.SelectValue(); } } public DataValueEnumerable<ShaderKeyword> metaKeywords { get { return m_MetaKeywords.SelectValue(); } } public DataValueEnumerable<ShaderDropdown> metaDropdowns { get { return m_MetaDropdowns.SelectValue(); } } public IEnumerable<string> metaPropertyIds => m_MetaPropertyIds; public IEnumerable<string> metaKeywordIds => m_MetaKeywordIds; public CopyPasteGraphSource copyPasteGraphSource => m_CopyPasteGraphSource; public override void OnAfterMultiDeserialize(string json) { // should we add support for versioning old CopyPasteGraphs from old versions of Unity? // so you can copy from old paste to new foreach (var node in m_Nodes.SelectValue()) { node.UpdateNodeAfterDeserialization(); node.SetupSlots(); } } internal static CopyPasteGraph FromJson(string copyBuffer, GraphData targetGraph) { try { var graph = new CopyPasteGraph(); MultiJson.Deserialize(graph, copyBuffer, targetGraph, true); return graph; } catch { // ignored. just means copy buffer was not a graph :( return null; } } } }