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 m_Edges = new List(); [SerializeField] List> m_Nodes = new List>(); [SerializeField] List> m_Groups = new List>(); [SerializeField] List> m_StickyNotes = new List>(); [SerializeField] List> m_Inputs = new List>(); [SerializeField] List> m_Categories = new List>(); // 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> m_MetaProperties = new List>(); [SerializeField] List m_MetaPropertyIds = new List(); // 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> m_MetaKeywords = new List>(); [SerializeField] List m_MetaKeywordIds = new List(); [SerializeField] List> m_MetaDropdowns = new List>(); [SerializeField] List m_MetaDropdownIds = new List(); public CopyPasteGraph() { } public CopyPasteGraph(IEnumerable groups, IEnumerable nodes, IEnumerable edges, IEnumerable inputs, IEnumerable categories, IEnumerable metaProperties, IEnumerable metaKeywords, IEnumerable metaDropdowns, IEnumerable 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(); 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 GetNodes() { return m_Nodes.SelectValue().OfType(); } public DataValueEnumerable groups => m_Groups.SelectValue(); public DataValueEnumerable stickyNotes => m_StickyNotes.SelectValue(); public IEnumerable edges { get { return m_Edges; } } public RefValueEnumerable inputs { get { return m_Inputs.SelectValue(); } } public DataValueEnumerable categories { get { return m_Categories.SelectValue(); } } public DataValueEnumerable metaProperties { get { return m_MetaProperties.SelectValue(); } } public DataValueEnumerable metaKeywords { get { return m_MetaKeywords.SelectValue(); } } public DataValueEnumerable metaDropdowns { get { return m_MetaDropdowns.SelectValue(); } } public IEnumerable metaPropertyIds => m_MetaPropertyIds; public IEnumerable 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; } } } }