public void CreateAndWriteGraph() { //Instantiate property set manager necessary to gather alignment names PropertySetManager psmPipeline = new PropertySetManager( Application.DocumentManager.MdiActiveDocument.Database, PSetDefs.DefinedSets.DriPipelineData); PSetDefs.DriPipelineData driPipelineData = new PSetDefs.DriPipelineData(); //Counter to count all disjoined graphs int graphCount = 0; //Flag to signal the entry point subgraph bool isEntryPoint = false; //Stringbuilder to collect all disjoined graphs StringBuilder sbAll = new StringBuilder(); sbAll.AppendLine("digraph G {"); while (GraphEntities.Count > 0) { //Increment graph counter graphCount++; //Collection to keep track of visited nodes //To prevent looping for ever //And to be able to handle disjoined piping networks HashSet <Handle> visitedHandles = new HashSet <Handle>(); //Determine starting entity //Criteria: Only one child -> means an end node AND largest DN of all not visited //Currently only for one network //Disjoined networks are not handled yet GraphEntity ge = GraphEntities.Where(x => x.Cons.Length == 1).MaxBy(x => x.LargestDn()).FirstOrDefault(); //prdDbg(ge.OwnerHandle.ToString()); if (ge == null) { //throw new System.Exception("No entity found!"); prdDbg("ERROR: Graph not complete!!!"); foreach (var item in GraphEntities) { Entity owner = item.Owner; Line line = new Line(); line.Layer = "0"; line.StartPoint = new Point3d(); switch (owner) { case Polyline pline: line.EndPoint = pline.GetPointAtDist(pline.Length / 2); break; case BlockReference br: line.EndPoint = br.Position; break; default: break; } line.AddEntityToDbModelSpace(Application.DocumentManager.MdiActiveDocument.Database); } break; } //Flag the entry point subgraph isEntryPoint = true; //Variable to cache previous handle to avoid backreferences Handle previousHandle = default; //Collection to collect the edges HashSet <Edge> edges = new HashSet <Edge>(); //Collection to collect the subgraphs Dictionary <string, Subgraph> subgraphs = new Dictionary <string, Subgraph>(); //Using a stack traversing strategy Stack <GraphEntity> stack = new Stack <GraphEntity>(); //Put the first element on to the stack manually stack.Push(ge); //Iterate the stack until no connected nodes left while (stack.Count > 0) { //Fetch the topmost entity on stack GraphEntity current = stack.Pop(); //Determine the subgraph it is part of string alName = psmPipeline.ReadPropertyString(current.Owner, driPipelineData.BelongsToAlignment); //Fetch or create new subgraph object Subgraph subgraph; if (subgraphs.ContainsKey(alName)) { subgraph = subgraphs[alName]; } else { subgraph = new Subgraph(dB, ComponentTable, alName); subgraphs.Add(alName, subgraph); } subgraph.Nodes.Add(current.OwnerHandle); if (isEntryPoint) { subgraph.isEntryPoint = isEntryPoint; isEntryPoint = false; } //Iterate over current node's children foreach (Con con in current.Cons) { //Find the child the con is referencing to GraphEntity child = GraphEntities.Where(x => x.OwnerHandle == con.ConHandle).FirstOrDefault(); //if it is the con refering back to the parent -> skip it if (child == default || child.OwnerHandle == current.OwnerHandle) { continue; } //Also skip if child has already been visited //This prevents from making circular graphs I think //Comment next line out to test circular graphs //if (visitedHandles.Contains(child.OwnerHandle)) continue; <-- First solution //Solution with caching of previous handle, it I don't think it works when backtracking to a branch -> there will be a double arrow if (previousHandle != null && previousHandle == child.OwnerHandle) { continue; } //Try to control which cons get written by their type //Build string string ownEnd = con.OwnEndType.ToString(); string conEnd = con.ConEndType.ToString(); string key = ownEnd + "-" + conEnd; if (!allowedCombinations[key]) { continue; } //Tries to prevent duplicate Main-Main edges by eliminating upstream Main-Main instance //Doesn't work if recursion just returned from a branch, because previous is set the the //Last node on the branch if (key == "Main-Main" && con.ConHandle == previousHandle) { continue; } //Record the edge between nodes edges.Add(new Edge(current.OwnerHandle, child.OwnerHandle)); //edges.Add(new Edge(current.OwnerHandle, child.OwnerHandle, key)); //If this child node is in visited collection -> skip, so we don't ger circular referencing if (visitedHandles.Contains(child.OwnerHandle)) { continue; } //If the node has not been visited yet, then put it on the stack stack.Push(child); } //When current iteration completes, put the current node handle in the visited collection visitedHandles.Add(current.OwnerHandle); //Cache current node handle to avoid backreference previousHandle = current.OwnerHandle; } //Write collected data //Stringbuilder to contain the overall file //This must be refactored when working with disjoined networks StringBuilder sb = new StringBuilder(); sb.AppendLine($"subgraph G_{graphCount} {{"); //First line of file stating a graph //Set the shape of the nodes for whole graph sb.AppendLine("node [shape=record];"); //Write edges foreach (Edge edge in edges) { sb.AppendLine(edge.ToString("->")); } //Write subgraphs int i = 0; foreach (var sg in subgraphs) { i++; sb.Append(sg.Value.WriteSubgraph(i)); } //Add closing curly brace correspoinding to the first line sb.AppendLine("}"); //Append current disjoined graph to all collector sbAll.Append(sb.ToString()); //using (System.IO.StreamWriter file = new System.IO.StreamWriter($"C:\\Temp\\MyGraph_{graphCount}.dot")) //{ // file.WriteLine(sb.ToString()); // "sb" is the StringBuilder //} //Modify the GraphEntities to remove visited entities GraphEntities = GraphEntities.ExceptWhere(x => visitedHandles.Contains(x.OwnerHandle)).ToHashSet(); } //Closing brace of the main graph sbAll.AppendLine("}"); //Check or create directory if (!Directory.Exists(@"C:\Temp\")) { Directory.CreateDirectory(@"C:\Temp\"); } //Write the collected graphs to one file using (System.IO.StreamWriter file = new System.IO.StreamWriter($"C:\\Temp\\MyGraph.dot")) { file.WriteLine(sbAll.ToString()); // "sb" is the StringBuilder } }