Esempio n. 1
0
        /// <summary>
        ///     Deserialize a function definition from a given path.  A side effect of this function is that
        ///     the node is added to the dictionary of loadedNodes.  
        /// </summary>
        /// <param name="funcDefGuid">The function guid we're currently loading</param>
        /// <param name="def">The resultant function definition</param>
        /// <returns></returns>
        private bool GetDefinitionFromPath(Guid funcDefGuid, out CustomNodeDefinition def)
        {
            try
            {
                var xmlPath = GetNodePath(funcDefGuid);

                #region read xml file

                var xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlPath);

                string funName = null;
                string category = "";
                double cx = 0;
                double cy = 0;
                string description = "";
                string version = "";

                double zoom = 1.0;
                string id = "";

                // load the header

                // handle legacy workspace nodes called dynWorkspace
                // and new workspaces without the dyn prefix
                XmlNodeList workspaceNodes = xmlDoc.GetElementsByTagName("Workspace");
                if(workspaceNodes.Count == 0)
                    workspaceNodes = xmlDoc.GetElementsByTagName("dynWorkspace");

                foreach (XmlNode node in workspaceNodes)
                {
                    foreach (XmlAttribute att in node.Attributes)
                    {
                        if (att.Name.Equals("X"))
                            cx = double.Parse(att.Value, CultureInfo.InvariantCulture);
                        else if (att.Name.Equals("Y"))
                            cy = double.Parse(att.Value, CultureInfo.InvariantCulture);
                        else if (att.Name.Equals("zoom"))
                            zoom = double.Parse(att.Value, CultureInfo.InvariantCulture);
                        else if (att.Name.Equals("Name"))
                            funName = att.Value;
                        else if (att.Name.Equals("Category"))
                            category = att.Value;
                        else if (att.Name.Equals("Description"))
                            description = att.Value;
                        else if (att.Name.Equals("ID"))
                            id = att.Value;
                        else if (att.Name.Equals("Version"))
                            version = att.Value;
                    }
                }

                Version fileVersion = MigrationManager.VersionFromString(version);

                var currentVersion = MigrationManager.VersionFromWorkspace(dynamoModel.HomeSpace);

                if (fileVersion > currentVersion)
                {
                    bool resume = Utils.DisplayFutureFileMessage(this.dynamoModel, xmlPath, fileVersion, currentVersion);
                    if (!resume)
                    {
                        def = null;
                        return false;
                    }
                }

                var decision = MigrationManager.ShouldMigrateFile(fileVersion, currentVersion);
                if (decision == MigrationManager.Decision.Abort)
                {
                    Utils.DisplayObsoleteFileMessage(this.dynamoModel, xmlPath, fileVersion, currentVersion);
                    
                    def = null;
                    return false;
                }
                else if (decision == MigrationManager.Decision.Migrate)
                {
                    string backupPath = string.Empty;
                    bool isTesting = DynamoModel.IsTestMode; // No backup during test.
                    if (!isTesting && MigrationManager.BackupOriginalFile(xmlPath, ref backupPath))
                    {
                        string message = string.Format(
                            "Original file '{0}' gets backed up at '{1}'",
                            Path.GetFileName(xmlPath), backupPath);

                        dynamoModel.Logger.Log(message);
                    }

                    MigrationManager.Instance.ProcessWorkspaceMigrations(this.dynamoModel, xmlDoc, fileVersion);
                    MigrationManager.Instance.ProcessNodesInWorkspace(this.dynamoModel, xmlDoc, fileVersion);
                }

                // we have a dyf and it lacks an ID field, we need to assign it
                // a deterministic guid based on its name.  By doing it deterministically,
                // files remain compatible
                if (string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(funName))
                {
                    id = GuidUtility.Create(GuidUtility.UrlNamespace, funName).ToString();
                }

                #endregion

                //DynamoCommands.WriteToLogCmd.Execute("Loading node definition for \"" + funName + "\" from: " + xmlPath);
                this.dynamoModel.Logger.Log("Loading node definition for \"" + funName + "\" from: " + xmlPath);

                var ws = new CustomNodeWorkspaceModel(dynamoModel,
                    funName, category.Length > 0
                    ? category
                    : "Custom Nodes", description, cx, cy)
                {
                    WatchChanges = false,
                    FileName = xmlPath,
                    Zoom = zoom
                };

                def = new CustomNodeDefinition(Guid.Parse(id))
                {
                    WorkspaceModel = ws,
                    IsBeingLoaded = true
                };

                // set the node as loaded
                LoadedCustomNodes.Remove(def.FunctionId);
                LoadedCustomNodes.Add(def.FunctionId, def);

                XmlNodeList elNodes = xmlDoc.GetElementsByTagName("Elements");
                XmlNodeList cNodes = xmlDoc.GetElementsByTagName("Connectors");
                XmlNodeList nNodes = xmlDoc.GetElementsByTagName("Notes");

                if (elNodes.Count == 0)
                    elNodes = xmlDoc.GetElementsByTagName("dynElements");
                if (cNodes.Count == 0)
                    cNodes = xmlDoc.GetElementsByTagName("dynConnectors");
                if (nNodes.Count == 0)
                    nNodes = xmlDoc.GetElementsByTagName("dynNotes");

                XmlNode elNodesList = elNodes[0];
                XmlNode cNodesList = cNodes[0];
                XmlNode nNodesList = nNodes[0];

                #region instantiate nodes

                foreach (XmlNode elNode in elNodesList.ChildNodes)
                {
                    XmlAttribute typeAttrib = elNode.Attributes["type"];
                    XmlAttribute guidAttrib = elNode.Attributes["guid"];
                    XmlAttribute nicknameAttrib = elNode.Attributes["nickname"];
                    XmlAttribute xAttrib = elNode.Attributes["x"];
                    XmlAttribute yAttrib = elNode.Attributes["y"];
                    XmlAttribute lacingAttrib = elNode.Attributes["lacing"];
                    XmlAttribute isVisAttrib = elNode.Attributes["isVisible"];
                    XmlAttribute isUpstreamVisAttrib = elNode.Attributes["isUpstreamVisible"];

                    string typeName = typeAttrib.Value;

                    //test the GUID to confirm that it is non-zero
                    //if it is zero, then we have to fix it
                    //this will break the connectors, but it won't keep
                    //propagating bad GUIDs
                    var guid = new Guid(guidAttrib.Value);
                    if (guid == Guid.Empty)
                    {
                        guid = Guid.NewGuid();
                    }

                    string nickname = nicknameAttrib.Value;

                    double x = double.Parse(xAttrib.Value, CultureInfo.InvariantCulture);
                    double y = double.Parse(yAttrib.Value, CultureInfo.InvariantCulture);

                    bool isVisible = true;
                    if (isVisAttrib != null)
                        isVisible = isVisAttrib.Value == "true" ? true : false;

                    bool isUpstreamVisible = true;
                    if (isUpstreamVisAttrib != null)
                        isUpstreamVisible = isUpstreamVisAttrib.Value == "true" ? true : false;

                    // Retrieve optional 'function' attribute (only for DSFunction).
                    XmlAttribute signatureAttrib = elNode.Attributes["function"];
                    var signature = signatureAttrib == null ? null : signatureAttrib.Value;

                    NodeModel el = null;
                    XmlElement dummyElement = null;

                    try
                    {
                        // The attempt to create node instance may fail due to "type" being
                        // something else other than "NodeModel" derived object type. This 
                        // is possible since some legacy nodes have been made to derive from
                        // "MigrationNode" object type that is not derived from "NodeModel".
                        // 
                        typeName = Nodes.Utilities.PreprocessTypeName(typeName);
                        System.Type type = Nodes.Utilities.ResolveType(this.dynamoModel, typeName);
                        if (type != null)
                            el = ws.NodeFactory.CreateNodeInstance(type, nickname, signature, guid);

                        if (el != null)
                        {
                            el.Load(elNode);
                        }
                        else
                        {
                            var e = elNode as XmlElement;
                            dummyElement = MigrationManager.CreateMissingNode(e, 1, 1);
                        }
                    }
                    catch (UnresolvedFunctionException)
                    {
                        // If a given function is not found during file load, then convert the 
                        // function node into a dummy node (instead of crashing the workflow).
                        // 
                        var e = elNode as XmlElement;
                        dummyElement = MigrationManager.CreateUnresolvedFunctionNode(e);
                    }

                    if (dummyElement != null) // If a dummy node placement is desired.
                    {
                        // The new type representing the dummy node.
                        typeName = dummyElement.GetAttribute("type");
                        System.Type type = Dynamo.Nodes.Utilities.ResolveType(this.dynamoModel, typeName);

                        el = ws.NodeFactory.CreateNodeInstance(type, nickname, string.Empty, guid);
                        el.Load(dummyElement);
                    }

                    ws.Nodes.Add(el);

                    el.X = x;
                    el.Y = y;

                    if (lacingAttrib != null)
                    {
                        LacingStrategy lacing = LacingStrategy.First;
                        Enum.TryParse(lacingAttrib.Value, out lacing);
                        el.ArgumentLacing = lacing;
                    }

                    el.DisableReporting();

                    // This is to fix MAGN-3648. Method reference in CBN that gets 
                    // loaded before method definition causes a CBN to be left in 
                    // a warning state. This is to clear such warnings and set the 
                    // node to "Dead" state (correct value of which will be set 
                    // later on with a call to "EnableReporting" below). Please 
                    // refer to the defect for details and other possible fixes.
                    // 
                    if (el.State == ElementState.Warning && (el is CodeBlockNodeModel))
                        el.State = ElementState.Dead;

                    el.IsVisible = isVisible;
                    el.IsUpstreamVisible = isUpstreamVisible;
                }

                #endregion

                #region instantiate connectors

                foreach (XmlNode connector in cNodesList.ChildNodes)
                {
                    XmlAttribute guidStartAttrib = connector.Attributes[0];
                    XmlAttribute intStartAttrib = connector.Attributes[1];
                    XmlAttribute guidEndAttrib = connector.Attributes[2];
                    XmlAttribute intEndAttrib = connector.Attributes[3];
                    XmlAttribute portTypeAttrib = connector.Attributes[4];

                    var guidStart = new Guid(guidStartAttrib.Value);
                    var guidEnd = new Guid(guidEndAttrib.Value);
                    int startIndex = Convert.ToInt16(intStartAttrib.Value);
                    int endIndex = Convert.ToInt16(intEndAttrib.Value);
                    var portType = ((PortType)Convert.ToInt16(portTypeAttrib.Value));

                    //find the elements to connect
                    NodeModel start = null;
                    NodeModel end = null;

                    foreach (NodeModel e in ws.Nodes)
                    {
                        if (e.GUID == guidStart)
                        {
                            start = e;
                        }
                        else if (e.GUID == guidEnd)
                        {
                            end = e;
                        }
                        if (start != null && end != null)
                        {
                            break;
                        }
                    }

                    try
                    {
                        var newConnector = ws.AddConnection(
                            start, end,
                            startIndex, endIndex,
                            portType);
                    }
                    catch
                    {
                        dynamoModel.WriteToLog(string.Format("ERROR : Could not create connector between {0} and {1}.", start.NickName, end.NickName));
                    }
                }

                #endregion

                #region instantiate notes

                if (nNodesList != null)
                {
                    foreach (XmlNode note in nNodesList.ChildNodes)
                    {
                        XmlAttribute textAttrib = note.Attributes[0];
                        XmlAttribute xAttrib = note.Attributes[1];
                        XmlAttribute yAttrib = note.Attributes[2];

                        string text = textAttrib.Value;
                        double x = Convert.ToDouble(xAttrib.Value, CultureInfo.InvariantCulture);
                        double y = Convert.ToDouble(yAttrib.Value, CultureInfo.InvariantCulture);

                        ws.AddNote(false, x, y, text, Guid.NewGuid());
                    }
                }

                #endregion

                foreach (var e in ws.Nodes)
                    e.EnableReporting();

                def.IsBeingLoaded = false;

                def.Compile(this.dynamoModel, this.dynamoModel.EngineController);

                ws.WatchChanges = true;

                OnGetDefinitionFromPath(def);

            }
            catch (Exception ex)
            {
                dynamoModel.WriteToLog("There was an error opening the workbench.");
                dynamoModel.WriteToLog(ex);

                if (DynamoModel.IsTestMode)
                    throw ex; // Rethrow for NUnit.

                def = null;
                return false;
            }

            return true;
        }
Esempio n. 2
0
        /// <summary>
        ///     Collapse a set of nodes in a given workspace.
        /// </summary>
        /// <param name="dynamoModel">The current DynamoModel</param>
        /// <param name="selectedNodes"> The function definition for the user-defined node </param>
        /// <param name="currentWorkspace"> The workspace where</param>
        /// <param name="args"></param>
        public static void Collapse(DynamoModel dynamoModel, IEnumerable<NodeModel> selectedNodes, WorkspaceModel currentWorkspace, FunctionNamePromptEventArgs args = null)
        {
            var selectedNodeSet = new HashSet<NodeModel>(selectedNodes);

            if (args == null || !args.Success)
            {
                args = new FunctionNamePromptEventArgs();
                dynamoModel.OnRequestsFunctionNamePrompt(null, args);

                
                if (!args.Success)
                {
                    return;
                }
            }

            // Note that undoable actions are only recorded for the "currentWorkspace", 
            // the nodes which get moved into "newNodeWorkspace" are not recorded for undo,
            // even in the new workspace. Their creations will simply be treated as part of
            // the opening of that new workspace (i.e. when a user opens a file, she will 
            // not expect the nodes that show up to be undoable).
            // 
            // After local nodes are moved into "newNodeWorkspace" as the result of 
            // conversion, if user performs an undo, new set of nodes will be created in 
            // "currentWorkspace" (not moving those nodes in the "newNodeWorkspace" back 
            // into "currentWorkspace"). In another word, undo recording is on a per-
            // workspace basis, it does not work across different workspaces.
            // 
            UndoRedoRecorder undoRecorder = currentWorkspace.UndoRecorder;
            using (undoRecorder.BeginActionGroup())
            {

                var newNodeWorkspace = new CustomNodeWorkspaceModel(
                    dynamoModel,
                    args.Name,
                    args.Category,
                    args.Description,
                    0,
                    0) { WatchChanges = false, HasUnsavedChanges = true };

                var newNodeDefinition = new CustomNodeDefinition(Guid.NewGuid())
                {
                    WorkspaceModel = 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<NodeModel, int, Tuple<int, NodeModel>>>(
                        selectedNodeSet.SelectMany(
                            node =>
                                Enumerable.Range(0, node.InPortData.Count)
                                .Where(node.HasConnectedInput)
                                .Select(data => Tuple.Create(node, data, node.Inputs[data]))
                                .Where(input => !selectedNodeSet.Contains(input.Item3.Item2))));

                var outputs =
                    new HashSet<Tuple<NodeModel, int, Tuple<int, NodeModel>>>(
                        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<NodeModel>(
                        inputs.Select(x => x.Item3.Item2)
                            .Intersect(outputs.Select(x => x.Item3.Item2))).Select(
                                outerNode =>
                                {
                                    //var node = new Apply1();
                                    var node = newNodeWorkspace.AddNode<Apply1>();
                                    node.SetNickNameFromAttribute();

                                    node.DisableReporting();

                                    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();

                                    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 Handle full selected connectors

                // Step 2: Determine all the connectors whose start/end owners are 
                // both in the selection set, and then move them from the current 
                // workspace into the new workspace.

                var fullySelectedConns =
                    new HashSet<ConnectorModel>(
                        currentWorkspace.Connectors.Where(
                            conn =>
                            {
                                bool startSelected = selectedNodeSet.Contains(conn.Start.Owner);
                                bool endSelected = selectedNodeSet.Contains(conn.End.Owner);
                                return startSelected && endSelected;
                            }));

                foreach (var ele in fullySelectedConns)
                {
                    undoRecorder.RecordDeletionForUndo(ele);
                    currentWorkspace.Connectors.Remove(ele);
                }

                #endregion

                #region Handle partially selected connectors

                // Step 3: Partially selected connectors (either one of its start 
                // and end owners is in the selection) are to be destroyed.

                var partiallySelectedConns =
                    currentWorkspace.Connectors.Where(
                        conn =>
                            selectedNodeSet.Contains(conn.Start.Owner)
                                || selectedNodeSet.Contains(conn.End.Owner)).ToList();

                foreach (ConnectorModel connector in partiallySelectedConns)
                {
                    undoRecorder.RecordDeletionForUndo(connector);
                    connector.NotifyConnectedPortsOfDeletion();
                    currentWorkspace.Connectors.Remove(connector);
                }

                #endregion

                #region Transfer nodes and connectors to new workspace

                // Step 4: move all nodes to new workspace remove from old
                // PB: This could be more efficiently handled by a copy paste, but we
                // are preservering the node 
                foreach (var ele in selectedNodeSet)
                {
                    undoRecorder.RecordDeletionForUndo(ele);
                    ele.SaveResult = false;
                    currentWorkspace.Nodes.Remove(ele);
                    ele.Workspace = newNodeWorkspace;
                }

                //  add to new
                newNodeWorkspace.Nodes.AddRange(selectedNodeSet);
                newNodeWorkspace.Connectors.AddRange(fullySelectedConns);

                foreach (var node in newNodeWorkspace.Nodes)
                    node.DisableReporting();

                double leftShift = leftMost - 250;
                foreach (NodeModel node in newNodeWorkspace.Nodes)
                {
                    node.X = node.X - leftShift;
                    node.Y = node.Y - topMost;
                }

                #endregion


                #region Process inputs

                var inConnectors = new List<Tuple<NodeModel, int>>();
                var uniqueInputSenders = new Dictionary<Tuple<NodeModel, int>, Symbol>();

                //Step 3: insert variables (reference step 1)
                foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create))
                {
                    int inputIndex = input.Item1;

                    NodeModel inputReceiverNode = input.Item2.Item1;
                    int inputReceiverData = input.Item2.Item2;

                    NodeModel inputNode = input.Item2.Item3.Item2;
                    int inputData = input.Item2.Item3.Item1;

                    Symbol node;

                    var key = Tuple.Create(inputNode, inputData);
                    if (uniqueInputSenders.ContainsKey(key))
                    {
                        node = uniqueInputSenders[key];
                    }
                    else
                    {
                        inConnectors.Add(Tuple.Create(inputNode, inputData));

                        node = newNodeWorkspace.AddNode<Symbol>();
                        node.InputSymbol = inputReceiverNode.InPortData[inputReceiverData].NickName;

                        node.SetNickNameFromAttribute();

                        node.DisableReporting();

                        node.X = 0;
                        node.Y = inputIndex*(50 + node.Height);

                        uniqueInputSenders[key] = node;
                    }

                    var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode);

                    if (curriedNode == null)
                    {
                        newNodeWorkspace.AddConnection(
                            node,
                            inputReceiverNode,
                            0,
                            inputReceiverData);
                    }
                    else
                    {
                        //Connect it to the applier
                        newNodeWorkspace.AddConnection(node, curriedNode.InnerNode, 0, 0);

                        //Connect applier to the inner input receive
                        newNodeWorkspace.AddConnection(
                            curriedNode.InnerNode,
                            inputReceiverNode,
                            0,
                            inputReceiverData);
                    }
                }

                #endregion

                #region Process outputs

                //List of all inner nodes to connect an output. Unique.
                var outportList = new List<Tuple<NodeModel, int>>();

                var outConnectors = new List<Tuple<NodeModel, int, int>>();

                int i = 0;
                if (outputs.Any())
                {
                    foreach (var output in outputs)
                    {
                        if (
                            outportList.All(
                                x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2)))
                        {
                            NodeModel outputSenderNode = output.Item1;
                            int outputSenderData = output.Item2;
                            NodeModel outputReceiverNode = output.Item3.Item2;

                            if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode))
                                continue;

                            outportList.Add(Tuple.Create(outputSenderNode, outputSenderData));

                            //Create Symbol Node
                            var node = newNodeWorkspace.AddNode<Output>();
                            node.Symbol = outputSenderNode.OutPortData[outputSenderData].NickName;

                            node.SetNickNameFromAttribute();
                            node.DisableReporting();

                            node.X = rightMost + 75 - leftShift;
                            node.Y = i*(50 + node.Height);

                            newNodeWorkspace.AddConnection(
                                outputSenderNode,
                                node,
                                outputSenderData,
                                0);

                            i++;
                        }
                    }

                    //Connect outputs to new node
                    foreach (var output in outputs)
                    {
                        //Node to be connected to in CurrentWorkspace
                        NodeModel outputSenderNode = output.Item1;

                        //Port to be connected to on outPutNode_outer
                        int outputSenderData = output.Item2;

                        int outputReceiverData = output.Item3.Item1;
                        NodeModel outputReceiverNode = output.Item3.Item2;

                        var curriedNode =
                            curriedNodeArgs.FirstOrDefault(x => x.OuterNode == outputReceiverNode);

                        if (curriedNode == null)
                        {
                            // we create the connectors in the current space later
                            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)
                            newNodeWorkspace.AddConnection(
                                outputSenderNode,
                                curriedNode.InnerNode,
                                outputSenderData,
                                targetPortIndex + 1);

                        }
                    }
                }
                else
                {
                    foreach (var hanging in
                        selectedNodeSet.SelectMany(
                            node =>
                                Enumerable.Range(0, node.OutPortData.Count)
                                .Where(port => !node.HasOutput(port))
                                .Select(port => new { node, port })).Distinct())
                    {
                        //Create Symbol Node
                        var node = newNodeWorkspace.AddNode<Output>();
                        node.Symbol = hanging.node.OutPortData[hanging.port].NickName;

                        node.SetNickNameFromAttribute();

                        //store the element in the elements list
                        node.DisableReporting();

                        node.X = rightMost + 75 - leftShift;
                        node.Y = i*(50 + node.Height);

                        newNodeWorkspace.AddConnection(hanging.node, node, hanging.port, 0);

                        i++;
                    }
                }

                #endregion

                // save and load the definition from file
                newNodeDefinition.SyncWithWorkspace(dynamoModel, true, true);
                dynamoModel.Workspaces.Add(newNodeWorkspace);

                string name = newNodeDefinition.FunctionId.ToString();
                var collapsedNode = currentWorkspace.AddNode(avgX, avgY, name);
                undoRecorder.RecordCreationForUndo(collapsedNode);

                // place the node as intended, not centered
                collapsedNode.X = avgX;
                collapsedNode.Y = avgY;

                collapsedNode.DisableReporting();

                foreach (
                    var nodeTuple in
                        inConnectors.Select(
                            (x, idx) => new { node = x.Item1, from = x.Item2, to = idx }))
                {
                    var conn = currentWorkspace.AddConnection(
                        nodeTuple.node,
                        collapsedNode,
                        nodeTuple.from,
                        nodeTuple.to);

                    if (conn != null)
                    {
                        undoRecorder.RecordCreationForUndo(conn);
                    }
                }

                foreach (var nodeTuple in outConnectors)
                {

                    var conn = currentWorkspace.AddConnection(
                        collapsedNode,
                        nodeTuple.Item1,
                        nodeTuple.Item2,
                        nodeTuple.Item3);

                    if (conn != null)
                    {
                        undoRecorder.RecordCreationForUndo(conn);
                    }
                }

                collapsedNode.EnableReporting();
                currentWorkspace.EnableReporting();

                foreach (var node in newNodeWorkspace.Nodes)
                    node.EnableReporting();

                newNodeWorkspace.WatchChanges = true;
            }
        }