I'm trying to automatically route pipes using the Revit API. My goal is to enable the plumbing engineers in my office to simply specify the location of a receptor and a few relevant endpoints (e.g a toilet and a shower), and for my plugin to route suitable pipes from the endpoints to the receptor.
I've read that routing can be an NP-Hard problem, but my hope is that it'll be possible to use certain heuristics to come up with a reasonable solution, in particular when the number of endpoints is relatively low.
I've started developing a POC just to see if I can connect two parallel pipes with an intermediate pipe using the Revit API, but this is proving to be somewhat difficult.
In particular, I'm getting the error:
Fitting cannot be created between the input connectors because the angle between them is too small or too large.
Upon printing the angles between the connectors, it seems like they are a combination of 45 and 135 (so 135 is what's triggering the error). 135 is 180-45, so I assume that there's an issue of directionality? But I'm not sure how to specify the direction of a pipe, if that is even possible. Oh, and worth noting that apparently wye connectors (which is what I actually need) are simply a subset of t connectors, so apparently I'm calling the right method.
Here's my code. Any insights would be greatly appreciated:
using (Transaction t = new Transaction(doc, "create pipes"))
{
t.Start();
XYZ location1_end = new XYZ(location1_start.X, location1_start.Y + 10 * zOffset, location1_start.Z);
Pipe pipe1 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location1_start, location1_end);
// Insert second pipe parallel to the first, beneath it
XYZ location2_start = new XYZ(location1_start.X, location1_start.Y, location1_start.Z - zOffset);
XYZ location2_end = new XYZ(location1_start.X, location1_start.Y + 10 * zOffset, location1_start.Z - zOffset);
Pipe pipe2 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location2_start, location2_end);
// pipe3 starts from someplace in the middle of pipe 1, and reaches someplace in the middle of pipe 2.
XYZ location3_start = new XYZ(location1_start.X, location1_start.Y + 10 , location1_start.Z);
XYZ location3_end = new XYZ(location1_start.X, location1_start.Y + 15, location1_start.Z - zOffset);
Pipe pipe3 = Pipe.Create(doc, systemType.Id, pipeType.Id, level.Id, location3_start, location3_end);
// By default there are two connectors for a pipe - one at the starting point, and one at the end.
ConnectorManager cm1 = pipe1.ConnectorManager;
ConnectorManager cm2 = pipe2.ConnectorManager;
ConnectorManager cm3 = pipe3.ConnectorManager;
Connector pipe1Connector = cm1.Connectors.Cast<Connector>().FirstOrDefault();
Connector pipe2Connector = cm2.Connectors.Cast<Connector>().FirstOrDefault();
// Where the third pipe begins (somewhere in the middle of pipe 1)
Connector thirdPipeConnector1 = cm3.Connectors.Cast<Connector>().FirstOrDefault();
// Where the third pipe ends (somewhere in the middle of pipe 2)
Connector thirdPipeConnector2 = cm3.Connectors.Cast<Connector>().LastOrDefault();
// From this point on, I'm not sure if the code is right. Here, I tried splitting pipes 1 and 2 at the locations where pipe 3 started and ended, so
// that new connectors could then be defined at those points, and those connectors could be used to connect to pipe 3.
ElementId pipe1Segment2Id = PlumbingUtils.BreakCurve(doc, pipe1.Id, location3_start);
ElementId pipe2Segment2Id = PlumbingUtils.BreakCurve(doc, pipe2.Id, location3_end);
Pipe pipe1Segment1 = doc.GetElement(pipe1.Id) as Pipe;
Pipe pipe1Segment2 = doc.GetElement(pipe1Segment2Id) as Pipe;
Pipe pipe2Segment1 = doc.GetElement(pipe2.Id) as Pipe;
Pipe pipe2Segment2 = doc.GetElement(pipe2Segment2Id) as Pipe;
// The connector right before the split of pipe 1?
Connector pipe1SplitConnector1 = pipe1Segment1.ConnectorManager.Connectors.Cast<Connector>().LastOrDefault(); // End of first segment
// The connector right after the split of pipe 1?
Connector pipe1SplitConnector2 = pipe1Segment2.ConnectorManager.Connectors.Cast<Connector>().FirstOrDefault(); // Start of second segment
Connector pipe2SplitConnector1 = pipe2Segment1.ConnectorManager.Connectors.Cast<Connector>().LastOrDefault(); // End of first segment
Connector pipe2SplitConnector2 = pipe2Segment2.ConnectorManager.Connectors.Cast<Connector>().FirstOrDefault(); // Start of second segment
// This was to help in debugging.
double ang1_3 = CalculateAngleBetweenConnectors(pipe1SplitConnector1, thirdPipeConnector1);
double ang1_end_3 = CalculateAngleBetweenConnectors(pipe1SplitConnector2, thirdPipeConnector1);
double ang2_3 = CalculateAngleBetweenConnectors(pipe2SplitConnector1, thirdPipeConnector2);
double ang2_end_3 = CalculateAngleBetweenConnectors(pipe2SplitConnector2, thirdPipeConnector2);
TaskDialog.Show("Angles", $"angle between pipe 1 and pipe 3: {ang1_3}, {ang1_end_3}, angle between pipe 2 and pipe 3: {ang2_3},{double ang2_end_3}");
// The errors are triggered by either of the following two lines.
//doc.Create.NewTeeFitting(pipe1SplitConnector1, thirdPipeConnector1, pipe1SplitConnector2);
//doc.Create.NewTeeFitting(pipe2SplitConnector1, thirdPipeConnector2, pipe2SplitConnector2);
t.Commit();
}
This makes sense, and such a task is realistic. To start with, you can take a look at the official Revit SDK MEP samples:
AutoRoute does exactly what you want, but for ducts: This sample demonstrates how to route a set of ducts and fittings between a base air supply equipment and 2 terminals.
Next, you can take a look at my research and series of solutions to create a rolling offset; that works with pipes.
Cf. also the thread with an identical question in the Revit API discussion forum, on connecting pipes to each other using the revit api.