Revit API C# Add-in for Sanitary Pipe Sizing Using IPC Tables and System Traversal via ConnectionManager (Beta Release)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Linkedin Post: https://www.linkedin.com/pulse/code-compliant-dfu-driven-sanitary-pipe-sizing-revit-derek-d3gqe
Still working on GitHub repository. I have the .cs source code file(C# code below) and .csproj file with updated references. I also made a .addin manifest file.
I welcome any input for this addin (beta) from other MEP or BIM developers.
🚀 Features:
• Sizes sanitary pipes per IPC Tables 703.2, 710.1(1), 710.1(2)
• Detects vertical stacks using ConnectorManager
• Prevents illegal size reductions (§710.1.8)
• Respects slope requirements (§704.1)
• Works directly in Revit using C# and Revit API
🔧 Introduction
Revit MEP provides basic tools for modeling plumbing systems, but it lacks robust, code-driven automation for sanitary pipe sizing. It does not apply the full set of requirements from the International Plumbing Code (IPC), which every engineer must follow to ensure legal, safe, and buildable drainage systems.
To solve this, I developed a custom Revit add-in using C# that automatically sizes selected sanitary piping based on:
• The accumulated fixture units (DFUs) in each pipe
• The slope of the pipe segment
• The pipe orientation (vertical stack vs. horizontal drain)
• The correct IPC tables for pipe type
• Flow direction restrictions (no diameter reductions)
• Minimum slope requirements
• Branch line DFU limits per IPC Table 703.2
• Intelligent stack detection using Revit’s ConnectorManager traversal
This tool works directly in Revit, updates pipe diameters in the model, and integrates with my other Revit tool that calculates flow rate (GPM) from DFUs using IPC/ASPE tables.
🤩 How the Add-in Works – Full Code Logic Explained
The logic is executed inside a Revit ExternalCommand, allowing it to run from the Add-Ins tab on selected pipe elements.
✅ Step 1: Select and Filter Sanitary Pipes
The add-in begins by identifying all user-selected pipes that are part of the “Sanitary” system type. This ensures the tool only affects relevant drainage systems and avoids domestic, vent, or storm systems.
✅ Step 2: Sort Pipes by Elevation (Top to Bottom)
Pipes are sorted in descending order by elevation (Z-value), so upstream pipes are processed first. This is critical for enforcing IPC §710.1.8, which prohibits pipe size reductions in the direction of flow. As the add-in moves downstream, it can compare to the largest upstream diameter and prevent regressions.
✅ Step 3: Retrieve DFUs, Slope, and Flow (Optional)
For each pipe, the code retrieves:
• DFU (from Revit’s built-in parameter RBS_DRAIN_FIXTURE_UNITS_PARAM)
• Slope (from RBS_PIPE_SLOPE_PARAM, in ft/ft)
• Optionally, flow (GPM) if available via a UserMEP server
The flow is not used in the current version, as the IPC dictates sizing for gravity systems based on DFUs, not GPM.
✅ Step 4: Determine Pipe Type Using Connector Traversal
To improve accuracy beyond just geometry, the add-in uses Revit's ConnectorManager to inspect all pipes connected to the current pipe. It evaluates the spatial direction of connections:
- If a connected pipe shows significantly greater change in elevation (Z-direction) than in horizontal dimensions (X/Y), it is identified as a vertical stack.
- Otherwise, the pipe is assumed to be horizontal (i.e., a horizontal drain or branch).
This approach is powered by ConnectorManager, which exposes each pipe's connectors and their connected references (AllRefs). By comparing connector positions and direction vectors, the add-in analyzes the geometry of connections and determines pipe orientation more reliably than angle-based checks alone. It enables tracing across fittings and pipe segments to identify true stack behavior based on connected system layout — even across offsets or multi-element risers.
📘 IPC Code Tables Used
• IPC Table 710.1(1) – Vertical stack DFU limits
• IPC Table 710.1(2) – Horizontal drain DFU limits
• IPC Table 703.2 – Horizontal branch DFU limits
• IPC §704.1 – Minimum allowable slope by diameter
• IPC §710.1.8 – No pipe size reductions in flow direction
✅ Step 5: Apply Vertical Pipe Sizing – IPC Table 710.1(1)
If the pipe is flagged as a vertical stack, the code compares its DFU value to IPC Table 710.1(1) and assigns a pipe size that meets or exceeds the required capacity.
For example:
• 3” stacks can carry up to 12 DFUs
• 4” up to 42 DFUs
• 6” up to 120 DFUs, and so on
✅ Step 6: Apply Horizontal Pipe Sizing – IPC Tables 703.2 and 710.1(2)
If the pipe is horizontal, the code enforces multiple layers of logic:
1. If slope is less than 1/8” per foot (0.0104 ft/ft), the diameter is set to a minimum 4” (IPC §704.1).
2. The pipe’s DFU count is compared to IPC Table 710.1(2) to determine minimum size.
3. The DFU is also checked against IPC Table 703.2 to ensure branch limits are not exceeded.
Examples:
• 1.5” pipe → max 3 DFUs (703.2)
• 2” pipe @ 1/4”/ft → max 6 DFUs
• 3” pipe @ 1/8”/ft → max 12 DFUs
• 4” pipe @ 1/8”/ft → max 26 DFUs
The more conservative requirement between the two tables is enforced.
✅ Step 7: Prevent Size Reductions – IPC §710.1.8
The code maintains a variable that tracks the largest upstream pipe diameter seen so far. For each downstream pipe, if the computed size is smaller, it is overridden to match or exceed the upstream diameter.
This prevents illegal pipe size reductions per IPC §710.1.8 and ensures flow performance is maintained.
✅ Step 8: Apply the Pipe Diameter to the Revit Model
Once the diameter is finalized, the add-in uses RBS_PIPE_DIAMETER_PARAM to update the pipe element directly in the model. These updates:
- Instantly reflect in the Revit UI
- Update tags and schedules
- Can be undone due to the enclosing Transaction
🧠 Summary – What This Add-in Accomplishes
This Revit C# add-in performs full sanitary pipe sizing per IPC and ASPE standards. Specifically, it:
• Identifies vertical vs. horizontal pipe based on connector-based spatial analysis
• Applies vertical stack sizing per Table 710.1(1)
• Applies horizontal drain sizing per Table 710.1(2)
• Validates branch pipes using DFU limits in Table 703.2
• Enforces slope requirements from §704.1
• Prevents size reductions in the direction of flow per §710.1.8
• Sizes pipes safely and automatically in Revit based on live model data
It can be used on any selected pipe segments and works well in combination with my companion tool for DFU-to-flow conversion using ASPE-based logic.
// (C) Revit ExternalCommand to Size Sanitary Piping per IPC & ASPE Standards
// Includes IPC Table 703.2 Branch DFU Limits and Stack Detection via System Traversal
// Author: Derek Eubanks, PE, HFDP, CHD, HBDP | GT OMSCS Candidate – Machine Learning
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Plumbing;
using Autodesk.Revit.UI;
namespace SanitaryPipeSizing
{
[Transaction(TransactionMode.Manual)]
public class SizeSanitaryPipes : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIDocument uidoc = commandData.Application.ActiveUIDocument;
Document doc = uidoc.Document;
var selection = uidoc.Selection.GetElementIds()
.Select(id => doc.GetElement(id))
.OfType<Pipe>()
.Where(p => p.PipeSystemType == PipeSystemType.Sanitary)
.ToList();
double maxUpstreamDiameter = 0.0;
using (Transaction tx = new Transaction(doc, "Size Sanitary Pipes per IPC/ASPE"))
{
tx.Start();
foreach (var pipe in selection.OrderByDescending(p => GetPipeZElevation(p)))
{
double dfu = pipe.get_Parameter(BuiltInParameter.RBS_DRAIN_FIXTURE_UNITS_PARAM)?.AsDouble() ?? 0;
double flow = pipe.get_Parameter(BuiltInParameter.RBS_PIPE_FLOW_PARAM)?.AsDouble() * 448.831 ?? 0;
if (dfu < 0.01 && flow < 0.01) continue;
bool isVertical = IsPipeVerticalStack(pipe);
double slope = pipe.get_Parameter(BuiltInParameter.RBS_PIPE_SLOPE_PARAM)?.AsDouble() ?? 0.0104;
double requiredDiameterIn = isVertical
? SizeVerticalStack(dfu)
: SizeHorizontalDrainWithLimits(dfu, slope);
if (!isVertical)
requiredDiameterIn = ApplyBranchDFULimits(dfu, requiredDiameterIn);
double diameterFt = Math.Max(requiredDiameterIn / 12.0, maxUpstreamDiameter);
var diaParam = pipe.get_Parameter(BuiltInParameter.RBS_PIPE_DIAMETER_PARAM);
if (diaParam != null && diaParam.StorageType == StorageType.Double)
diaParam.Set(diameterFt);
maxUpstreamDiameter = Math.Max(maxUpstreamDiameter, diameterFt);
}
tx.Commit();
}
return Result.Succeeded;
}
private double GetPipeZElevation(Pipe pipe)
{
LocationCurve curve = pipe.Location as LocationCurve;
if (curve == null || curve.Curve == null) return 0.0;
return Math.Max(curve.Curve.GetEndPoint(0).Z, curve.Curve.GetEndPoint(1).Z);
}
private bool IsPipeVerticalStack(Pipe pipe)
{
ConnectorManager cm = pipe.ConnectorManager;
foreach (Connector conn in cm.Connectors)
{
foreach (Connector refConn in conn.AllRefs)
{
if (refConn.Owner is Pipe connectedPipe &&connectedPipe.Id != pipe.Id)
{
double dz = Math.Abs(connectedPipe.get_EndPoint(0).Z - connectedPipe.get_EndPoint(1).Z);
double dx = Math.Abs(connectedPipe.get_EndPoint(0).X - connectedPipe.get_EndPoint(1).X);
double dy = Math.Abs(connectedPipe.get_EndPoint(0).Y - connectedPipe.get_EndPoint(1).Y);
if (dz > dx && dz > dy)
return true;
}
}
}
return false;
}
private double SizeHorizontalDrainWithLimits(double dfu, double slope)
{
if (slope < 0.0104) return 4;
if (slope >= 0.0208 && dfu <= 3) return 1.5;
if (slope >= 0.0104)
{
if (dfu <= 4) return 2;
if (dfu <= 6) return 2.5;
if (dfu <= 12) return 3;
if (dfu <= 26) return 4;
if (dfu <= 50) return 5;
if (dfu <= 75) return 6;
if (dfu <= 150) return 8;
if (dfu <= 216) return 10;
if (dfu <= 300) return 12;
if (dfu <= 575) return 15;
}
return 4;
}
private double SizeVerticalStack(double dfu)
{
if (dfu <= 2) return 1.5;
if (dfu <= 4) return 2;
if (dfu <= 6) return 2.5;
if (dfu <= 12) return 3;
if (dfu <= 42) return 4;
if (dfu <= 72) return 5;
if (dfu <= 120) return 6;
if (dfu <= 250) return 8;
if (dfu <= 500) return 10;
if (dfu <= 840) return 12;
return 15;
}
private double ApplyBranchDFULimits(double dfu, double initialSizeIn)
{
var branchLimits = new Dictionary<double, double>
{
{ 1.5, 3 },
{ 2.0, 6 },
{ 2.5, 9 },
{ 3.0, 20 },
{ 4.0, 160 },
{ 5.0, 360 },
{ 6.0, 620 },
{ 8.0, 1400 },
{ 10.0, 2500 },
{ 12.0, 3900 },
{ 15.0, 7000 }
};
foreach (var kvp in branchLimits.OrderBy(k => k.Key))
{
if (dfu <= kvp.Value && kvp.Key >= initialSizeIn)
return kvp.Key;
}
return initialSizeIn;
}
}
public static class PipeExtensions
{
public static XYZ get_EndPoint(this Pipe pipe, int index)
{
LocationCurve curve = pipe.Location as LocationCurve;
return (curve != null) ? curve.Curve.GetEndPoint(index) :XYZ.Zero;
}
}
}