Revit API (C#) Add-In to Analyze DHW Systems for Critical Heat Loss Path and Required Hot Water Return Flow Rate

Revit API (C#) Add-In to Analyze DHW Systems for Critical Heat Loss Path and Required Hot Water Return Flow Rate

Derek_Eubanks
Explorer Explorer
106 Views
0 Replies
Message 1 of 1

Revit API (C#) Add-In to Analyze DHW Systems for Critical Heat Loss Path and Required Hot Water Return Flow Rate

Derek_Eubanks
Explorer
Explorer

LinkedIn: https://www.linkedin.com/pulse/revit-api-c-add-in-traversing-dhw-piping-networks-hot-derek...

 

This post explains the engineering logic and implementation details behind a Revit API C# add-in that calculates the required return flow rate in a domestic hot water (DHW) system using thermodynamic principles. The add-in analyzes each pipe segment for heat loss through insulation and identifies the critical run (the branch with the highest cumulative thermal loss). The output is a calculated GPM return rate based on the worst-case thermal load.

This approach eliminates guesswork (e.g., HWR flow rate and pipe size assumptions), supports energy efficiency, and aligns with decarbonization goals.

### Assumptions and Setup Requirements

This add-in analyzes only the portion of the DHW piping system that is physically connected to the pipe element selected in Revit when the add-in is run. It does not require the entire system to be selected manually. Instead, the add-in starts from the selected pipe (typically the outlet from the water heater) and uses the Revit API to automatically traverse all directly connected downstream pipe segments.

This ensures that only the active connected system is analyzed—ideal for subsystem evaluations, tenant improvements, or zone-specific modeling.

* Select a single pipe (or multiple pipes) that belong to the hot water system immediately downstream of the water heater.

* Ensure that all connected downstream pipes are part of the intended analysis network.

The tool will recursively traverse only the pipes connected to the selected starting element(s) and ignore all other unrelated piping systems in the model. This makes it suitable for focused subsystem analysis without requiring full model scanning.

For this add-in to work correctly and return accurate results, the following modeling and system setup requirements must be met within Revit:

1. All Plumbing Fixture Families Must Be Properly Connected

* Each fixture must be physically connected to the domestic hot water piping system.

* The fixture family connectors must be correctly assigned and associated with the hot water system.

2. Fixture Units (FUs) Must Be Correctly Defined and Communicated

* Each fixture connector must include the correct FU value (e.g., per IPC or ASPE lookup).

* These values are required by Revit’s internal MEP system engine to compute flow rates.

3. Revit’s Built-In FU-to-Flow Conversion Must Be Enabled

* Revit uses internal rules (or optionally a custom PlumbingFixtureFlowServer) to convert FUs to GPM.

* Flow values must propagate into the piping system from fixtures to allow sizing.

4. Revit’s Auto Sizing Tool Should Be Run

* Once the pipe network has valid flow rates, Revit’s native auto sizing feature must be used.

* Pipe diameters will then be updated based on flow and velocity constraints.

5. Pipe Geometry Must Be Correct

* Accurate pipe lengths and diameters are essential to produce valid heat loss calculations.

When these conditions are satisfied, the add-in can traverse the system and analyze it as intended.

---

### Step 1: Segment-Level Heat Loss Calculation

Each hot water pipe segment is modeled as a cylindrical geometry wrapped in insulation. The steady-state heat loss is calculated using the radial conduction formula:

Formula:

Q\_loss = (2 \* PI \* L \* k\_eff \* (T\_hot - T\_ambient)) / ln(r2 / r1)

Where:

* Q\_loss = heat loss per pipe segment (BTU/hr)

* L = pipe length (ft)

* T\_hot = hot water temperature (°F)

* T\_ambient = ambient air temperature (°F)

* r1 = inner radius (pipe OD) (ft)

* r2 = outer radius (pipe + insulation) (ft)

* k\_eff = effective thermal conductivity (BTU/hr-ft-°F)

To derive k\_eff from insulation R-value:

**k\_eff = ln(r2 / r1) / (2 \* PI \* R)**

Where R = insulation thermal resistance (hr-ft²-°F/BTU).

---

### Step 2: Traversing the Entire DHW Piping System (Topological Graph Traversal)

The Revit piping network is modeled as a graph where each pipe is a node, and connections between pipes (through fittings, elbows, or direct connectors) form the edges. This add-in dynamically builds a graph representation of the hot water piping system directly from the model geometry using the Revit API.

The code executes the following key steps:

#### 2.1: Loop Through All Hot Water Pipes

Using a FilteredElementCollector, the tool gathers all elements of class Pipe where the PipeSystemType is HotWaterDomestic. Each pipe's diameter and length are read directly from the element geometry.

#### 2.2: Build a Heat Loss Map and Connectivity Graph

For each pipe segment:

* The heat loss (`Q_loss`) is calculated using the cylindrical conduction formula.

* This loss is stored in a dictionary: pipeHeatLosses[pipe.Id] = Q_loss.

Then, the pipe’s physical connections are explored using ConnectorManager:

* Each Pipe has connectors on both ends.

* Each connector exposes AllRefs, which lists all other connectors it is physically connected to.

* If a referenced connector belongs to another Pipe, that pipe is added to the current pipe’s connectivity list. This results in a dictionary:

Dictionary<ElementId, List<ElementId>> pipeConnectivity; Which represents the piping system as a graph in memory.

#### 2.3: Recursively Traverse the Graph

To find the critical run, a recursive depth-first search (DFS) is used. Starting from every pipe in the system:

* A traversal is launched that visits all connected pipes (nodes), tracking visited nodes to avoid cycles.

* At each step, the current pipe’s Q_loss is added to the path’s cumulative total.

* If a path ends (no more connected unvisited pipes), the total accumulated heat loss is recorded.

This process is repeated from every pipe in the system, ensuring that every possible path is evaluated. The code keeps track of the maximum cumulative heat loss encountered across all paths.

double totalLoss = TraversePath(pipe.Id, pipeConnectivity, pipeHeatLosses, visited, path);

#### 2.4: Identify the Critical Run

At the end of traversal, the pipe path with the highest Q_loss total is identified as the critical thermal run. This path represents the most demanding condition for hot water return temperature maintenance.

No changes are made to the model. This step is purely analytical and is designed to support high-accuracy performance calculations based on actual system layout and sizing.

---

### Step 3: Calculate Required Return Flow Rate

Once the maximum cumulative heat loss, Q\_MaxTotal, is identified, the required return flow is calculated using the energy balance:

Formula 1: Mass Flow Rate

m\_dot = Q\_MaxTotal / (cp \* DeltaT)

Formula 2: Volumetric Flow Rate

GPM = m\_dot / (density \* 60)

Where:

* Q\_MaxTotal = total heat loss on critical run (BTU/hr)

* cp = specific heat of water (1.0 BTU/lb-°F)

* DeltaT = allowable temp drop (e.g., 10°F)

* density = 8.34 lb/gal (for water)

* 60 = minutes per hour

The result is the minimum required hot water return (HWR) flow rate in GPM to maintain system temperature over the longest/highest-loss path.

---

### Summary

This tool provides:

* A rigorously accurate, thermodynamics-based return flow calculation

* Full Revit API integration using physical pipe connectivity

* Revit-native scalability across small and large DHW systems

With this approach, plumbing engineers can now perform automated thermal analysis directly in Revit, replacing approximations with science-backed logic.

 

// Revit ExternalCommand: Hot Water Critical Heat Path + Return Flow Analyzer
// Author: Derek Eubanks, PE, HFDP, CHD, HBDP
//
// Purpose:
// Starting from a user-selected domestic hot water pipe, this Revit add-in traverses all physically connected downstream pipes,
// calculates the total radial heat loss from pipe segments based on insulation and geometry,
// identifies the path with the highest cumulative loss (the critical run),
// and calculates the minimum required return flow rate (GPM) needed to offset that loss across a defined ΔT.
// This approach does not require pipe flow parameters — it calculates flow rate *from* heat loss using thermodynamic principles.

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 RevitHotWaterReturnFlow
{
    [Transaction(TransactionMode.Manual)]
    public class CalculateHotWaterReturn_GPMfromCriticalLoss : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            Document doc = uidoc.Document;

            // Step 1: User must select exactly one pipe to serve as the starting point
            var selectedIds = uidoc.Selection.GetElementIds();
            if (selectedIds.Count != 1)
            {
                TaskDialog.Show("Error", "Please select a single domestic hot water pipe to analyze.");
                return Result.Failed;
            }

            Pipe startPipe = doc.GetElement(selectedIds.First()) as Pipe;
            if (startPipe == null || startPipe.PipeSystemType != PipeSystemType.HotWaterDomestic)
            {
                TaskDialog.Show("Error", "Selected element must be a Hot Water Domestic pipe.");
                return Result.Failed;
            }

            // Step 2: Constants — could be replaced with user input via WPF
            double supplyTempF = 120;
            double ambientTempF = 75;
            double insulationThicknessIn = 1.0;
            double insulationRValue = 4.0;
            double allowableDeltaT = 10;

            // Step 3: Find all DHW pipes in model for reference
            var allDHWpipes = new FilteredElementCollector(doc)
                .OfClass(typeof(Pipe))
                .Cast<Pipe>()
                .Where(p => p.PipeSystemType == PipeSystemType.HotWaterDomestic)
                .ToDictionary(p => p.Id);

            // Graph structures for traversal
            Dictionary<ElementId, List<ElementId>> pipeGraph = new();
            Dictionary<ElementId, double> pipeLosses = new();
            HashSet<ElementId> visited = new();
            Queue<ElementId> queue = new();
            queue.Enqueue(startPipe.Id);

            // Step 4: Traverse connected system using Revit ConnectorManager (graph-style traversal)
            while (queue.Count > 0)
            {
                var currentId = queue.Dequeue();
                if (!visited.Add(currentId)) continue;
                if (!allDHWpipes.TryGetValue(currentId, out Pipe pipe)) continue;

                double diameterIn = pipe.Diameter * 12.0;
                double lengthFt = (pipe.Location as LocationCurve)?.Curve.Length ?? 0.0;

                double qLoss = HeatLossCalculator.CalculatePipeSegmentHeatLoss(
                    diameterIn, insulationThicknessIn, insulationRValue,
                    lengthFt, supplyTempF, ambientTempF);

                pipeLosses[currentId] = qLoss;

                var connected = new List<ElementId>();
                foreach (Connector conn in pipe.ConnectorManager.Connectors)
                {
                    foreach (Connector refConn in conn.AllRefs)
                    {
                        if (refConn.Owner is Pipe neighbor && neighbor.Id != pipe.Id)
                        {
                            connected.Add(neighbor.Id);
                            if (!visited.Contains(neighbor.Id))
                                queue.Enqueue(neighbor.Id);
                        }
                    }
                }
                pipeGraph[currentId] = connected.Distinct().ToList();
            }

            // Step 5: Recursively evaluate all possible paths to find the critical heat loss path
            double maxTotalLoss = 0;
            foreach (var pipeId in pipeLosses.Keys)
            {
                var subVisited = new HashSet<ElementId>();
                var path = new List<ElementId>();
                double loss = Traverse(pipeId, pipeGraph, pipeLosses, subVisited, path);
                if (loss > maxTotalLoss)
                    maxTotalLoss = loss;
            }

            // Step 6: Solve for required return GPM based on total BTU/hr loss and ΔT
            double cp = 1.0;         // BTU/lb·°F
            double rho = 8.34;       // lb/gal
            double mDot = maxTotalLoss / (cp * allowableDeltaT); // lb/hr
            double gpm = mDot / (rho * 60);                      // gal/min

            TaskDialog.Show("Hot Water Return Flow",
                $"Max cumulative heat loss: {maxTotalLoss:F1} BTU/hr\nRequired return flow: {gpm:F2} GPM");

            return Result.Succeeded;
        }

        // Recursive DFS to sum total loss down each path from given node
        private double Traverse(ElementId currentId,
            Dictionary<ElementId, List<ElementId>> graph,
            Dictionary<ElementId, double> losses,
            HashSet<ElementId> visited,
            List<ElementId> path)
        {
            if (visited.Contains(currentId)) return 0;
            visited.Add(currentId);
            path.Add(currentId);

            double localLoss = losses.GetValueOrDefault(currentId, 0);
            double maxBranchLoss = 0;

            if (graph.TryGetValue(currentId, out var neighbors))
            {
                foreach (var next in neighbors)
                {
                    var subVisited = new HashSet<ElementId>(visited);
                    var subPath = new List<ElementId>();
                    double branchLoss = Traverse(next, graph, losses, subVisited, subPath);
                    if (branchLoss > maxBranchLoss)
                        maxBranchLoss = branchLoss;
                }
            }

            return localLoss + maxBranchLoss;
        }
    }

    public static class HeatLossCalculator
    {
        // Calculates radial heat loss (Q) for cylindrical insulated pipe segment
        public static double CalculatePipeSegmentHeatLoss(
            double diameterIn,
            double insulationIn,
            double rValue,
            double lengthFt,
            double tempSupply,
            double tempAmbient)
        {
            double r1 = (diameterIn / 2.0) / 12.0; // Convert in → ft
            double r2 = ((diameterIn + 2 * insulationIn) / 2.0) / 12.0; // ft

            if (r2 <= r1 || rValue <= 0) return 0;

            double kEff = Math.Log(r2 / r1) / (2 * Math.PI * rValue);
            return (2 * Math.PI * lengthFt * kEff * (tempSupply - tempAmbient)) / Math.Log(r2 / r1);
        }
    }
}

 

0 Likes
107 Views
0 Replies
Replies (0)