using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; using NodeID = System.UInt32; namespace UnityEditor.VFX.UI { class VFXPaste : VFXCopyPasteCommon { readonly List>> newContexts = new List>>(); readonly List newOperators = new List(); readonly List>> newParameterNodes = new List>>(); readonly List newBlackboardItems = new List(); private readonly List newCategories = new List(); Dictionary newControllers = new Dictionary(); int firstCopiedGroup = -1; int firstCopiedStickyNote = -1; static VFXPaste s_Instance = null; public static void UnserializeAndPasteElements(VFXViewController viewController, Vector2 center, string data, VFXView view = null, VFXGroupNodeController groupNode = null) { var serializableGraph = JsonUtility.FromJson(data); if (s_Instance == null) s_Instance = new VFXPaste(); s_Instance.DoPaste(viewController, center, serializableGraph, view, groupNode, null); } public static void Paste(VFXViewController viewController, Vector2 center, object data, VFXView view, VFXGroupNodeController groupNode, List nodesInTheSameOrder = null) { if (s_Instance == null) s_Instance = new VFXPaste(); s_Instance.DoPaste(viewController, center, data, view, groupNode, nodesInTheSameOrder); } public static void PasteBlocks(VFXViewController viewController, object data, VFXContext targetModelContext, int targetIndex, List blocksInTheSameOrder = null) { if (s_Instance == null) s_Instance = new VFXPaste(); s_Instance.PasteBlocks(viewController, (data as SerializableGraph).operators, targetModelContext, targetIndex, blocksInTheSameOrder); } public static void PasteStickyNotes(VFXViewController viewController, object data) { if (s_Instance == null) s_Instance = new VFXPaste(); s_Instance.PasteStickyNotes(viewController, data as SerializableGraph, Vector2.zero, viewController.graph.UIInfos); } static bool CanPasteSubgraph(VisualEffectSubgraph subgraph, string openedAssetPath) { var path = AssetDatabase.GetAssetPath(subgraph); if (path == openedAssetPath) { return false; } var resource = VisualEffectResource.GetResourceAtPath(path); var graph = resource.GetOrCreateGraph(); return graph.children .OfType() .All(x => CanPasteSubgraph(x.subgraph, openedAssetPath)); } static bool CanPasteNode(Node node, string openedAssetPath) { var subgraphType = typeof(VisualEffectSubgraph); return node.settings .Where(x => subgraphType.IsAssignableFrom(x.value.type)) .All(x => { var obj = x.value.Get(); //var path = AssetDatabase.GetAssetPath(obj); // Check if the copied node does not contains the destination graph to prevent recursion return CanPasteSubgraph(obj, openedAssetPath); } ); } public static bool CanPaste(VFXView view, object data) { var content = data?.ToString(); if (string.IsNullOrEmpty(content)) { return false; } try { var serializableGraph = JsonUtility.FromJson(content); if (view.controller.model.isSubgraph) { var path = AssetDatabase.GetAssetPath(view.controller.model.subgraph); if (!serializableGraph.operators.All(x => CanPasteNode(x, path))) { return false; } } if (serializableGraph.blocksOnly) { var selectedContexts = view.selection.OfType(); var selectedBlocks = view.selection.OfType(); return selectedBlocks.Any() || selectedContexts.Count() == 1; } return true; } catch { return false; } } void DoPaste(VFXViewController viewController, Vector2 center, object data, VFXView view, VFXGroupNodeController groupNode, List nodesInTheSameOrder) { SerializableGraph serializableGraph = (SerializableGraph)data; if (serializableGraph.blocksOnly) { if (view != null) { PasteVFXAttributes(viewController, serializableGraph); PasteBlocks(view, ref serializableGraph, nodesInTheSameOrder); } } else { PasteAll(viewController, center, ref serializableGraph, view, groupNode, nodesInTheSameOrder); } } static readonly GUIContent m_BlockPasteError = EditorGUIUtility.TextContent("To paste blocks, please select one target block or one target context."); void PasteBlocks(VFXView view, ref SerializableGraph serializableGraph, List nodesInTheSameOrder) { var selectedContexts = view.selection.OfType().ToArray(); var selectedBlocks = view.selection.OfType().ToArray(); VFXBlockUI targetBlock = null; VFXContextUI targetContext; if (selectedBlocks.Any()) { targetBlock = selectedBlocks.OrderByDescending(t => t.context.controller.model.GetIndex(t.controller.model)).First(); targetContext = targetBlock.context; } else if (selectedContexts.Length == 1) { targetContext = selectedContexts[0]; } else { Debug.LogWarning(m_BlockPasteError.text); return; } using (new VFXContextUI.GrowContext(targetContext)) { VFXContext targetModelContext = targetContext.controller.model; int targetIndex = -1; if (targetBlock != null) { targetIndex = targetModelContext.GetIndex(targetBlock.controller.model) + 1; } List blockControllers = nodesInTheSameOrder != null ? new List() : null; PasteBlocks(view.controller, serializableGraph.operators, targetModelContext, targetIndex, blockControllers); nodesInTheSameOrder?.AddRange(blockControllers); targetModelContext.Invalidate(VFXModel.InvalidationCause.kStructureChanged); } view.ClearSelection(); foreach (var uiBlock in targetContext.Query().OfType().Where(t => m_NodesInTheSameOrder.Any(u => u.model == t.controller.model)).ToList()) view.AddToSelection(uiBlock); } private int PasteBlocks(VFXViewController viewController, Node[] blocks, VFXContext targetModelContext, int targetIndex, List blocksInTheSameOrder = null) { newControllers.Clear(); m_NodesInTheSameOrder = new VFXNodeID[blocks.Length]; int cpt = 0; foreach (var block in blocks) { Node blk = block; VFXBlock newBlock = PasteAndInitializeNode(viewController, Vector2.zero, Rect.zero, ref blk); if (targetModelContext.AcceptChild(newBlock, targetIndex)) { m_NodesInTheSameOrder[cpt] = new VFXNodeID(newBlock, 0); targetModelContext.AddChild(newBlock, targetIndex, false); // only notify once after all blocks have been added targetIndex++; } ++cpt; } targetModelContext.Invalidate(VFXModel.InvalidationCause.kStructureChanged); var targetContextController = viewController.GetRootNodeController(targetModelContext, 0) as VFXContextController; targetContextController.ApplyChanges(); if (blocksInTheSameOrder != null) { blocksInTheSameOrder.Clear(); for (int i = 0; i < m_NodesInTheSameOrder.Length; ++i) { blocksInTheSameOrder.Add(m_NodesInTheSameOrder[i].model != null ? targetContextController.blockControllers.First(t => t.model == m_NodesInTheSameOrder[i].model as VFXBlock) : null); } } return targetIndex; } VFXNodeID[] m_NodesInTheSameOrder = null; void PasteAll(VFXViewController viewController, Vector2 center, ref SerializableGraph serializableGraph, VFXView view, VFXGroupNodeController groupNode, List nodesInTheSameOrder) { newControllers.Clear(); newContexts.Clear(); newOperators.Clear(); newParameterNodes.Clear(); newContextUIs.Clear(); newNodesUI.Clear(); newBlackboardItems.Clear(); newCategories.Clear(); if (serializableGraph.categories?.Length > 0) { newCategories.AddRange(serializableGraph.categories); } m_NodesInTheSameOrder = new VFXNodeID[serializableGraph.controllerCount]; // Paste custom attributes first so they are already available to nodes using them PasteVFXAttributes(viewController, serializableGraph); // Can't paste context within subgraph block/operator if (viewController.model.visualEffectObject is VisualEffectSubgraphOperator || viewController.model.visualEffectObject is VisualEffectSubgraphBlock) { if (serializableGraph.contexts != null) { var count = serializableGraph.contexts.Count(); if (count != 0) Debug.LogWarningFormat("{0} context{1} been skipped during the paste operation. Contexts aren't available in this kind of subgraph.", count, count > 1 ? "s have" : " has"); } } else { PasteContexts(viewController, center, serializableGraph); } PasteOperators(viewController, center, serializableGraph); PasteParameters(viewController, serializableGraph, center); PasteCategories(viewController); // Create controllers for all new nodes viewController.LightApplyChanges(); // Register all nodes for usage in groupNodes and edges RegisterContexts(viewController); RegisterOperators(viewController); RegisterParameterNodes(viewController, serializableGraph); VFXUI ui = viewController.graph.UIInfos; firstCopiedGroup = -1; firstCopiedStickyNote = ui.stickyNoteInfos != null ? ui.stickyNoteInfos.Length : 0; //Paste Everything else PasteGroupNodes(serializableGraph, center, ui); PasteStickyNotes(viewController, serializableGraph, center, ui); PasteDatas(viewController, serializableGraph); // TODO Data settings should be pasted at context creation. This can lead to issues as blocks are added before data is initialized PasteDataEdges(serializableGraph); PasteFlowEdges(serializableGraph); // Create all ui based on model viewController.LightApplyChanges(); if (nodesInTheSameOrder != null) { nodesInTheSameOrder.Clear(); nodesInTheSameOrder.AddRange(m_NodesInTheSameOrder.Select(t => t.model == null ? null : viewController.GetNodeController(t.model, t.id))); } if (view != null) { SelectCopiedElements(view, groupNode); } } void PasteDataEdges(SerializableGraph serializableGraph) { if (serializableGraph.dataEdges != null) { foreach (var dataEdge in serializableGraph.dataEdges) { if (dataEdge.input.targetIndex == InvalidID || dataEdge.output.targetIndex == InvalidID) continue; if (newControllers.TryGetValue(dataEdge.input.targetIndex, out var inputController) && inputController.model is IVFXSlotContainer inputModel && newControllers.TryGetValue(dataEdge.output.targetIndex, out var outputController) && outputController.model is IVFXSlotContainer outputModel) { var outputSlot = FetchSlot(outputModel, dataEdge.output.slotPath, false); var inputSlot = FetchSlot(inputModel, dataEdge.input.slotPath, true); inputSlot?.Link(outputSlot); if (outputController is VFXParameterNodeController parameterNodeController) { parameterNodeController.infos.linkedSlots.Add(new VFXParameter.NodeLinkedSlot { inputSlot = inputSlot, outputSlot = outputSlot }); } } } } } void PasteSubOutputs(VFXAbstractRenderedOutput output, ref Context src) { if (src.subOutputs == null) return; var newSubOutputs = new List(src.subOutputs.Length); for (int i = 0; i < src.subOutputs.Length; ++i) { Type type = (Type)src.subOutputs[i].type; if (type != null) { newSubOutputs.Add((VFXSRPSubOutput)ScriptableObject.CreateInstance(type)); PasteModelSettings(newSubOutputs.Last(), src.subOutputs[i].settings, type); } } output.InitSubOutputs(newSubOutputs); } VFXContext PasteContext(VFXViewController controller, Vector2 center, Rect bounds, ref Context context) { VFXContext newContext = PasteAndInitializeNode(controller, center, bounds, ref context.node); if (newContext == null) { newContexts.Add(new KeyValuePair>(null, null)); return null; } newContext.label = context.label; VFXSystemNames.SetSystemName(newContext, context.systemName); if (newContext is VFXAbstractRenderedOutput) PasteSubOutputs((VFXAbstractRenderedOutput)newContext, ref context); List blocks = new List(); foreach (var block in context.blocks) { var blk = block; VFXBlock newBlock = PasteAndInitializeNode(controller, center, bounds, ref blk); blocks.Add(newBlock); if (newBlock != null) newContext.AddChild(newBlock); } newContexts.Add(new KeyValuePair>(newContext, blocks)); return newContext; } T PasteAndInitializeNode(VFXViewController controller, Vector2 center, Rect bounds, ref Node node) where T : VFXModel { Type type = node.type; if (type == null) return null; var newNode = ScriptableObject.CreateInstance(type) as T; if (newNode == null) return null; var ope = node; if (!(newNode is VFXBlock)) { controller.graph.AddChild(newNode); m_NodesInTheSameOrder[node.indexInClipboard] = new VFXNodeID(newNode, 0); } PasteNode(newNode, center, bounds, ref ope); return newNode; } void PasteModelSettings(VFXModel model, Property[] settings, Type type) { var fields = GetFields(type); for (int i = 0; i < settings.Length; ++i) { string name = settings[i].name; var field = fields.Find(t => t.Name == name); try { field.SetValue(model, settings[i].value.Get()); } catch { } // Don't break paste operation if a field value cannot be assigned (see UUM-46548) } } void PasteNode(VFXModel model, Vector2 center, Rect bounds, ref Node node) { var offset = node.position - bounds.min; model.position = center + offset; PasteModelSettings(model, node.settings, model.GetType()); model.Invalidate(VFXModel.InvalidationCause.kSettingChanged); var slotContainer = model as IVFXSlotContainer; if (slotContainer.activationSlot) slotContainer.activationSlot.value = node.activationSlotValue; var inputSlots = slotContainer.inputSlots; for (int i = 0; i < node.inputSlots.Length && i < inputSlots.Count; ++i) { if (inputSlots[i].name == node.inputSlots[i].name) { inputSlots[i].value = node.inputSlots[i].value.Get(); if (inputSlots[i].spaceable) inputSlots[i].space = node.inputSlots[i].space; } } if ((node.flags & Node.Flags.Collapsed) == Node.Flags.Collapsed) model.collapsed = true; if ((node.flags & Node.Flags.SuperCollapsed) == Node.Flags.SuperCollapsed) model.superCollapsed = true; foreach (var slot in AllSlots(slotContainer.inputSlots)) { slot.collapsed = !node.expandedInputs.Contains(slot.path); } foreach (var slot in AllSlots(slotContainer.outputSlots)) { slot.collapsed = !node.expandedOutputs.Contains(slot.path); } } HashSet newNodesUI = new HashSet(); HashSet newContextUIs = new HashSet(); private void SelectCopiedElements(VFXView view, VFXGroupNodeController groupNode) { view.ClearSelection(); var elements = view.graphElements.ToList(); newNodesUI.Clear(); newContextUIs.Clear(); FindContextUIsAndSelect(view, elements); FindOperatorsUIsAndSelect(view, elements); FindParameterUIsAndSelect(view, elements); SelectEdges(view, elements); //Select all groups that are new SelectGroupNodes(view, elements); // Add all copied element that are not in a copied groupNode to the potentially selected groupnode if (groupNode != null) { foreach (var newSlotContainerUI in newNodesUI) { groupNode.AddNode(newSlotContainerUI.controller); } } SelectStickyNotes(view, elements); SelectBlackboardElements(view); } private void SelectBlackboardElements(VFXView view) { foreach (var item in newBlackboardItems) { view.blackboard.AddPendingSelection(item); } } private void SelectGroupNodes(VFXView view, List elements) { if (firstCopiedGroup >= 0) { foreach (var gn in elements.OfType()) { if (gn.controller.index >= firstCopiedGroup) { view.AddToSelection(gn); foreach (var node in gn.containedElements.OfType()) { newNodesUI.Remove(node); } } } } } private void SelectStickyNotes(VFXView view, List elements) { //Select all groups that are new if (firstCopiedStickyNote >= 0) { foreach (var gn in elements.OfType()) { if (gn.controller.index >= firstCopiedStickyNote) { view.AddToSelection(gn); } } } } private void SelectEdges(VFXView view, List elements) { // Simply selected all data edge with the context or slot container, they can be no other than the copied ones foreach (var dataEdge in elements.OfType()) { if (newNodesUI.Contains(dataEdge.input.GetFirstAncestorOfType())) { view.AddToSelection(dataEdge); } } // Simply selected all data edge with the context or slot container, they can be no other than the copied ones foreach (var flowEdge in elements.OfType()) { if (newContextUIs.Contains(flowEdge.input.GetFirstAncestorOfType())) { view.AddToSelection(flowEdge); } } } private void FindParameterUIsAndSelect(VFXView view, List elements) { foreach (var param in newControllers.Values.OfType()) { foreach (var parameterUI in elements.OfType().Where(t => t.controller == param)) { newNodesUI.Add(parameterUI); view.AddToSelection(parameterUI); } } } private void FindOperatorsUIsAndSelect(VFXView view, List elements) { foreach (var slotContainer in newControllers.Values.OfType()) { VFXOperatorUI slotContainerUI = elements.OfType().FirstOrDefault(t => t.controller == slotContainer); if (slotContainerUI != null) { newNodesUI.Add(slotContainerUI); view.AddToSelection(slotContainerUI); } } } private void FindContextUIsAndSelect(VFXView view, List elements) { foreach (var slotContainer in newContexts.Select(t => t.Key).OfType()) { VFXContextUI contextUI = elements.OfType().FirstOrDefault(t => t.controller.model == slotContainer); if (contextUI != null) { newNodesUI.Add(contextUI); foreach (var block in contextUI.GetAllBlocks().Cast()) { newNodesUI.Add(block); } newContextUIs.Add(contextUI); view.AddToSelection(contextUI); } } } private void PasteGroupNodes(SerializableGraph serializableGraph, Vector2 center, VFXUI ui) { if (serializableGraph.groupNodes != null && serializableGraph.groupNodes.Length > 0) { if (ui.groupInfos == null) { ui.groupInfos = new VFXUI.GroupInfo[0]; } firstCopiedGroup = ui.groupInfos.Length; List newGroupInfos = new List(); foreach (var groupInfos in serializableGraph.groupNodes) { var newGroupInfo = new VFXUI.GroupInfo(); var offset = groupInfos.infos.position.min - serializableGraph.bounds.min; newGroupInfo.position = new Rect(center + offset, groupInfos.infos.position.size); newGroupInfo.title = groupInfos.infos.title; newGroupInfos.Add(newGroupInfo); newGroupInfo.contents = groupInfos.contents?.Take(groupInfos.contents.Length - groupInfos.stickNodeCount).Select(t => { VFXNodeController node = null; newControllers.TryGetValue(t, out node); return node; }).Where(t => t != null).Select(node => new VFXNodeID(node.model, node.id)) .Concat(groupInfos.contents.Skip(groupInfos.contents.Length - groupInfos.stickNodeCount).Select(t => new VFXNodeID((int)t + firstCopiedStickyNote))) .ToArray(); } ui.groupInfos = ui.groupInfos.Concat(newGroupInfos).ToArray(); } } private void RegisterParameterNodes(VFXViewController viewController, SerializableGraph serializableGraph) { for (int i = 0; i < newParameterNodes.Count; ++i) { var parameterController = viewController.GetParameterController(newParameterNodes[i].Key); parameterController.ApplyChanges(); if (parameterController.spaceableAndMasterOfSpace) { parameterController.space = serializableGraph.parameterNodes[i].space; } if (newParameterNodes[i].Value.Count > 0) { for (int j = 0; j < newParameterNodes[i].Value.Count; j++) { var nodeController = viewController.GetNodeController(newParameterNodes[i].Key, newParameterNodes[i].Value[j]) as VFXParameterNodeController; newControllers[GetParameterNodeID((uint)i, (uint)j)] = nodeController; } } } } private void RegisterOperators(VFXViewController viewController) { for (int i = 0; i < newOperators.Count; ++i) { newControllers[OperatorFlag | (uint)i] = viewController.GetNodeController(newOperators[i], 0); } } private void RegisterContexts(VFXViewController viewController) { for (int i = 0; i < newContexts.Count; ++i) { if (newContexts[i].Key != null) { VFXContextController controller = viewController.GetNodeController(newContexts[i].Key, 0) as VFXContextController; newControllers[ContextFlag | (uint)i] = controller; for (int j = 0; j < newContexts[i].Value.Count; ++j) { var block = newContexts[i].Value[j]; if (block != null) { VFXBlockController blockController = controller.blockControllers.FirstOrDefault(t => t.model == block); if (blockController != null) newControllers[GetBlockID((uint)i, (uint)j)] = blockController; } } } } } private void PasteStickyNotes(VFXViewController vfxViewController, SerializableGraph serializableGraph, Vector2 center, VFXUI ui) { if (serializableGraph.stickyNotes != null && serializableGraph.stickyNotes.Length > 0) { if (ui.stickyNoteInfos == null) { ui.stickyNoteInfos = Array.Empty(); } var bounds = serializableGraph.bounds; ui.stickyNoteInfos = ui.stickyNoteInfos.Concat(serializableGraph.stickyNotes.Select(t => { var offset = t.position.position - bounds.min; return new VFXUI.StickyNoteInfo(t) { position = new Rect(center + offset, t.position.size) }; })).ToArray(); vfxViewController?.graph?.Invalidate(VFXModel.InvalidationCause.kUIChanged); } } private void PasteDatas(VFXViewController vfxViewController, SerializableGraph serializableGraph) { for (int i = 0; i < newContexts.Count; ++i) { VFXNodeController nodeController = null; newControllers.TryGetValue(ContextFlag | (uint)i, out nodeController); var contextController = nodeController as VFXContextController; if (contextController != null) { if ((contextController.flowInputAnchors.Count() == 0 || contextController.flowInputAnchors.First().connections.Count() == 0 || contextController.flowInputAnchors.First().connections.First().output.context.model.GetData() == null) && serializableGraph.contexts[i].dataIndex >= 0) { var data = serializableGraph.datas[serializableGraph.contexts[i].dataIndex]; VFXData targetData = contextController.model.GetData(); if (targetData != null) { PasteModelSettings(targetData, data.settings, targetData.GetType()); targetData.Invalidate(VFXModel.InvalidationCause.kSettingChanged); } } } } } private void PasteFlowEdges(SerializableGraph serializableGraph) { if (serializableGraph.flowEdges != null) { foreach (var flowEdge in serializableGraph.flowEdges) { VFXContext inputContext = newControllers.ContainsKey(flowEdge.input.contextIndex) ? (newControllers[flowEdge.input.contextIndex] as VFXContextController).model : null; VFXContext outputContext = newControllers.ContainsKey(flowEdge.output.contextIndex) ? (newControllers[flowEdge.output.contextIndex] as VFXContextController).model : null; if (inputContext != null && outputContext != null) inputContext.LinkFrom(outputContext, flowEdge.output.flowIndex, flowEdge.input.flowIndex); } } } private void PasteContexts(VFXViewController viewController, Vector2 center, SerializableGraph serializableGraph) { if (serializableGraph.contexts != null) { newContexts.Clear(); foreach (var context in serializableGraph.contexts) { var ctx = context; PasteContext(viewController, center, serializableGraph.bounds, ref ctx); } } } private void PasteVFXAttributes(VFXViewController viewController, SerializableGraph serializableGraph) { if (serializableGraph.attributes != null) { foreach (var attribute in serializableGraph.attributes) { VFXAttribute pastedAttribute; // Check that the attribute still exists because it can have been deleted between copy and paste operations // Also do not duplicate when the custom attribute comes from a block (we might have multiple blocks referring to the same custom attribute for instance) if (attribute.canDuplicate && viewController.graph.attributesManager.Exist(attribute.name)) { pastedAttribute = viewController.graph.DuplicateCustomAttribute(attribute.name); } else { viewController.graph.TryAddCustomAttribute(attribute.name, attribute.type, attribute.description, false, out pastedAttribute); } newBlackboardItems.Add(pastedAttribute.name); } viewController.graph.SetCustomAttributeDirty(); } } private void PasteOperators(VFXViewController viewController, Vector2 center, SerializableGraph serializableGraph) { newOperators.Clear(); if (serializableGraph.operators != null) { foreach (var operat in serializableGraph.operators) { Node ope = operat; VFXOperator newOperator = PasteAndInitializeNode(viewController, center, serializableGraph.bounds, ref ope); newOperators.Add(newOperator); // add even they are null so that the index is correct } } } private void PasteParameters(VFXViewController viewController, SerializableGraph serializableGraph, Vector2 center) { newParameterNodes.Clear(); if (serializableGraph.parameterNodes != null) { foreach (var parameter in serializableGraph.parameterNodes) { // if we have a parameter with the same name use it else create it with the copied data VFXParameter p = viewController.graph.children.OfType().FirstOrDefault(t => t.GetInstanceID() == parameter.originalInstanceID); if (p == null) { Type type = parameter.value.type; VFXModelDescriptorParameters desc = VFXLibrary.GetParameters().FirstOrDefault(t => t.variant.modelType == type); if (desc != null) { p = viewController.AddVFXParameter(Vector2.zero, desc.variant); serializableGraph.parameters = serializableGraph.parameters.Where(x => x.name != parameter.name).ToArray(); CopyParameter(parameter, p, viewController.model.visualEffectObject is VisualEffectSubgraphOperator && parameter.isOutput); } } if (p == null) { this.newParameterNodes.Add(new KeyValuePair>(null, null)); continue; } var newParameterNodes = new List(); foreach (var node in parameter.nodes) { var offset = node.position - serializableGraph.bounds.min; int nodeIndex = p.AddNode(center + offset); var nodeModel = p.nodes.LastOrDefault(t => t.id == nodeIndex); nodeModel.expanded = !node.collapsed; nodeModel.expandedSlots = AllSlots(p.outputSlots).Where(t => node.expandedOutput.Contains(t.path)).ToList(); m_NodesInTheSameOrder[node.indexInClipboard] = new VFXNodeID(p, nodeModel.id); newParameterNodes.Add(nodeIndex); } this.newParameterNodes.Add(new KeyValuePair>(p, newParameterNodes)); } } var existingCategories = viewController.graph.UIInfos.categories?.Select(x => x.name).ToHashSet() ?? new HashSet(); var categoryMapping = new Dictionary(); if (serializableGraph.categories != null) { foreach (var category in serializableGraph.categories) { var newCategoryName = VFXParameterController.MakeNameUnique(category, existingCategories); existingCategories.Add(newCategoryName); categoryMapping[category] = newCategoryName; } } if (serializableGraph.parameters != null) { foreach (var parameter in serializableGraph.parameters) { var newVfxParameter = ScriptableObject.CreateInstance(); newVfxParameter.Init(parameter.value.type); CopyParameter(parameter, newVfxParameter, viewController.model.visualEffectObject is VisualEffectSubgraphOperator && parameter.isOutput); if (categoryMapping.TryGetValue(newVfxParameter.category, out var category)) { newBlackboardItems.Add(category); newVfxParameter.category = category; newCategories.Remove(parameter.category); } viewController.AddVFXModel(Vector2.zero, newVfxParameter); var groupChanged = false; viewController.SyncControllerFromModel(ref groupChanged); newBlackboardItems.Add(newVfxParameter.exposedName); } } } private void PasteCategories(VFXViewController viewController) { var existingCategories = viewController.graph.UIInfos.categories.Select(x => x.name).ToHashSet(); foreach (var category in newCategories) { var newCategoryName = VFXParameterController.MakeNameUnique(category, existingCategories); existingCategories.Add(newCategoryName); viewController.graph.UIInfos.categories ??= new List(); viewController.graph.UIInfos.categories.Add(new VFXUI.CategoryInfo { name = newCategoryName }); newBlackboardItems.Add(newCategoryName); } if (newCategories.Count > 0) { viewController.graph.Invalidate(VFXModel.InvalidationCause.kUIChanged); } } private void CopyParameter(Parameter parameter, VFXParameter vfxParameter, bool isOutput) { vfxParameter.value = parameter.value.Get(); vfxParameter.valueFilter = parameter.valueFilter; vfxParameter.category = parameter.category; if (parameter.valueFilter == VFXValueFilter.Range) { vfxParameter.min = parameter.min.Get(); vfxParameter.max = parameter.max.Get(); } else if (parameter.valueFilter == VFXValueFilter.Enum) { vfxParameter.enumValues = parameter.enumValue.ToList(); } vfxParameter.SetSettingValue("m_Exposed", parameter.exposed); vfxParameter.SetSettingValue("m_ExposedName", parameter.name); // the controller will take care or name unicity later vfxParameter.isOutput = isOutput; vfxParameter.tooltip = parameter.tooltip; vfxParameter.collapsed = parameter.collapsed; } static VFXSlot FetchSlot(IVFXSlotContainer container, int[] slotPath, bool input) { int containerSlotIndex = slotPath[slotPath.Length - 1]; VFXSlot slot = null; if (containerSlotIndex == -2) // activation slot { slot = container.activationSlot; } else if (input) { if (container.GetNbInputSlots() > containerSlotIndex) slot = container.GetInputSlot(slotPath[slotPath.Length - 1]); } else { if (container.GetNbOutputSlots() > containerSlotIndex) slot = container.GetOutputSlot(slotPath[slotPath.Length - 1]); } if (slot == null) return null; for (int i = slotPath.Length - 2; i >= 0; --i) { if (slot.GetNbChildren() > slotPath[i]) slot = slot[slotPath[i]]; else return null; } return slot; } } }