public void CanRefactorCustomNodeName() { var nodeName = "TheNoodle"; var catName = "TheCat"; var descr = "TheCat"; var path = @"C:\turtle\graphics.dyn"; var guid1 = Guid.NewGuid(); var dummyInfo1 = new CustomNodeInfo(guid1, nodeName, catName, descr, path); _search.Add(dummyInfo1); Assert.AreEqual(1, _search.SearchDictionary.NumElements); var newNodeName = "TheTurtle"; var newInfo = new CustomNodeInfo(guid1, newNodeName, catName, descr, path); _search.Refactor(newInfo); Assert.AreEqual(1, _search.SearchDictionary.NumElements); // search for new name _search.SearchAndUpdateResultsSync(newNodeName); // results are correct Assert.AreEqual(1, _search.SearchResults.Count); var res1 = _search.SearchResults[0]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res1); var node1 = res1 as CustomNodeSearchElement; Assert.AreEqual(node1.Guid, guid1); // search for old name _search.SearchAndUpdateResultsSync(nodeName); // results are correct Assert.AreEqual(0, _search.SearchResults.Count); }
public void CanRefactorCustomNodeDescription() { var nodeName = "TheNoodle"; var catName = "TheCat"; var descr = "Cool description, man"; var path = @"C:\turtle\graphics.dyn"; var guid1 = Guid.NewGuid(); var dummyInfo1 = new CustomNodeInfo(guid1, nodeName, catName, descr, path); _search.Add(dummyInfo1); Assert.AreEqual(1, _search.SearchDictionary.NumElements); // search for name _search.SearchAndUpdateResultsSync(nodeName); // results are correct Assert.AreEqual(1, _search.SearchResults.Count); var res1 = _search.SearchResults[0]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res1); var node1 = res1 as CustomNodeSearchElement; Assert.AreEqual(node1.Guid, guid1); Assert.AreEqual(node1.Description, descr); // refactor description const string newDescription = "Tickle me elmo"; var newInfo = new CustomNodeInfo(guid1, nodeName, catName, newDescription, path); _search.Refactor(newInfo); // num elements is unchanged Assert.AreEqual(1, _search.SearchDictionary.NumElements); // search for name _search.SearchAndUpdateResultsSync(nodeName); // description is updated Assert.AreEqual(1, _search.SearchResults.Count); var res2 = _search.SearchResults[0]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res2); var node2 = res2 as CustomNodeSearchElement; Assert.AreEqual( guid1, node2.Guid ); Assert.AreEqual( newDescription, node2.Description); }
/// <summary> /// Stores the path and function definition without initializing a node. Overwrites /// the existing NodeInfo if necessary /// </summary> private void SetNodeInfo(CustomNodeInfo newInfo) { var guids = NodeInfos.Where(x => { return !string.IsNullOrEmpty(x.Value.Path) && string.Compare(x.Value.Path, newInfo.Path, StringComparison.OrdinalIgnoreCase) == 0; }).Select(x => x.Key).ToList(); foreach (var guid in guids) { NodeInfos.Remove(guid); } NodeInfos[newInfo.FunctionId] = newInfo; OnInfoUpdated(newInfo); }
/// <summary> /// Creates a new Custom Node Instance. /// </summary> /// <param name="id">Identifier referring to a custom node definition.</param> /// <param name="nickname"> /// Nickname for the custom node to be instantiated, used for error recovery if /// the given id could not be found. /// </param> /// <param name="isTestMode"> /// Flag specifying whether or not this should operate in "test mode". /// </param> public Function CreateCustomNodeInstance( Guid id, string nickname = null, bool isTestMode = false) { CustomNodeWorkspaceModel workspace; CustomNodeDefinition def; CustomNodeInfo info; // Try to get the definition, initializing the custom node if necessary if (TryGetFunctionDefinition(id, isTestMode, out def)) { // Got the definition, proceed as planned. info = NodeInfos[id]; } else { // Couldn't get the workspace with the given ID, try a nickname lookup instead. if (nickname != null && TryGetNodeInfo(nickname, out info)) return CreateCustomNodeInstance(info.FunctionId, nickname, isTestMode); // Couldn't find the workspace at all, prepare for a late initialization. Log( Properties.Resources.UnableToCreateCustomNodeID + id + "\"", WarningLevel.Moderate); info = new CustomNodeInfo(id, nickname ?? "", "", "", ""); } if (def == null) { def = CustomNodeDefinition.MakeProxy(id, info.Name); } var node = new Function(def, info.Name, info.Description, info.Category); if (loadedWorkspaceModels.TryGetValue(id, out workspace)) RegisterCustomNodeInstanceForUpdates(node, workspace); else RegisterCustomNodeInstanceForLateInitialization(node, id, nickname, isTestMode); return node; }
/// <summary> /// Stores the path and function definition without initializing a node. Overwrites /// the existing NodeInfo if necessary /// </summary> /// <param name="guid">The unique id for the node.</param> /// <param name="path">The path for the node.</param> public void SetNodeInfo(CustomNodeInfo newInfo) { var nodeInfo = GetNodeInfo(newInfo.Guid); if (nodeInfo == null) { NodeInfos.Add(newInfo.Guid, newInfo); } else { NodeInfos[newInfo.Guid] = newInfo; } }
public void CustomNodeSaveAsAddsNewCustomNodeToSearchAndItCanBeRefactoredWhilePreservingOriginalFromExistingDyf2() { // open custom node // SaveAs // two nodes are returned in search on custom node name, difer var model = Controller.DynamoModel; var examplePath = Path.Combine(GetTestDirectory(), @"core\custom_node_saving", "Constant2.dyf"); Controller.DynamoViewModel.OpenCommand.Execute(examplePath); var nodeWorkspace = model.Workspaces.FirstOrDefault(x => x is CustomNodeWorkspaceModel) as CustomNodeWorkspaceModel; Assert.IsNotNull(nodeWorkspace); var oldId = nodeWorkspace.CustomNodeDefinition.FunctionId; var newPath = this.GetNewFileNameOnTempPath("dyf"); var originalNumElements = Controller.SearchViewModel.SearchDictionary.NumElements; // save as nodeWorkspace.SaveAs(newPath); // introduces new function id var newId = nodeWorkspace.CustomNodeDefinition.FunctionId; // refactor oldId with new name var nodeName = "Constant2 Alt"; var catName = "TheCat"; var descr = "TheCat"; var dummyInfo1 = new CustomNodeInfo(newId, nodeName, catName, descr, ""); Controller.CustomNodeManager.Refactor(dummyInfo1); // num elements is unchanged by refactor Assert.AreEqual(originalNumElements + 1, Controller.SearchViewModel.SearchDictionary.NumElements); // search common base name Controller.SearchViewModel.SearchAndUpdateResultsSync("Constant2"); // results are correct Assert.AreEqual(2, Controller.SearchViewModel.SearchResults.Count); var res1 = Controller.SearchViewModel.SearchResults[0]; var res2 = Controller.SearchViewModel.SearchResults[1]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res1); Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res2); var node1 = res1 as CustomNodeSearchElement; var node2 = res2 as CustomNodeSearchElement; Assert.IsTrue((node1.Guid == oldId && node2.Guid == newId) || (node1.Guid == newId && node2.Guid == oldId)); }
public Package GetOwnerPackage(CustomNodeInfo def) { return GetOwnerPackage(def.Path); }
internal void Refactor(CustomNodeInfo nodeInfo) { this.Remove(nodeInfo.Name); this.Add(nodeInfo); }
/// <summary> /// Stores the path and function definition without initializing a node. Overwrites /// the existing NodeInfo if necessary /// </summary> private void SetNodeInfo(CustomNodeInfo newInfo) { NodeInfos[newInfo.FunctionId] = newInfo; OnInfoUpdated(newInfo); }
public void CanRefactorCustomNodeWhilePreservingDuplicates() { var nodeName = "TheNoodle"; var catName = "TheCat"; var descr = "TheCat"; var path = @"C:\turtle\graphics.dyn"; var guid1 = Guid.NewGuid(); var dummyInfo1 = new CustomNodeInfo(guid1, nodeName, catName, descr, path); var guid2 = Guid.NewGuid(); var dummyInfo2 = new CustomNodeInfo(guid2, nodeName, catName, descr, path); _search.Add(dummyInfo1); _search.Add(dummyInfo2); Assert.AreEqual(2, _search.SearchDictionary.NumElements); // refactor one of the nodes with newNodeName var newNodeName = "TheTurtle"; var newInfo = new CustomNodeInfo(guid1, newNodeName, catName, descr, path); _search.Refactor(newInfo); // num elements is unchanged Assert.AreEqual(2, _search.SearchDictionary.NumElements); // search for new name _search.SearchAndUpdateResultsSync(newNodeName); // results are correct - only one result Assert.AreEqual(1, _search.SearchResults.Count); var res1 = _search.SearchResults[0]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res1); var node1 = res1 as CustomNodeSearchElement; Assert.AreEqual(node1.Guid, guid1); // search for old name _search.SearchAndUpdateResultsSync(nodeName); // results are correct - the first nodes are returned Assert.AreEqual(1, _search.SearchResults.Count); var res2 = _search.SearchResults[0]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res2); var node2 = res2 as CustomNodeSearchElement; Assert.AreEqual(node2.Guid, guid2); }
/// <summary> /// Collapse a set of nodes in a given workspace. Has the side effects of prompting the user /// first in order to obtain the name and category for the new node, /// writes the function to a dyf file, adds it to the FunctionDict, adds it to search, and compiles and /// places the newly created symbol (defining a lambda) in the Controller's FScheme Environment. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="currentWorkspace"> The workspace where</param> internal static void Collapse(IEnumerable <dynNodeModel> selectedNodes, dynWorkspaceModel currentWorkspace) { var selectedNodeSet = new HashSet <dynNodeModel>(selectedNodes); //First, prompt the user to enter a name string newNodeName = "", newNodeCategory = "", newNodeDescription = "A collapsed node"; if (!dynSettings.Controller.DynamoViewModel.ShowNewFunctionDialog(ref newNodeName, ref newNodeCategory, ref newNodeDescription)) { return; } var newNodeWorkspace = new FuncWorkspace(newNodeName, newNodeCategory, newNodeDescription, 0, 0) { WatchChanges = false }; var newNodeDefinition = new FunctionDefinition(Guid.NewGuid()) { Workspace = newNodeWorkspace }; currentWorkspace.DisableReporting(); #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <dynNodeModel, int, Tuple <int, dynNodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count).Where(node.HasInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <dynNodeModel, int, Tuple <int, dynNodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count).Where(node.HasOutput).SelectMany( data => node.Outputs[data] .Where(output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region Detect 1-node holes (higher-order function extraction) var curriedNodeArgs = new HashSet <dynNodeModel>( inputs .Select(x => x.Item3.Item2) .Intersect(outputs.Select(x => x.Item3.Item2))) .Select( outerNode => { var node = new dynApply1(); //MVVM : Don't make direct reference to view here //MVVM: no reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Can't set view location here //dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot //Canvas.SetLeft(nodeUI, Canvas.GetLeft(outerNode.NodeUI)); //Canvas.SetTop(nodeUI, Canvas.GetTop(outerNode.NodeUI)); node.X = outerNode.X; node.Y = outerNode.Y; //Fetch all input ports // in order // that have inputs // and whose input comes from an inner node List <int> inPortsConnected = Enumerable.Range(0, outerNode.InPortData.Count) .Where( x => outerNode.HasInput(x) && selectedNodeSet.Contains( outerNode.Inputs[x].Item2)) .ToList(); var nodeInputs = outputs .Where(output => output.Item3.Item2 == outerNode) .Select( output => new { InnerNodeInputSender = output.Item1, OuterNodeInPortData = output.Item3.Item1 }).ToList(); nodeInputs.ForEach(_ => node.AddInput()); node.RegisterAllPorts(); //MVVM: don't call update layout here //dynSettings.Bench.WorkBench.UpdateLayout(); return(new { OuterNode = outerNode, InnerNode = node, Outputs = inputs.Where(input => input.Item3.Item2 == outerNode) .Select(input => input.Item3.Item1), Inputs = nodeInputs, OuterNodePortDataList = inPortsConnected }); }).ToList(); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); #endregion #region Move selection to new workspace var connectors = new HashSet <dynConnectorModel>(currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) && selectedNodeSet.Contains(conn.End.Owner))); //Step 2: move all nodes to new workspace // remove from old foreach (var ele in selectedNodeSet) { currentWorkspace.Nodes.Remove(ele); } foreach (var ele in connectors) { currentWorkspace.Connectors.Remove(ele); } // add to new newNodeWorkspace.Nodes.AddRange(selectedNodeSet); newNodeWorkspace.Connectors.AddRange(connectors); double leftShift = leftMost - 250; foreach (dynNodeModel node in newNodeWorkspace.Nodes) { node.X = node.X - leftShift; node.Y = node.Y - topMost; } #endregion #region Insert new node into the current workspace //Step 5: insert new node into original workspace var collapsedNode = dynSettings.Controller.DynamoViewModel.CreateFunction( inputs.Select(x => x.Item1.InPortData[x.Item2].NickName), outputs .Where(x => !curriedNodeArgs.Any(y => y.OuterNode == x.Item3.Item2)) .Select(x => x.Item1.OutPortData[x.Item2].NickName), newNodeDefinition); collapsedNode.GUID = Guid.NewGuid(); currentWorkspace.Nodes.Add(collapsedNode); collapsedNode.WorkSpace = currentWorkspace; collapsedNode.X = avgX; collapsedNode.Y = avgY; #endregion #region Destroy all hanging connectors //Step 6: connect inputs and outputs var removeConnectors = currentWorkspace.Connectors.Where(c => selectedNodeSet.Contains(c.Start.Owner) || selectedNodeSet.Contains(c.End.Owner)) .ToList(); foreach (dynConnectorModel connector in removeConnectors) { connector.NotifyConnectedPortsOfDeletion(); currentWorkspace.Connectors.Remove(connector); } #endregion newNodeWorkspace.Nodes.ToList().ForEach(x => x.DisableReporting()); var inConnectors = new List <Tuple <dynNodeModel, int, int> >(); #region Process inputs var uniqueInputSenders = new Dictionary <Tuple <dynNodeModel, int>, dynSymbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; dynNodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; dynNodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; dynSymbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { //MVVM : replace NodeUI reference with node inConnectors.Add(Tuple.Create(inputNode, inputData, inputIndex)); //Create Symbol Node node = new dynSymbol { Symbol = inputReceiverNode.InPortData[inputReceiverData].NickName }; //MVVM : Don't make direct reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); * * //Place it in an appropriate spot * Canvas.SetLeft(nodeUI, 0); * Canvas.SetTop(nodeUI, inputIndex * (50 + node.NodeUI.Height)); * * dynSettings.Bench.WorkBench.UpdateLayout();*/ node.X = 0; node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; } var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode); if (curriedNode == null) { var conn1 = dynConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData, 0); if (conn1 != null) { newNodeWorkspace.Connectors.Add(conn1); } } else { //Connect it to the applier var conn = dynConnectorModel.Make(node, curriedNode.InnerNode, 0, 0, 0); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } //Connect applier to the inner input receive var conn2 = dynConnectorModel.Make( curriedNode.InnerNode, inputReceiverNode, 0, inputReceiverData, 0); if (conn2 != null) { newNodeWorkspace.Connectors.Add(conn2); } } } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <dynNodeModel, int> >(); var outConnectors = new List <Tuple <dynNodeModel, int, int> >(); int i = 0; foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { dynNodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; dynNodeModel outputReceiverNode = output.Item3.Item2; if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) { continue; } outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new dynOutput { Symbol = outputSenderNode.OutPortData[outputSenderData].NickName }; //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), false)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); * * //Place it in an appropriate spot * Canvas.SetLeft(nodeUI, rightMost + 75 - leftShift); * Canvas.SetTop(nodeUI, i * (50 + node.NodeUI.Height)); * * dynSettings.Bench.WorkBench.UpdateLayout();*/ node.X = rightMost + 75 - leftShift; node.Y = i * (50 + node.Height); var conn = dynConnectorModel.Make( outputSenderNode, node, outputSenderData, 0, 0); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } i++; } } //Connect outputs to new node foreach (var output in outputs) { //Node to be connected to in CurrentSpace dynNodeModel outputSenderNode = output.Item1; //Port to be connected to on outPutNode_outer int outputSenderData = output.Item2; int outputReceiverData = output.Item3.Item1; dynNodeModel outputReceiverNode = output.Item3.Item2; var curriedNode = curriedNodeArgs.FirstOrDefault( x => x.OuterNode == outputReceiverNode); if (curriedNode == null) { // we create the connectors in the current space later //MVVM : replaced multiple dynNodeView refrences with dynNode outConnectors.Add( Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { int targetPort = curriedNode.Inputs .First( x => x.InnerNodeInputSender == outputSenderNode) .OuterNodeInPortData; int targetPortIndex = curriedNode.OuterNodePortDataList.IndexOf(targetPort); //Connect it (new dynConnector) var conn = dynConnectorModel.Make( outputSenderNode, curriedNode.InnerNode, outputSenderData, targetPortIndex + 1, 0); if (conn != null) { newNodeWorkspace.Connectors.Add(conn); } } } #endregion //set the name on the node collapsedNode.NickName = newNodeName; currentWorkspace.Nodes.Remove(collapsedNode); // save and load the definition from file var customNodeInfo = new CustomNodeInfo(newNodeDefinition.FunctionId, newNodeName, newNodeCategory, "", ""); dynSettings.Controller.CustomNodeManager.SetNodeInfo(customNodeInfo); var path = dynSettings.Controller.DynamoViewModel.SaveFunctionOnly(newNodeDefinition); dynSettings.Controller.CustomNodeManager.SetNodePath(newNodeDefinition.FunctionId, path); dynSettings.Controller.SearchViewModel.Add(newNodeName, newNodeCategory, "No description provided", newNodeDefinition.FunctionId); dynSettings.Controller.DynamoViewModel.CreateNodeCommand.Execute(new Dictionary <string, object>() { { "name", collapsedNode.Definition.FunctionId.ToString() }, { "x", avgX }, { "y", avgY } }); var newlyPlacedCollapsedNode = currentWorkspace.Nodes .Where(node => node is dynFunction) .First(node => ((dynFunction)node).Definition.FunctionId == newNodeDefinition.FunctionId); // place the node as intended, not centered newlyPlacedCollapsedNode.X = avgX; newlyPlacedCollapsedNode.Y = avgY; newlyPlacedCollapsedNode.DisableReporting(); foreach (var nodeTuple in inConnectors) { var conn = dynConnectorModel.Make( nodeTuple.Item1, newlyPlacedCollapsedNode, nodeTuple.Item2, nodeTuple.Item3, 0); if (conn != null) { currentWorkspace.Connectors.Add(conn); } } foreach (var nodeTuple in outConnectors) { var conn = dynConnectorModel.Make( newlyPlacedCollapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3, 0); if (conn != null) { currentWorkspace.Connectors.Add(conn); } } newlyPlacedCollapsedNode.EnableReporting(); currentWorkspace.EnableReporting(); newNodeWorkspace.WatchChanges = true; }
public void Refactor(CustomNodeInfo nodeInfo) { this.Refactor(nodeInfo.Guid, nodeInfo.Name, nodeInfo.Category, nodeInfo.Description); }
/// <summary> /// Attempts to retrieve information for the given custom node name. If there are multiple /// custom nodes matching the given name, this method will return any one of them. /// </summary> /// <param name="name">Name of a custom node.</param> /// <param name="info"></param> /// <returns></returns> public bool TryGetNodeInfo(string name, out CustomNodeInfo info) { info = NodeInfos.Values.FirstOrDefault(x => x.Name == name); return(info != null); }
/// <summary> /// Attempts to retrieve information for the given custom node identifier. /// </summary> /// <param name="id">Custom node identifier.</param> /// <param name="info"></param> /// <returns>Success or failure.</returns> public bool TryGetNodeInfo(Guid id, out CustomNodeInfo info) { return(NodeInfos.TryGetValue(id, out info)); }
private void RegisterCustomNodeWorkspace( CustomNodeWorkspaceModel newWorkspace, CustomNodeInfo info, CustomNodeDefinition definition) { loadedWorkspaceModels[newWorkspace.CustomNodeId] = newWorkspace; SetFunctionDefinition(definition); OnDefinitionUpdated(definition); newWorkspace.DefinitionUpdated += () => { var newDef = newWorkspace.CustomNodeDefinition; SetFunctionDefinition(newDef); OnDefinitionUpdated(newDef); }; SetNodeInfo(info); newWorkspace.InfoChanged += () => { var newInfo = newWorkspace.CustomNodeInfo; SetNodeInfo(newInfo); OnInfoUpdated(newInfo); }; newWorkspace.FunctionIdChanged += oldGuid => { loadedWorkspaceModels.Remove(oldGuid); loadedCustomNodes.Remove(oldGuid); loadOrder.Remove(oldGuid); loadedWorkspaceModels[newWorkspace.CustomNodeId] = newWorkspace; }; }
/// <summary> /// Attempts to retrieve information for the given custom node name. If there are multiple /// custom nodes matching the given name, this method will return any one of them. /// </summary> /// <param name="name">Name of a custom node.</param> /// <param name="info"></param> /// <returns></returns> public bool TryGetNodeInfo(string name, out CustomNodeInfo info) { info = NodeInfos.Values.FirstOrDefault(x => x.Name == name); return info != null; }
public void CanRemoveNodeAndCategoryByFunctionId() { var nodeName = "TheNoodle"; var catName = "TheCat"; var descr = "TheCat"; var path = @"C:\turtle\graphics.dyn"; var guid1 = Guid.NewGuid(); var dummyInfo1 = new CustomNodeInfo(guid1, nodeName, catName, descr, path); // add custom node _search.Add(dummyInfo1); // confirm it's in the dictionary Assert.AreEqual(1, _search.SearchDictionary.NumElements); // remove custom node _search.RemoveNodeAndEmptyParentCategory(guid1); // it's gone Assert.AreEqual(0, _search.SearchDictionary.NumElements); _search.SearchAndUpdateResultsSync(nodeName); Assert.AreEqual(0, _search.SearchResults.Count); }
public void Add(CustomNodeInfo nodeInfo) { this.Add(nodeInfo.Name, nodeInfo.Category, nodeInfo.Description, nodeInfo.Guid); }
public void CanAddDuplicateCustomNodeWithDifferentGuidsAndGetBothInResults() { var nodeName = "TheNoodle"; var catName = "TheCat"; var descr = "TheCat"; var path = @"C:\turtle\graphics.dyn"; var guid1 = Guid.NewGuid(); var guid2 = Guid.NewGuid(); var dummyInfo1 = new CustomNodeInfo(guid1, nodeName, catName, descr, path); var dummyInfo2 = new CustomNodeInfo(guid2, nodeName, catName, descr, path); _search.Add(dummyInfo1); _search.Add(dummyInfo2); Assert.AreEqual(2, _search.SearchDictionary.NumElements); _search.SearchAndUpdateResultsSync(nodeName); Assert.AreEqual(2, _search.SearchResults.Count); var res1 = _search.SearchResults[0]; var res2 = _search.SearchResults[1]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res1); Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res2); var node1 = res1 as CustomNodeSearchElement; var node2 = res2 as CustomNodeSearchElement; Assert.AreEqual(node1.Guid, guid1); Assert.AreEqual(node2.Guid, guid2); }
public bool IsUnderPackageControl(CustomNodeInfo def) { return IsUnderPackageControl(def.Path); }
public void CanRemoveSingleCustomNodeByIdWhereThereAreDuplicatesWithDifferentIds() { var nodeName = "TheNoodle"; var catName = "TheCat"; var descr = "TheCat"; var path = @"C:\turtle\graphics.dyn"; var guid1 = Guid.NewGuid(); var guid2 = Guid.NewGuid(); var dummyInfo1 = new CustomNodeInfo(guid1, nodeName, catName, descr, path); var dummyInfo2 = new CustomNodeInfo(guid2, nodeName, catName, descr, path); _search.Add(dummyInfo1); _search.Add(dummyInfo2); Assert.AreEqual(2, _search.SearchDictionary.NumElements); _search.RemoveNodeAndEmptyParentCategory(guid2); Assert.AreEqual(1, _search.SearchDictionary.NumElements); _search.SearchAndUpdateResultsSync(nodeName); Assert.AreEqual(1, _search.SearchResults.Count); var res1 = _search.SearchResults[0]; Assert.IsAssignableFrom(typeof(CustomNodeSearchElement), res1); var node1 = res1 as CustomNodeSearchElement; Assert.AreEqual(node1.Guid, guid1); }
/// <summary> /// Helper method for custom node adding and removing /// </summary> public void AssertAddAndRemoveCustomNode(SearchViewModel search, string nodeName, string catName, string descr = "Bla", string path = "Bla") { var dummyInfo = new CustomNodeInfo(Guid.NewGuid(), nodeName, catName, descr, path); search.Add(dummyInfo); search.SearchAndUpdateResultsSync(nodeName); Assert.AreNotEqual(0, search.SearchResults.Count); Assert.AreEqual(search.SearchResults[0].Name, nodeName); Assert.IsTrue(search.ContainsCategory(catName)); search.RemoveNodeAndEmptyParentCategory(nodeName); search.SearchAndUpdateResultsSync(nodeName); Assert.AreEqual(0, search.SearchResults.Count); Assert.IsFalse(search.ContainsCategory(catName)); }
/// <summary> /// Save a function. This includes writing to a file and compiling the /// function and saving it to the FSchemeEnvironment /// </summary> /// <param name="definition">The definition to save</param> /// <param name="bool">Whether to write the function to file.</param> /// <returns>Whether the operation was successful</returns> public void SaveFunction(FunctionDefinition definition, bool writeDefinition = true, bool addToSearch = false, bool compileFunction = true) { if (definition == null) return; // Get the internal nodes for the function var functionWorkspace = definition.Workspace as FuncWorkspace; // If asked to, write the definition to file if (writeDefinition) { string path = ""; if (String.IsNullOrEmpty(definition.Workspace.FilePath)) { var pluginsPath = this.Controller.CustomNodeManager.GetDefaultSearchPath(); if (!Directory.Exists(pluginsPath)) Directory.CreateDirectory(pluginsPath); path = Path.Combine(pluginsPath, dynSettings.FormatFileName(functionWorkspace.Name) + ".dyf"); } else { path = definition.Workspace.FilePath; } try { dynWorkspaceModel.SaveWorkspace(path, functionWorkspace); if (addToSearch) { Controller.SearchViewModel.Add(functionWorkspace.Name, functionWorkspace.Category, functionWorkspace.Description, definition.FunctionId); } var customNodeInfo = new CustomNodeInfo(definition.FunctionId, functionWorkspace.Name, functionWorkspace.Category, functionWorkspace.Description, path); Controller.CustomNodeManager.SetNodeInfo(customNodeInfo); #region Compile Function and update all nodes IEnumerable<string> inputNames = new List<string>(); IEnumerable<string> outputNames = new List<string>(); dynSettings.Controller.FSchemeEnvironment.DefineSymbol(definition.FunctionId.ToString(), CustomNodeManager.CompileFunction(definition, ref inputNames, ref outputNames)); //Update existing function nodes which point to this function to match its changes foreach (dynNodeModel el in AllNodes) { if (el is dynFunction) { var node = (dynFunction)el; if (node.Definition != definition) continue; node.SetInputs(inputNames); node.SetOutputs(outputNames); el.RegisterAllPorts(); } } //Call OnSave for all saved elements foreach (dynNodeModel el in functionWorkspace.Nodes) el.onSave(); #endregion } catch (Exception e) { Log("Error saving:" + e.GetType()); Log(e); } } }
/// <summary> /// Import a dyf file for eventual initialization. /// </summary> /// <returns>null if we failed to get data from the path, otherwise the CustomNodeInfo object for the </returns> public CustomNodeInfo AddFileToPath(string file) { Guid guid; string name; string category; string description; if (!GetHeaderFromPath(file, out guid, out name, out category, out description)) { return null; } // the node has already been loaded // from somewhere else if (Contains(guid)) { var nodeInfo = GetNodeInfo(guid); if (nodeInfo != null) { return nodeInfo; } } var info = new CustomNodeInfo(guid, name, category, description, file); SetNodeInfo(info); return info; }
/// <summary> /// Stores the path and function definition without initializing a node /// </summary> /// <param name="guid">The unique id for the node.</param> /// <param name="path">The path for the node.</param> public void SetNodeInfo(CustomNodeInfo info) { this.SetNodeName(info.Guid, info.Name); this.SetNodeCategory(info.Guid, info.Category); this.SetNodeDescription(info.Guid, info.Description); this.SetNodePath(info.Guid, info.Path); }
public void Refactor(CustomNodeInfo nodeInfo) { Refactor(nodeInfo.Guid, nodeInfo.Name, nodeInfo.Category, nodeInfo.Description); }
public bool Add(CustomNodeInfo nodeInfo) { var nodeEle = new CustomNodeSearchElement(nodeInfo); if (SearchDictionary.Contains(nodeEle)) { return this.Refactor(nodeInfo); } SearchDictionary.Add(nodeEle, nodeEle.Name); SearchDictionary.Add(nodeEle, nodeInfo.Category + "." + nodeEle.Name); TryAddCategoryAndItem(nodeInfo.Category, nodeEle); return true; }
/// <summary> /// Import a dyf file for eventual initialization. /// </summary> /// <param name="file">Path to a custom node file on disk.</param> /// <param name="isTestMode"> /// Flag specifying whether or not this should operate in "test mode". /// </param> /// <param name="info"> /// If the info was successfully processed, this parameter will be set to /// it. Otherwise, it will be set to null. /// </param> /// <returns>True on success, false if the file could not be read properly.</returns> public bool AddUninitializedCustomNode(string file, bool isTestMode, out CustomNodeInfo info) { if (TryGetInfoFromPath(file, isTestMode, out info)) { SetNodeInfo(info); return true; } return false; }
public bool Refactor(CustomNodeInfo nodeInfo) { this.RemoveNodeAndEmptyParentCategory(nodeInfo.Guid); return this.Add(nodeInfo); }
/// <summary> /// Get a guid from a specific path, internally this first calls GetDefinitionFromPath /// </summary> /// <param name="path">The path from which to get the guid</param> /// <param name="isTestMode"> /// Flag specifying whether or not this should operate in "test mode". /// </param> /// <param name="info"></param> /// <returns>The custom node info object - null if we failed</returns> public bool TryGetInfoFromPath(string path, bool isTestMode, out CustomNodeInfo info) { try { var xmlDoc = new XmlDocument(); xmlDoc.Load(path); WorkspaceInfo header; if (!WorkspaceInfo.FromXmlDocument(xmlDoc, path, isTestMode, false, AsLogger(), out header)) { Log(String.Format(Properties.Resources.FailedToLoadHeader, path)); info = null; return false; } info = new CustomNodeInfo( Guid.Parse(header.ID), header.Name, header.Category, header.Description, path); return true; } catch (Exception e) { Log(String.Format(Properties.Resources.FailedToLoadHeader, path)); Log(e.ToString()); info = null; return false; } }
/// <summary> /// Attempts to retrieve information for the given custom node identifier. /// </summary> /// <param name="id">Custom node identifier.</param> /// <param name="info"></param> /// <returns>Success or failure.</returns> public bool TryGetNodeInfo(Guid id, out CustomNodeInfo info) { return NodeInfos.TryGetValue(id, out info); }
public void CustomNodeSaveAsAddsNewCustomNodeToSearchAndItCanBeRefactoredWhilePreservingOriginalFromExistingDyf() { // open custom node // SaveAs // two nodes are returned in search on custom node name, difer var model = Controller.DynamoModel; var examplePath = Path.Combine(GetTestDirectory(), @"core\custom_node_saving", "Constant2.dyf"); model.Open(examplePath); var nodeWorkspace = model.Workspaces.FirstOrDefault(x => x is CustomNodeWorkspaceModel) as CustomNodeWorkspaceModel; Assert.IsNotNull(nodeWorkspace); var oldId = nodeWorkspace.CustomNodeDefinition.FunctionId; var newPath = this.GetNewFileNameOnTempPath("dyf"); var originalNumElements = Controller.SearchViewModel.SearchDictionary.NumElements; // save as nodeWorkspace.SaveAs(newPath); // introduces new function id var newId = nodeWorkspace.CustomNodeDefinition.FunctionId; // refactor oldId with new name var nodeName = "TheNoodle"; var catName = "TheCat"; var descr = "TheCat"; var dummyInfo1 = new CustomNodeInfo(newId, nodeName, catName, descr, ""); Controller.CustomNodeManager.Refactor(dummyInfo1); // num elements is unchanged by refactor Assert.AreEqual(originalNumElements + 1, Controller.SearchViewModel.SearchDictionary.NumElements); // search for refactored node Controller.SearchViewModel.SearchAndUpdateResultsSync("TheNoodle"); // results are correct Assert.AreEqual(1, Controller.SearchViewModel.SearchResults.Count); var node3 = Controller.SearchViewModel.SearchResults[0] as CustomNodeSearchElement; Assert.AreEqual(newId, node3.Guid); // search for un-refactored node Controller.SearchViewModel.SearchAndUpdateResultsSync("Constant2"); // results are correct Assert.AreEqual(1, Controller.SearchViewModel.SearchResults.Count); var node4 = Controller.SearchViewModel.SearchResults[0] as CustomNodeSearchElement; Assert.AreEqual(oldId, node4.Guid); }
protected virtual void OnInfoUpdated(CustomNodeInfo obj) { var handler = InfoUpdated; if (handler != null) handler(obj); }
/// <summary> /// Collapse a set of nodes in a given workspace. Has the side effects of prompting the user /// first in order to obtain the name and category for the new node, /// writes the function to a dyf file, adds it to the FunctionDict, adds it to search, and compiles and /// places the newly created symbol (defining a lambda) in the Controller's FScheme Environment. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="currentWorkspace"> The workspace where</param> internal static void Collapse(IEnumerable<dynNodeModel> selectedNodes, dynWorkspaceModel currentWorkspace) { var selectedNodeSet = new HashSet<dynNodeModel>(selectedNodes); //First, prompt the user to enter a name string newNodeName = "", newNodeCategory = "", newNodeDescription = "A collapsed node"; if (!dynSettings.Controller.DynamoViewModel.ShowNewFunctionDialog(ref newNodeName, ref newNodeCategory, ref newNodeDescription)) { return; } var newNodeWorkspace = new FuncWorkspace(newNodeName, newNodeCategory, newNodeDescription, 0, 0) { WatchChanges = false }; var newNodeDefinition = new FunctionDefinition(Guid.NewGuid()) { Workspace = newNodeWorkspace }; currentWorkspace.DisableReporting(); #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet<Tuple<dynNodeModel, int, Tuple<int, dynNodeModel>>>( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count).Where(node.HasInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet<Tuple<dynNodeModel, int, Tuple<int, dynNodeModel>>>( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count).Where(node.HasOutput).SelectMany( data => node.Outputs[data] .Where(output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region Detect 1-node holes (higher-order function extraction) var curriedNodeArgs = new HashSet<dynNodeModel>( inputs .Select(x => x.Item3.Item2) .Intersect(outputs.Select(x => x.Item3.Item2))) .Select( outerNode => { var node = new dynApply1(); //MVVM : Don't make direct reference to view here //MVVM: no reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Can't set view location here //dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot //Canvas.SetLeft(nodeUI, Canvas.GetLeft(outerNode.NodeUI)); //Canvas.SetTop(nodeUI, Canvas.GetTop(outerNode.NodeUI)); node.X = outerNode.X; node.Y = outerNode.Y; //Fetch all input ports // in order // that have inputs // and whose input comes from an inner node List<int> inPortsConnected = Enumerable.Range(0, outerNode.InPortData.Count) .Where( x => outerNode.HasInput(x) && selectedNodeSet.Contains( outerNode.Inputs[x].Item2)) .ToList(); var nodeInputs = outputs .Where(output => output.Item3.Item2 == outerNode) .Select( output => new { InnerNodeInputSender = output.Item1, OuterNodeInPortData = output.Item3.Item1 }).ToList(); nodeInputs.ForEach(_ => node.AddInput()); node.RegisterAllPorts(); //MVVM: don't call update layout here //dynSettings.Bench.WorkBench.UpdateLayout(); return new { OuterNode = outerNode, InnerNode = node, Outputs = inputs.Where(input => input.Item3.Item2 == outerNode) .Select(input => input.Item3.Item1), Inputs = nodeInputs, OuterNodePortDataList = inPortsConnected }; }).ToList(); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); #endregion #region Move selection to new workspace var connectors = new HashSet<dynConnectorModel>(currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) && selectedNodeSet.Contains(conn.End.Owner))); //Step 2: move all nodes to new workspace // remove from old foreach (var ele in selectedNodeSet) { currentWorkspace.Nodes.Remove(ele); } foreach (var ele in connectors) { currentWorkspace.Connectors.Remove(ele); } // add to new newNodeWorkspace.Nodes.AddRange(selectedNodeSet); newNodeWorkspace.Connectors.AddRange(connectors); double leftShift = leftMost - 250; foreach (dynNodeModel node in newNodeWorkspace.Nodes) { node.X = node.X - leftShift; node.Y = node.Y - topMost; } #endregion #region Insert new node into the current workspace //Step 5: insert new node into original workspace var collapsedNode = dynSettings.Controller.DynamoViewModel.CreateFunction( inputs.Select(x => x.Item1.InPortData[x.Item2].NickName), outputs .Where(x => !curriedNodeArgs.Any(y => y.OuterNode == x.Item3.Item2)) .Select(x => x.Item1.OutPortData[x.Item2].NickName), newNodeDefinition); collapsedNode.GUID = Guid.NewGuid(); currentWorkspace.Nodes.Add(collapsedNode); collapsedNode.WorkSpace = currentWorkspace; collapsedNode.X = avgX; collapsedNode.Y = avgY; #endregion #region Destroy all hanging connectors //Step 6: connect inputs and outputs var removeConnectors = currentWorkspace.Connectors.Where(c => selectedNodeSet.Contains(c.Start.Owner) || selectedNodeSet.Contains(c.End.Owner)) .ToList(); foreach (dynConnectorModel connector in removeConnectors) { connector.NotifyConnectedPortsOfDeletion(); currentWorkspace.Connectors.Remove(connector); } #endregion newNodeWorkspace.Nodes.ToList().ForEach(x => x.DisableReporting()); var inConnectors = new List<Tuple<dynNodeModel, int, int>>(); #region Process inputs var uniqueInputSenders = new Dictionary<Tuple<dynNodeModel, int>, dynSymbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; dynNodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; dynNodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; dynSymbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { //MVVM : replace NodeUI reference with node inConnectors.Add(Tuple.Create(inputNode, inputData, inputIndex)); //Create Symbol Node node = new dynSymbol { Symbol = inputReceiverNode.InPortData[inputReceiverData].NickName }; //MVVM : Don't make direct reference to view here //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), true)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot Canvas.SetLeft(nodeUI, 0); Canvas.SetTop(nodeUI, inputIndex * (50 + node.NodeUI.Height)); dynSettings.Bench.WorkBench.UpdateLayout();*/ node.X = 0; node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; } var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode); if (curriedNode == null) { var conn1 = dynConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData, 0 ); if (conn1 != null) newNodeWorkspace.Connectors.Add(conn1); } else { //Connect it to the applier var conn = dynConnectorModel.Make(node, curriedNode.InnerNode, 0, 0, 0); if (conn != null) newNodeWorkspace.Connectors.Add(conn); //Connect applier to the inner input receive var conn2 = dynConnectorModel.Make( curriedNode.InnerNode, inputReceiverNode, 0, inputReceiverData, 0); if (conn2 != null) newNodeWorkspace.Connectors.Add(conn2); } } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List<Tuple<dynNodeModel, int>>(); var outConnectors = new List<Tuple<dynNodeModel, int, int>>(); int i = 0; foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { dynNodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; dynNodeModel outputReceiverNode = output.Item3.Item2; if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) continue; outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new dynOutput { Symbol = outputSenderNode.OutPortData[outputSenderData].NickName }; //dynNodeView nodeUI = node.NodeUI; var elNameAttrib = node.GetType().GetCustomAttributes(typeof(NodeNameAttribute), false)[0] as NodeNameAttribute; if (elNameAttrib != null) { node.NickName = elNameAttrib.Name; } node.GUID = Guid.NewGuid(); //store the element in the elements list newNodeWorkspace.Nodes.Add(node); node.WorkSpace = newNodeWorkspace; node.DisableReporting(); //MVVM : Do not add view directly to canvas /*dynSettings.Bench.WorkBench.Children.Add(nodeUI); //Place it in an appropriate spot Canvas.SetLeft(nodeUI, rightMost + 75 - leftShift); Canvas.SetTop(nodeUI, i * (50 + node.NodeUI.Height)); dynSettings.Bench.WorkBench.UpdateLayout();*/ node.X = rightMost + 75 - leftShift; node.Y = i*(50 + node.Height); var conn = dynConnectorModel.Make( outputSenderNode, node, outputSenderData, 0, 0 ); if (conn != null) newNodeWorkspace.Connectors.Add(conn); i++; } } //Connect outputs to new node foreach (var output in outputs) { //Node to be connected to in CurrentSpace dynNodeModel outputSenderNode = output.Item1; //Port to be connected to on outPutNode_outer int outputSenderData = output.Item2; int outputReceiverData = output.Item3.Item1; dynNodeModel outputReceiverNode = output.Item3.Item2; var curriedNode = curriedNodeArgs.FirstOrDefault( x => x.OuterNode == outputReceiverNode); if (curriedNode == null) { // we create the connectors in the current space later //MVVM : replaced multiple dynNodeView refrences with dynNode outConnectors.Add( Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { int targetPort = curriedNode.Inputs .First( x => x.InnerNodeInputSender == outputSenderNode) .OuterNodeInPortData; int targetPortIndex = curriedNode.OuterNodePortDataList.IndexOf(targetPort); //Connect it (new dynConnector) var conn = dynConnectorModel.Make( outputSenderNode, curriedNode.InnerNode, outputSenderData, targetPortIndex + 1, 0); if (conn != null) newNodeWorkspace.Connectors.Add(conn); } } #endregion //set the name on the node collapsedNode.NickName = newNodeName; currentWorkspace.Nodes.Remove(collapsedNode); // save and load the definition from file var customNodeInfo = new CustomNodeInfo(newNodeDefinition.FunctionId, newNodeName, newNodeCategory, "", ""); dynSettings.Controller.CustomNodeManager.SetNodeInfo(customNodeInfo); var path = dynSettings.Controller.DynamoViewModel.SaveFunctionOnly(newNodeDefinition); dynSettings.Controller.CustomNodeManager.SetNodePath(newNodeDefinition.FunctionId, path); dynSettings.Controller.SearchViewModel.Add(newNodeName, newNodeCategory, "No description provided", newNodeDefinition.FunctionId); dynSettings.Controller.DynamoViewModel.CreateNodeCommand.Execute(new Dictionary<string, object>() { {"name", collapsedNode.Definition.FunctionId.ToString() }, {"x", avgX }, {"y", avgY } }); var newlyPlacedCollapsedNode = currentWorkspace.Nodes .Where(node => node is dynFunction) .First(node => ((dynFunction)node).Definition.FunctionId == newNodeDefinition.FunctionId); // place the node as intended, not centered newlyPlacedCollapsedNode.X = avgX; newlyPlacedCollapsedNode.Y = avgY; newlyPlacedCollapsedNode.DisableReporting(); foreach (var nodeTuple in inConnectors) { var conn = dynConnectorModel.Make( nodeTuple.Item1, newlyPlacedCollapsedNode, nodeTuple.Item2, nodeTuple.Item3, 0 ); if (conn != null) currentWorkspace.Connectors.Add(conn); } foreach (var nodeTuple in outConnectors) { var conn = dynConnectorModel.Make( newlyPlacedCollapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3, 0 ); if (conn != null) currentWorkspace.Connectors.Add(conn); } newlyPlacedCollapsedNode.EnableReporting(); currentWorkspace.EnableReporting(); newNodeWorkspace.WatchChanges = true; }