/// <summary> /// Make sure our list of PortViews and editables sync up with our NodePorts /// </summary> protected void UpdatePorts() { foreach (var port in target.ports) { if (port.isInput) { AddInputPort(port); } else { AddOutputPort(port); } } var reflectionData = NodeReflection.GetNodeType(target.GetType()); if (reflectionData != null) { foreach (var editable in reflectionData.editables) { AddEditableField(m_SerializedNode.FindPropertyRelative(editable.fieldName)); } } // Toggle visibility of the extension container RefreshExpandedState(); // Update state classes EnableInClassList("hasInputs", inputs.Count > 0); EnableInClassList("hasOutputs", outputs.Count > 0); }
/// <summary> /// Create a new node from reflection data and insert into the Graph. /// </summary> internal void AddNodeFromSearch(Node node, Vector2 screenPosition, PortView connectedPort = null, bool registerUndo = true) { // Calculate where to place this node on the graph var windowRoot = EditorWindow.rootVisualElement; var windowMousePosition = EditorWindow.rootVisualElement.ChangeCoordinatesTo( windowRoot.parent, screenPosition - EditorWindow.position.position ); var graphMousePosition = contentViewContainer.WorldToLocal(windowMousePosition); // Track undo and add to the graph if (registerUndo) { Undo.RegisterCompleteObjectUndo(Graph, $"Add Node {node.Name}"); } node.Position = graphMousePosition; Graph.AddNode(node); serializedGraph.Update(); EditorUtility.SetDirty(Graph); // Add a node to the visual graph var editorType = NodeReflection.GetNodeEditorType(node.GetType()); var element = Activator.CreateInstance(editorType) as NodeView; element.Initialize(node, this, edgeConnectorListener); AddElement(element); // If there was a provided existing port to connect to, find the best // candidate port on the new node and connect. if (connectedPort != null) { var edge = new GraphViewEdge(); if (connectedPort.direction == Direction.Input) { edge.input = connectedPort; edge.output = element.GetCompatibleOutputPort(connectedPort); } else { edge.output = connectedPort; edge.input = element.GetCompatibleInputPort(connectedPort); } AddEdge(edge, false); } Dirty(element); }
public List <SearchTreeEntry> CreateSearchTree(SearchWindowContext context) { var tree = new List <SearchTreeEntry>(); // First item is the title of the window tree.Add(new SearchTreeGroupEntry(new GUIContent("Add Node"), 0)); // TODO: Hooks for custom top level pieces (Comments, new variables, etc) // Construct a tree of available nodes by module path var nodes = NodeReflection.GetNodeTypes(); var groups = new SearchGroup(1); foreach (var node in nodes.Values) { var path = node.path; // Skip the node if it the module isn't whitelisted if (!IsInSupportedModule(path)) { continue; } // If we're coming from a port, make sure to only add nodes that accept // an input (or output) that's compatible. if (sourcePort == null || IsCompatibleWithSourcePort(node)) { var group = groups; if (path != null) { for (int i = 0; i < path.Length; i++) { if (!group.subgroups.ContainsKey(path[i])) { group.subgroups.Add(path[i], new SearchGroup(group.depth + 1)); } group = group.subgroups[path[i]]; } } group.nodes.Add(node); } } groups.AddToTree(tree); return(tree); }
public void Load(Graph graph) { Graph = graph; serializedGraph = new SerializedObject(Graph); title.text = graph.Title; SetupZoom(graph.ZoomMinScale, graph.ZoomMaxScale); AddNodeViews(graph.Nodes); AddCommentViews(graph.Comments); // Reset the search to a new set of tags and providers searchWindow.ClearSearchProviders(); searchWindow.ClearTags(); foreach (var provider in NodeReflection.SearchProviders) { if (provider.IsSupported(graph)) { searchWindow.AddSearchProvider(provider); } } // TODO: Move into reflection var attrs = graph.GetType().GetCustomAttributes(true); foreach (var attr in attrs) { //Add Tags for search provider if (attr is IncludeTagsAttribute include) { searchWindow.IncludeTags.AddRange(include.Tags); } //Add Required nodes from GraphAttributes if (attr is RequireNodeAttribute required) { Node node = graph.GetNode(required.type); if (node == null) { node = NodeReflection.Instantiate(required.type); node.Graph = graph; node.Name = required.nodeName; node.Position = required.position; AddNodeFromSearch(node, node.Position, null, false); } } } }
public IEnumerable <SearchResult> GetSearchResults(SearchFilter filter) { foreach (var entry in NodeReflection.GetNodeTypes()) { var node = entry.Value; if ( IsCompatible(filter.SourcePort, node) && IsInSupportedTags(filter.IncludeTags, node.Tags) ) { yield return(new SearchResult { Name = node.Name, Path = node.Path, UserData = node, }); } } }
public NodeReflectionData(Type type, NodeAttribute nodeAttr) { Type = type; Name = nodeAttr.Name ?? ObjectNames.NicifyVariableName(type.Name); Path = nodeAttr.Path?.Split('/'); Help = nodeAttr.Help; Deletable = nodeAttr.Deletable; Moveable = nodeAttr.Moveable; EditorType = NodeReflection.GetNodeEditorType(type); contextMethods = new Dictionary <ContextMenu, MethodInfo>(); var attrs = type.GetCustomAttributes(true); foreach (var attr in attrs) { if (attr is TagsAttribute tagAttr) { // Load any tags associated with the node Tags.AddRange(tagAttr.Tags); } else if (attr is OutputAttribute output) { // Load any Outputs defined at the class level Ports.Add(new PortReflectionData() { Name = output.Name, Type = output.Type, Direction = PortDirection.Output, Capacity = output.Multiple ? PortCapacity.Multiple : PortCapacity.Single, HasControlElement = false }); } } // Load additional data from class fields AddFieldsFromClass(type); SetScriptNodeType(); SetScriptNodeViewType(); LoadContextMethods(); }
/// <summary> /// Append views for a set of nodes /// </summary> void AddNodeViews(List <AbstractNode> nodes, bool selectOnceAdded = false, bool centerOnMouse = false) { var serializedNodesArr = m_SerializedGraph.FindProperty("nodes"); // Add views of each node from the graph Dictionary <AbstractNode, NodeView> nodeMap = new Dictionary <AbstractNode, NodeView>(); // TODO: Could just be a list with index checking. for (int i = 0; i < nodes.Count; i++) { var node = nodes[i]; var graphIdx = m_Graph.nodes.IndexOf(node); if (graphIdx < 0) { Debug.LogError("Cannot add NodeView: Node is not indexed on the graph"); } else { var serializedNode = serializedNodesArr.GetArrayElementAtIndex(graphIdx); var editorType = NodeReflection.GetNodeEditorType(node.GetType()); var element = Activator.CreateInstance(editorType) as NodeView; element.Initialize(node, serializedNode, m_EdgeListener); AddElement(element); nodeMap.Add(node, element); Dirty(element); if (selectOnceAdded) { AddToSelection(element); } } } if (centerOnMouse) { Rect bounds = GetBounds(nodeMap.Values); Vector2 worldPosition = contentViewContainer.WorldToLocal(m_LastMousePosition); Vector2 delta = worldPosition - bounds.center; foreach (var node in nodeMap) { node.Value.SetPosition(new Rect(node.Key.graphPosition + delta, Vector2.one)); } } // Sync edges on the graph with our graph's connections // TODO: Deal with trash connections from bad imports // and try to just refactor this whole thing tbh foreach (var node in nodeMap) { foreach (var port in node.Key.ports) { if (!port.isInput) { continue; } var connections = port.ConnectedPorts; foreach (var conn in connections) { if (conn.node == null) { Debug.LogError( $"Could not connect `{node.Value.title}:{port.name}`: " + $"Connected node no longer exists." ); continue; } // Only add if the linked node is in the collection if (nodeMap.ContainsKey(conn.node)) { var inPort = node.Value.GetInputPort(port.name); var outPort = nodeMap[conn.node].GetOutputPort(conn.name); if (inPort == null) { Debug.LogError( $"Could not connect `{node.Value.title}:{port.name}` -> `{conn.node.name}:{conn.name}`. " + $"Input port `{port.name}` no longer exists." ); } else if (outPort == null) { Debug.LogError( $"Could not connect `{conn.node.name}:{conn.name}` to `{node.Value.name}:{port.name}`. " + $"Output port `{conn.name}` no longer exists." ); } else { var edge = inPort.ConnectTo(outPort); AddElement(edge); } } } } } }
/// <summary> /// Create a new node from reflection data and insert into the Graph. /// </summary> internal void AddNodeFromReflectionData( NodeReflectionData data, Vector2 screenPosition, PortView connectedPort = null ) { // Calculate where to place this node on the graph var windowRoot = m_EditorWindow.rootVisualElement; var windowMousePosition = m_EditorWindow.rootVisualElement.ChangeCoordinatesTo( windowRoot.parent, screenPosition - m_EditorWindow.position.position ); var graphMousePosition = contentViewContainer.WorldToLocal(windowMousePosition); // Create a new node instance and set initial data (ports, etc) Undo.RegisterCompleteObjectUndo(m_Graph, $"Add Node {data.name}"); Debug.Log($"+node {data.name}"); var node = data.CreateInstance(); node.graphPosition = graphMousePosition; m_Graph.AddNode(node); m_SerializedGraph.Update(); EditorUtility.SetDirty(m_Graph); var serializedNodesArr = m_SerializedGraph.FindProperty("nodes"); var nodeIdx = m_Graph.nodes.IndexOf(node); var serializedNode = serializedNodesArr.GetArrayElementAtIndex(nodeIdx); // Add a node to the visual graph var editorType = NodeReflection.GetNodeEditorType(data.type); var element = Activator.CreateInstance(editorType) as NodeView; element.Initialize(node, serializedNode, m_EdgeListener); AddElement(element); // If there was a provided existing port to connect to, find the best // candidate port on the new node and connect. if (connectedPort != null) { var edge = new Edge(); if (connectedPort.direction == Direction.Input) { edge.input = connectedPort; edge.output = element.GetCompatibleOutputPort(connectedPort); } else { edge.output = connectedPort; edge.input = element.GetCompatibleInputPort(connectedPort); } AddEdge(edge, false); } Dirty(element); }
/// <summary> /// Deserialize a string back into a CopyPasteGraph. /// /// If <c>includeTags</c> are empty, no filtering will be done. Otherwise, /// only nodes with an intersection to one or more tags will be kept. /// </summary> public static CopyPasteGraph Deserialize(string data, IEnumerable <string> includeTags) { var graph = CreateInstance <CopyPasteGraph>(); JsonUtility.FromJsonOverwrite(data, graph); // Remove nodes that aren't on the allow list for tags var allowedAllNodes = true; if (includeTags.Count() > 0) { graph.Nodes = graph.Nodes.FindAll((node) => { var reflectedNode = NodeReflection.GetNodeType(node.GetType()); var allowed = includeTags.Intersect(reflectedNode.Tags).Count() > 0; allowedAllNodes = allowedAllNodes && allowed; return(allowed); }); } // If we're excluding any from the paste content, notify the user. if (!allowedAllNodes) { Debug.LogWarning("Could not paste one or more nodes - not allowed by the target graph"); } // Generate new unique IDs for each node in the list // in case we're copy+pasting back onto the same graph var idMap = new Dictionary <string, string>(); foreach (var node in graph.Nodes) { var newId = Guid.NewGuid().ToString(); idMap[node.ID] = newId; node.ID = newId; } // Remap connections to new node IDs and drop any connections // that were to nodes outside of the subset of pasted nodes foreach (var node in graph.Nodes) { foreach (var port in node.Ports.Values) { var edges = new List <Connection>(port.Connections); port.Connections.Clear(); // Only re-add connections that are in the new pasted subset foreach (var edge in edges) { if (idMap.ContainsKey(edge.NodeID)) { port.Connections.Add(new Connection { NodeID = idMap[edge.NodeID], PortName = edge.PortName }); } } } } return(graph); }