Application: AvoidObstruction
Revit Platform: MEP
Revit Version: 2011.0

First Released For: 2010.0
Programming Language: C#
Skill Level: Medium

Category: MEP
Type: ExternalCommand

主题:障碍物检测并解决。

摘要:此示例将演示如何检测和解决“带管道的管道”、“带梁的管道”或“带风管的管道”的障碍物。

相关类:

Autodesk.Revit.UI.IExternalCommand

Autodesk.Revit.DB.Document

Autodesk.Revit.Creation.Document

Autodesk.Revit.DB.Plumbing.Pipe

Autodesk.Revit.DB.Mechanical.Duct

项目文件

Command.cs此文件包含一个类Command,它实现Revit API的接口IExternalCommand。

Detector.cs此文件包含一个类Detector,用于检测线路或射线的障碍物。

Resolver.cs该文件包含一个类Resolver,它实现了检测障碍物并解决障碍物的算法。

Section.cs此文件包含一个类Section,它表示管道的障碍物。

功能:

此示例提供以下功能。

-它将检测并解决管道堵塞问题。

-只需检测“管道与管道”、“管道与梁”或“管道与风管”的障碍物。其他如“带墙管道”(楼梯、楼板等)将被忽略。

-我们只使用管道的中心线来检测障碍物,而不考虑管道的半径。

-只需重新布线管道,不要更改其他管道(梁、风管)。

-如何解决障碍:

保持管道的原始方向不变。

根据检测到的障碍物将管道线拆分为一些线段。

重新布置管道以避开障碍物。

限制:

如果所有图元(管道、风管和梁)都位于同一平面(不需要是水平或垂直的),则此示例可以正常工作,而在其他情况下可能无法正常工作。

为了简化问题,只需使用管道中心线来检测障碍物,在以下情况下,使用管道中心线上检测障碍物将失败:

实施:

1.打开Revit MEP应用程序,然后打开位于示例目录中的项目文件“AvoidObstruction.rvt”。然后执行命令。

2.运行此命令后,结果将反映到当前文件中。

完整的源代码请加入QQ群649037449,在群文件中下载RevitSDK.exe,解压后在文件夹中搜索本文中应用程序名称即可获得完整源码

Detector.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//


using System;
using System.Collections.Generic;
using System.Text;
using Autodesk.Revit.DB;
using Autodesk.Revit;
using Element = Autodesk.Revit.DB.Element;

namespace Revit.SDK.Samples.AvoidObstruction.CS
{
    /// <summary>
    /// This class is used to detect the obstructions of a Line or a ray.
    /// </summary>
    class Detector
    {
        /// <summary>
        /// Revit Document.
        /// </summary>
        private Document m_rvtDoc;

        /// <summary>
        /// Revit 3D view.
        /// </summary>
        private View3D m_view3d;

        /// <summary>
        /// Constructor, initialize all the fields.
        /// </summary>
        /// <param name="rvtDoc">Revit Document</param>
        public Detector(Document rvtDoc)
        {
            m_rvtDoc = rvtDoc;
            FilteredElementCollector collector = new FilteredElementCollector(m_rvtDoc);
            FilteredElementIterator iter = collector.OfClass(typeof(View3D)).GetElementIterator();
            iter.Reset();
            while (iter.MoveNext())
            {
                m_view3d = iter.Current as View3D;
                if (null != m_view3d && !m_view3d.IsTemplate)
                    break;
            }
        }

        /// <summary>
        /// Return all the obstructions which intersect with a ray given by an origin and a direction.
        /// </summary>
        /// <param name="origin">Ray's origin</param>
        /// <param name="dir">Ray's direction</param>
        /// <returns>Obstructions intersected with the given ray</returns>
        public List<ReferenceWithContext> Obstructions(Autodesk.Revit.DB.XYZ origin, Autodesk.Revit.DB.XYZ dir)
        {
            List<ReferenceWithContext> result = new List<ReferenceWithContext>();
            ReferenceIntersector referenceIntersector = new ReferenceIntersector(m_view3d);
            referenceIntersector.TargetType = FindReferenceTarget.Face;
            IList<ReferenceWithContext> obstructionsOnUnboundLine = referenceIntersector.Find(origin, dir);
            foreach (ReferenceWithContext gRef in obstructionsOnUnboundLine)
            {
                if (!InArray(result, gRef))
                {
                    result.Add(gRef);
                }
            }

            result.Sort(CompareReferencesWithContext);
            return result;
        }

        /// <summary>
        /// Return all the obstructions which intersect with a bound line.
        /// </summary>
        /// <param name="boundLine">Bound line</param>
        /// <returns>Obstructions intersected with the bound line</returns>
        public List<ReferenceWithContext> Obstructions(Line boundLine)
        {
            List<ReferenceWithContext> result = new List<ReferenceWithContext>();
            Autodesk.Revit.DB.XYZ startPt = boundLine.GetEndPoint(0);
            Autodesk.Revit.DB.XYZ endPt = boundLine.GetEndPoint(1);
            Autodesk.Revit.DB.XYZ dir = (endPt - startPt).Normalize();
            ReferenceIntersector referenceIntersector = new ReferenceIntersector(m_view3d);
            referenceIntersector.TargetType = FindReferenceTarget.Face;
            IList<ReferenceWithContext> obstructionsOnUnboundLine = referenceIntersector.Find(startPt, dir);
            foreach (ReferenceWithContext gRefWithContext in obstructionsOnUnboundLine)
            {
                Reference gRef = gRefWithContext.GetReference();
                // Judge whether the point is in the bound line or not, if the distance between the point and line
                // is Zero, then the point is in the bound line.
                if (boundLine.Distance(gRef.GlobalPoint) < 1e-9)
                {
                    if (!InArray(result, gRefWithContext))
                    {
                        result.Add(gRefWithContext);
                    }
                }
            }

            result.Sort(CompareReferencesWithContext);
            return result;
        }

        /// <summary>
        /// Judge whether a given Reference is in a Reference list.
        /// Give two References, if their Proximity and Element Id is equal, 
        /// we say the two reference is equal.
        /// </summary>
        /// <param name="arr">Reference Array</param>
        /// <param name="entry">Reference</param>
        /// <returns>True of false</returns>
        private bool InArray(List<ReferenceWithContext> arr, ReferenceWithContext entry)
        {
            foreach (ReferenceWithContext tmp in arr)
            {
                if (Math.Abs(tmp.Proximity - entry.Proximity) < 1e-9 &&
                    tmp.GetReference().ElementId == entry.GetReference().ElementId)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Used to compare two references, just compare their ProximityParameter.
        /// </summary>
        /// <param name="a">First Reference to compare</param>
        /// <param name="b">Second Reference to compare</param>
        /// <returns>-1, 0, or 1</returns>
        private int CompareReferencesWithContext(ReferenceWithContext a, ReferenceWithContext b)
        {
            if (a.Proximity > b.Proximity)
            {
                return 1;
            }

            if (a.Proximity < b.Proximity)
            {
                return -1;
            }

            return 0;
        }
    }
}

Resolver.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//


using System;
using System.Collections.Generic;
using System.Text;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.DB.Plumbing;
using Autodesk.Revit.DB.Mechanical;
using System.Diagnostics;
using Element = Autodesk.Revit.DB.Element;
using System.Collections;

namespace Revit.SDK.Samples.AvoidObstruction.CS
{
   /// <summary>
   /// This class implement the algorithm to detect the obstruction and resolve it.
   /// </summary>
   class Resolver
   {
      /// <summary>
      /// Revit Document.
      /// </summary>
      private Document m_rvtDoc;

      /// <summary>
      /// Revit Application.
      /// </summary>
      private Application m_rvtApp;

      /// <summary>
      /// Detector to detect the obstructions.
      /// </summary>
      private Detector m_detector;


      PipingSystemType m_pipingSystemType;

      /// <summary>
      /// Constructor, initialize all the fields of this class.
      /// </summary>
      /// <param name="data">Revit ExternalCommandData from external command entrance</param>
      public Resolver(ExternalCommandData data)
      {
         m_rvtDoc = data.Application.ActiveUIDocument.Document;
         m_rvtApp = data.Application.Application;
         m_detector = new Detector(m_rvtDoc);

         FilteredElementCollector collector = new FilteredElementCollector(m_rvtDoc);
         var pipingSystemTypes = collector.OfClass(typeof(PipingSystemType)).ToElements();
         foreach (PipingSystemType pipingSystemType in pipingSystemTypes)
         {
            if (pipingSystemType.SystemClassification == MEPSystemClassification.SupplyHydronic ||
               pipingSystemType.SystemClassification == MEPSystemClassification.ReturnHydronic)
            {
               m_pipingSystemType = pipingSystemType; 
               break;
            }
         }
      }

      /// <summary>
      /// Detect and resolve the obstructions of all Pipes.
      /// </summary>
      public void Resolve()
      {
         List<Autodesk.Revit.DB.Element> pipes = new List<Autodesk.Revit.DB.Element>();
         FilteredElementCollector collector = new FilteredElementCollector(m_rvtDoc);
         pipes.AddRange(collector.OfClass(typeof(Pipe)).ToElements());
         foreach (Element pipe in pipes)
         {
            Resolve(pipe as Pipe);
         }
      }

      /// <summary>
      /// Calculate the uniform perpendicular directions with inputting direction "dir".
      /// </summary>
      /// <param name="dir">Direction to calculate</param>
      /// <param name="count">How many perpendicular directions will be calculated</param>
      /// <returns>The calculated perpendicular directions with dir</returns>
      private List<Autodesk.Revit.DB.XYZ> PerpendicularDirs(Autodesk.Revit.DB.XYZ dir, int count)
      {
         List<Autodesk.Revit.DB.XYZ> dirs = new List<Autodesk.Revit.DB.XYZ>();
         Plane plane = Plane.CreateByNormalAndOrigin(dir, Autodesk.Revit.DB.XYZ.Zero);
         Arc arc = Arc.Create(plane, 1.0, 0, 6.28);

         double delta = 1.0 / (double)count;
         for (int i = 1; i <= count; i++)
         {
            Autodesk.Revit.DB.XYZ pt = arc.Evaluate(delta * i, true);
            dirs.Add(pt);
         }

         return dirs;
      }

      /// <summary>
      /// Detect the obstructions of pipe and resolve them.
      /// </summary>
      /// <param name="pipe">Pipe to resolve</param>
      private void Resolve(Pipe pipe)
      {
         var parameter = pipe.get_Parameter(BuiltInParameter.RBS_START_LEVEL_PARAM);
         var levelId = parameter.AsElementId();
         var systemTypeId = m_pipingSystemType.Id;
         // Get the centerline of pipe.
         Line pipeLine = (pipe.Location as LocationCurve).Curve as Line;

         // Calculate the intersection references with pipe's centerline.
         List<ReferenceWithContext> obstructionRefArr = m_detector.Obstructions(pipeLine);

         // Filter out the references, just allow Pipe, Beam, and Duct.
         Filter(pipe, obstructionRefArr);

         if (obstructionRefArr.Count == 0)
         {
            // There are no intersection found.
            return;
         }

         // Calculate the direction of pipe's centerline.
         Autodesk.Revit.DB.XYZ dir = pipeLine.GetEndPoint(1) - pipeLine.GetEndPoint(0);

         // Build the sections from the intersection references.
         List<Section> sections = Section.BuildSections(obstructionRefArr, dir.Normalize());

         // Merge the neighbor sections if the distance of them is too close.
         for (int i = sections.Count - 2; i >= 0; i--)
         {
            Autodesk.Revit.DB.XYZ detal = sections[i].End - sections[i + 1].Start;
            if (detal.GetLength() < pipe.Diameter * 3)
            {
               sections[i].Refs.AddRange(sections[i + 1].Refs);
               sections.RemoveAt(i + 1);
            }
         }

         // Resolve the obstructions one by one.
         foreach (Section sec in sections)
         {
            Resolve(pipe, sec);
         }

         // Connect the neighbor sections with pipe and elbow fittings.
         //
         for (int i = 1; i < sections.Count; i++)
         {
            // Get the end point from the previous section.
            Autodesk.Revit.DB.XYZ start = sections[i - 1].End;

            // Get the start point from the current section.
            Autodesk.Revit.DB.XYZ end = sections[i].Start;

            // Create a pipe between two neighbor section.
            Pipe tmpPipe = Pipe.Create(m_rvtDoc, systemTypeId, pipe.PipeType.Id, levelId, start, end);

            // Copy pipe's parameters values to tmpPipe.
            CopyParameters(pipe, tmpPipe);

            // Create elbow fitting to connect previous section with tmpPipe.
            Connector conn1 = FindConnector(sections[i - 1].Pipes[2], start);
            Connector conn2 = FindConnector(tmpPipe, start);
            FamilyInstance fi = m_rvtDoc.Create.NewElbowFitting(conn1, conn2);

            // Create elbow fitting to connect current section with tmpPipe.
            Connector conn3 = FindConnector(sections[i].Pipes[0], end);
            Connector conn4 = FindConnector(tmpPipe, end);
            FamilyInstance f2 = m_rvtDoc.Create.NewElbowFitting(conn3, conn4);
         }

         // Find two connectors which pipe's two ends connector connected to. 
         Connector startConn = FindConnectedTo(pipe, pipeLine.GetEndPoint(0));
         Connector endConn = FindConnectedTo(pipe, pipeLine.GetEndPoint(1));

         Pipe startPipe = null;
         if (null != startConn)
         {
            // Create a pipe between pipe's start connector and pipe's start section.
            startPipe = Pipe.Create(m_rvtDoc, pipe.PipeType.Id, levelId, startConn, sections[0].Start);
         }
         else
         {
            // Create a pipe between pipe's start point and pipe's start section.
            startPipe = Pipe.Create(m_rvtDoc, systemTypeId, pipe.PipeType.Id, levelId, sections[0].Start, pipeLine.GetEndPoint(0));
         }

         // Copy parameters from pipe to startPipe. 
         CopyParameters(pipe, startPipe);

         // Connect the startPipe and first section with elbow fitting.
         Connector connStart1 = FindConnector(startPipe, sections[0].Start);
         Connector connStart2 = FindConnector(sections[0].Pipes[0], sections[0].Start);
         FamilyInstance fii = m_rvtDoc.Create.NewElbowFitting(connStart1, connStart2);

         Pipe endPipe = null;
         int count = sections.Count;
         if (null != endConn)
         {
            // Create a pipe between pipe's end connector and pipe's end section.
            endPipe = Pipe.Create(m_rvtDoc, pipe.PipeType.Id, levelId, endConn, sections[count - 1].End);
         }
         else
         {
            // Create a pipe between pipe's end point and pipe's end section.
            endPipe = Pipe.Create(m_rvtDoc, systemTypeId, pipe.PipeType.Id, levelId, sections[count - 1].End, pipeLine.GetEndPoint(1));
         }

         // Copy parameters from pipe to endPipe.
         CopyParameters(pipe, endPipe);

         // Connect the endPipe and last section with elbow fitting.
         Connector connEnd1 = FindConnector(endPipe, sections[count - 1].End);
         Connector connEnd2 = FindConnector(sections[count - 1].Pipes[2], sections[count - 1].End);
         FamilyInstance fiii = m_rvtDoc.Create.NewElbowFitting(connEnd1, connEnd2);

         // Delete the pipe after resolved.
         m_rvtDoc.Delete(pipe.Id);
      }

      /// <summary>
      /// Filter the inputting References, just allow Pipe, Duct and Beam References.
      /// </summary>
      /// <param name="pipe">Pipe</param>
      /// <param name="refs">References to filter</param>
      private void Filter(Pipe pipe, List<ReferenceWithContext> refs)
      {
         for (int i = refs.Count - 1; i >= 0; i--)
         {
            Reference cur = refs[i].GetReference();
            Element curElem = m_rvtDoc.GetElement(cur);
            if (curElem.Id == pipe.Id ||
            (!(curElem is Pipe) && !(curElem is Duct) &&
            curElem.Category.Id.IntegerValue != (int)BuiltInCategory.OST_StructuralFraming))
            {
               refs.RemoveAt(i);
            }
         }
      }

      /// <summary>
      /// This method will find out a route to avoid the obstruction. 
      /// </summary>
      /// <param name="pipe">Pipe to resolve</param>
      /// <param name="section">Pipe's one obstruction</param>
      /// <returns>A route which can avoid the obstruction</returns>
      private Line FindRoute(Pipe pipe, Section section)
      {
         // Perpendicular direction minimal length.
         double minLength = pipe.Diameter * 2;

         // Parallel direction jump step. 
         double jumpStep = pipe.Diameter;

         // Calculate the directions in which to find the solution.
         List<Autodesk.Revit.DB.XYZ> dirs = new List<Autodesk.Revit.DB.XYZ>();
         Autodesk.Revit.DB.XYZ crossDir = null;
         foreach (ReferenceWithContext gref in section.Refs)
         {
            Element elem = m_rvtDoc.GetElement(gref.GetReference());
            Line locationLine = (elem.Location as LocationCurve).Curve as Line;
            Autodesk.Revit.DB.XYZ refDir = locationLine.GetEndPoint(1) - locationLine.GetEndPoint(0);
            refDir = refDir.Normalize();
            if (refDir.IsAlmostEqualTo(section.PipeCenterLineDirection) || refDir.IsAlmostEqualTo(-section.PipeCenterLineDirection))
            {
               continue;
            }
            crossDir = refDir.CrossProduct(section.PipeCenterLineDirection);
            dirs.Add(crossDir.Normalize());
            break;
         }

         // When all the obstruction are parallel with the centerline of the pipe,
         // We can't calculate the direction from the vector.Cross method.
         if (dirs.Count == 0)
         {
            // Calculate perpendicular directions with dir in four directions.
            List<Autodesk.Revit.DB.XYZ> perDirs = PerpendicularDirs(section.PipeCenterLineDirection, 4);
            dirs.Add(perDirs[0]);
            dirs.Add(perDirs[1]);
         }

         Line foundLine = null;
         while (null == foundLine)
         {
            // Extend the section interval by jumpStep.
            section.Inflate(0, jumpStep);
            section.Inflate(1, jumpStep);

            // Find solution in the given directions.
            for (int i = 0; null == foundLine && i < dirs.Count; i++)
            {
               // Calculate the intersections.
               List<ReferenceWithContext> obs1 = m_detector.Obstructions(section.Start, dirs[i]);
               List<ReferenceWithContext> obs2 = m_detector.Obstructions(section.End, dirs[i]);

               // Filter out the intersection result.
               Filter(pipe, obs1);
               Filter(pipe, obs2);

               // Find out the minimal intersections in two opposite direction.
               ReferenceWithContext[] mins1 = GetClosestSectionsToOrigin(obs1);
               ReferenceWithContext[] mins2 = GetClosestSectionsToOrigin(obs2);

               // Find solution in the given direction and its opposite direction.
               for (int j = 0; null == foundLine && j < 2; j++)
               {
                  if (mins1[j] != null && Math.Abs(mins1[j].Proximity) < minLength ||
                      mins2[j] != null && Math.Abs(mins2[j].Proximity) < minLength)
                  {
                     continue;
                  }

                  // Calculate the maximal height that the parallel line can be reached.
                  double maxHight = 1000 * pipe.Diameter;
                  if (mins1[j] != null && mins2[j] != null)
                  {
                     maxHight = Math.Min(Math.Abs(mins1[j].Proximity), Math.Abs(mins2[j].Proximity));
                  }
                  else if (mins1[j] != null)
                  {
                     maxHight = Math.Abs(mins1[j].Proximity);
                  }
                  else if (mins2[j] != null)
                  {
                     maxHight = Math.Abs(mins2[j].Proximity);
                  }

                  Autodesk.Revit.DB.XYZ dir = (j == 1) ? dirs[i] : -dirs[i];

                  // Calculate the parallel line which can avoid obstructions.
                  foundLine = FindParallelLine(pipe, section, dir, maxHight);
               }
            }
         }
         return foundLine;
      }

      /// <summary>
      /// Find a line Parallel to pipe's centerline to avoid the obstruction.
      /// </summary>
      /// <param name="pipe">Pipe who has obstructions</param>
      /// <param name="section">Pipe's one obstruction</param>
      /// <param name="dir">Offset Direction of the parallel line</param>
      /// <param name="maxLength">Maximum offset distance</param>
      /// <returns>Parallel line which can avoid the obstruction</returns>
      private Line FindParallelLine(Pipe pipe, Section section, Autodesk.Revit.DB.XYZ dir, double maxLength)
      {
         double step = pipe.Diameter;
         double hight = 2 * pipe.Diameter;
         while (hight <= maxLength)
         {
            Autodesk.Revit.DB.XYZ detal = dir * hight;
            Line line = Line.CreateBound(section.Start + detal, section.End + detal);
            List<ReferenceWithContext> refs = m_detector.Obstructions(line);
            Filter(pipe, refs);

            if (refs.Count == 0)
            {
               return line;
            }
            hight += step;
         }
         return null;
      }

      /// <summary>
      /// Find out two References, whose ProximityParameter is negative or positive,
      /// And Get the minimal value from all positive reference, and get the maximal value 
      /// from the negative reference. if there are no such reference, using null instead.
      /// </summary>
      /// <param name="refs">References</param>
      /// <returns>Reference array</returns>
      private ReferenceWithContext[] GetClosestSectionsToOrigin(List<ReferenceWithContext> refs)
      {
         ReferenceWithContext[] mins = new ReferenceWithContext[2];
         if (refs.Count == 0)
         {
            return mins;
         }

         if (refs[0].Proximity > 0)
         {
            mins[1] = refs[0];
            return mins;
         }

         for (int i = 0; i < refs.Count - 1; i++)
         {
            if (refs[i].Proximity < 0 && refs[i + 1].Proximity > 0)
            {
               mins[0] = refs[i];
               mins[1] = refs[i + 1];
               return mins;
            }
         }

         mins[0] = refs[refs.Count - 1];

         return mins;
      }


      /// <summary>
      /// Resolve one obstruction of Pipe.
      /// </summary>
      /// <param name="pipe">Pipe to resolve</param>
      /// <param name="section">One pipe's obstruction</param>
      private void Resolve(Pipe pipe, Section section)
      {
         // Find out a parallel line of pipe centerline, which can avoid the obstruction.
         Line offset = FindRoute(pipe, section);

         // Construct a section line according to the given section.
         Line sectionLine = Line.CreateBound(section.Start, section.End);

         // Construct two side lines, which can avoid the obstruction too.
         Line side1 = Line.CreateBound(sectionLine.GetEndPoint(0), offset.GetEndPoint(0));
         Line side2 = Line.CreateBound(offset.GetEndPoint(1), sectionLine.GetEndPoint(1));

         //
         // Create an "U" shape, which connected with three pipes and two elbows, to round the obstruction.
         //
         PipeType pipeType = pipe.PipeType;
         Autodesk.Revit.DB.XYZ start = side1.GetEndPoint(0);
         Autodesk.Revit.DB.XYZ startOffset = offset.GetEndPoint(0);
         Autodesk.Revit.DB.XYZ endOffset = offset.GetEndPoint(1);
         Autodesk.Revit.DB.XYZ end = side2.GetEndPoint(1);

         var parameter = pipe.get_Parameter(BuiltInParameter.RBS_START_LEVEL_PARAM);
         var levelId = parameter.AsElementId();
         // Create three side pipes of "U" shape.
         var systemTypeId = m_pipingSystemType.Id;
         Pipe pipe1 = Pipe.Create(m_rvtDoc, systemTypeId, pipeType.Id, levelId, start, startOffset);
         Pipe pipe2 = Pipe.Create(m_rvtDoc, systemTypeId, pipeType.Id, levelId, startOffset, endOffset);
         Pipe pipe3 = Pipe.Create(m_rvtDoc, systemTypeId, pipeType.Id, levelId, endOffset, end);

         // Copy parameters from pipe to other three created pipes.
         CopyParameters(pipe, pipe1);
         CopyParameters(pipe, pipe2);
         CopyParameters(pipe, pipe3);

         // Add the created three pipes to current section.
         section.Pipes.Add(pipe1);
         section.Pipes.Add(pipe2);
         section.Pipes.Add(pipe3);

         // Create the first elbow to connect two neighbor pipes of "U" shape.
         Connector conn1 = FindConnector(pipe1, startOffset);
         Connector conn2 = FindConnector(pipe2, startOffset);
         m_rvtDoc.Create.NewElbowFitting(conn1, conn2);

         // Create the second elbow to connect another two neighbor pipes of "U" shape.
         Connector conn3 = FindConnector(pipe2, endOffset);
         Connector conn4 = FindConnector(pipe3, endOffset);
         m_rvtDoc.Create.NewElbowFitting(conn3, conn4);
      }

      /// <summary>
      /// Copy parameters from source pipe to target pipe.
      /// </summary>
      /// <param name="source">Coping source</param>
      /// <param name="target">Coping target</param>
      private void CopyParameters(Pipe source, Pipe target)
      {
         double diameter = source.get_Parameter(BuiltInParameter.RBS_PIPE_DIAMETER_PARAM).AsDouble();
         target.get_Parameter(BuiltInParameter.RBS_PIPE_DIAMETER_PARAM).Set(diameter);
      }

      /// <summary>
      /// Find out a connector from pipe with a specified point.
      /// </summary>
      /// <param name="pipe">Pipe to find the connector</param>
      /// <param name="conXYZ">Specified point</param>
      /// <returns>Connector whose origin is conXYZ</returns>
      private Connector FindConnector(Pipe pipe, Autodesk.Revit.DB.XYZ conXYZ)
      {
         ConnectorSet conns = pipe.ConnectorManager.Connectors;
         foreach (Connector conn in conns)
         {
            if (conn.Origin.IsAlmostEqualTo(conXYZ))
            {
               return conn;
            }
         }
         return null;
      }

      /// <summary>
      /// Find out the connector which the pipe's specified connector connected to.
      /// The pipe's specified connector is given by point conxyz.
      /// </summary>
      /// <param name="pipe">Pipe to find the connector</param>
      /// <param name="conXYZ">Specified point</param>
      /// <returns>Connector whose origin is conXYZ</returns>
      private Connector FindConnectedTo(Pipe pipe, Autodesk.Revit.DB.XYZ conXYZ)
      {
         Connector connItself = FindConnector(pipe, conXYZ);
         ConnectorSet connSet = connItself.AllRefs;
         foreach (Connector conn in connSet)
         {
            if (conn.Owner.Id.IntegerValue != pipe.Id.IntegerValue &&
                conn.ConnectorType == ConnectorType.End)
            {
               return conn;
            }
         }
         return null;
      }
   }
}

Section.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//

using System;
using System.Collections.Generic;
using System.Text;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Plumbing;

namespace Revit.SDK.Samples.AvoidObstruction.CS
{
    /// <summary>
    /// This class presents an obstruction of a Pipe.
    /// </summary>
    class Section
    {   
        /// <summary>
        /// Pipe centerline's direction.
        /// </summary>
        private Autodesk.Revit.DB.XYZ  m_dir;

        /// <summary>
        /// Extend factor in negative direction.
        /// </summary>
        private double m_startFactor;

        /// <summary>
        /// Extend factor in positive direction.
        /// </summary>
        private double m_endFactor;

        /// <summary>
        /// References contained in this obstruction.
        /// </summary>
        private List<ReferenceWithContext> m_refs;

        /// <summary>
        /// Pipes to avoid this obstruction, it is assigned when resolving this obstruction.
        /// Its count will be three if resolved, the three pipe constructs a "U" shape to round the obstruction.
        /// </summary>
        private List<Pipe> m_pipes;        

        /// <summary>
        /// Private constructor, just be called in static factory method BuildSections.
        /// </summary>
        /// <param name="dir">Pipe's direction</param>
        private Section(Autodesk.Revit.DB.XYZ  dir)
        {
            m_dir = dir;
            m_startFactor = 0;
            m_endFactor = 0;
            m_refs = new List<ReferenceWithContext>();
            m_pipes = new List<Pipe>();
        }

        /// <summary>
        /// Pipe centerline's direction.
        /// </summary>
        public Autodesk.Revit.DB.XYZ  PipeCenterLineDirection
        {
            get { return m_dir; }
        }

        /// <summary>
        /// Pipes to avoid this obstruction, it is assigned when resolving this obstruction.
        /// Its count will be three if resolved, the three pipe constructs a "U" shape to round the obstruction.
        /// </summary>
        public List<Pipe> Pipes
        {
            get { return m_pipes; }
        }

        /// <summary>
        /// Start point of this obstruction.
        /// </summary>
        public Autodesk.Revit.DB.XYZ  Start
        {
            get
            {
                return m_refs[0].GetReference().GlobalPoint + m_dir * m_startFactor;
            }
        }

        /// <summary>
        /// End point of this obstruction.
        /// </summary>
        public Autodesk.Revit.DB.XYZ  End
        {
            get
            {
                return m_refs[m_refs.Count - 1].GetReference().GlobalPoint + m_dir * m_endFactor;
            }
        }

        /// <summary>
        /// References contained in this obstruction.
        /// </summary>
        public List<ReferenceWithContext> Refs
        {
            get { return m_refs; }
        }

        /// <summary>
        /// Extend this obstruction's interval in one direction.
        /// </summary>
        /// <param name="index">index of direction, 0 => start, 1 => end</param>
        public void Inflate(int index, double value)
        {
            if (index == 0)
            {
                m_startFactor -= value;
            }
            else if(index == 1)
            {
                m_endFactor += value;
            }
            else
            {
                throw new ArgumentOutOfRangeException("Index should be 0 or 1.");
            }            
        }

        /// <summary>
        /// Build sections for References, it's a factory method to build sections.
        /// A section contains several points through which the ray passes the obstruction(s). 
        /// for example, a section may contain 2 points when the obstruction is stand alone, 
        /// or contain 4 points if 2 obstructions are intersects with each other in the direction of the ray.
        /// </summary>
        /// <param name="allrefs">References</param>
        /// <param name="dir">Pipe's direction</param>
        /// <returns>List of Section</returns>
        public static List<Section> BuildSections(List<ReferenceWithContext> allrefs, Autodesk.Revit.DB.XYZ  dir)
        {
            List<ReferenceWithContext> buildStack = new List<ReferenceWithContext>();            
            List<Section> sections = new List<Section>();
            Section current = null;
            foreach (ReferenceWithContext geoRef in allrefs)
            {                
                if (buildStack.Count == 0)
                {
                    current = new Section(dir);
                    sections.Add(current);
                }

                current.Refs.Add(geoRef);

                ReferenceWithContext tmp = Find(buildStack, geoRef);
                if (tmp != null)
                {
                    buildStack.Remove(tmp);
                }
                else
                    buildStack.Add(geoRef);
            }

            return sections;
        }

        /// <summary>
        /// Judge whether a Reference is already in the list of Reference, return the founded value.
        /// </summary>
        /// <param name="arr">List of Reference</param>
        /// <param name="entry">Reference to test</param>
        /// <returns>One Reference has the same element's Id with entry</returns>
        private static ReferenceWithContext Find(List<ReferenceWithContext> arr, ReferenceWithContext entry)
        {
            foreach (ReferenceWithContext tmp in arr)
            {
                if (tmp.GetReference().ElementId == entry.GetReference().ElementId)
                {
                    return tmp;
                }
            }
            return null;
        }
    }
}