应用程序名称: FabricationPartLayout

Revit 平台: MEP(机电工程)

Revit 版本: 2017.0

首次发布版本: 2016.0

编程语言: C#

技能要求水平: 中等

类别: MEP(机电工程)

类型: ExternalCommand(外部命令)

主题: Fabrication零件

摘要:

本示例演示了如何使用Fabrication零件API创建一个简单的布局,并对模型中的制作数据进行修改。其中包括:

·矩形和圆形管道

·放置接头

·旋转

·直线优化

·连接到一个通用的Revit族实例

·将制作部件适配到目标元素

·将设计元素转换为制作零件

·重新编号直接/耦合制作零件

·导入和导出零件文件

·将制作零件导出到PCFMAJ格式

·访问制作零件网格几何信息。

相关类别:

Autodesk.Revit.DB.Connector

Autodesk.Revit.DB.ConnectorSet

Autodesk.Revit.DB.Document

Autodesk.Revit.DB.ElementId

Autodesk.Revit.DB.FabricationAncillaryUsage

Autodesk.Revit.DB.FabricationConfiguration

Autodesk.Revit.DB.FabricationDimensionDefinition

Autodesk.Revit.DB.FabricationItemFile

Autodesk.Revit.DB.FabricationItemFolder

Autodesk.Revit.DB.FabricationRodInfo

Autodesk.Revit.DB.FabricationPart

Autodesk.Revit.DB.FabricationService

Autodesk.Revit.DB.FabricationServiceButton

Autodesk.Revit.DB.FamilyInstance

Autodesk.Revit.DB.Level

Autodesk.Revit.DB.MEPConnectorInfo

Autodesk.Revit.DB.Parameter

Autodesk.Revit.DB.Fabrication.CustomDataType

Autodesk.Revit.DB.Fabrication.DesignToFabricationConverter

Autodesk.Revit.DB.Fabrication.DesignToFabricationConverterResult

Autodesk.Revit.DB.Fabrication.FabricationAncillaryType

Autodesk.Revit.DB.Fabrication.FabricationAncillaryUsageType

Autodesk.Revit.DB.Fabrication.FabricationPartCompareType

Autodesk.Revit.DB.Fabrication.FabricationPartFitResult

Autodesk.Revit.DB.Fabrication.FabricationPartRouteEnd

Autodesk.Revit.DB.Fabrication.FabricationUtils

Autodesk.Revit.UI.IExternalCommand

项目文件:

FabricationPartLayout.cs

它继承自接口IExternalCommand并实现Execute方法。这个类向用户展示了如何创建一个包含制作部件的样本布局。

OptimizeStraights.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何根据用户选择使用“优化长度”方法。

StretchAndFit.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何根据用户选择使用StretchAndFit方法。

ConvertToFabrication.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何根据用户选择使用DesignToFabrication方法。

PartRenumber.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何使用FabricationPart.ItemNumber重新编号一组零件,并使用Fabrication.IsSameAs方法。

ButtonGroupExclusions.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何覆盖路由操作的默认制作服务组和按钮排除。

Ancillaries.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何获取所选制作部件的附属信息。

CustomData.cs包含两个类:

GetCustomDataSetCustomData,它们都继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何获取所选制作部件的附属信息。GetCustomData类向用户展示如何在制作零件上获取制作自定义数据,而SetCustomData类向用户展示如何设置制作自定义数据。

ExportToPCF.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何将所选制作部件导出到PCF文件。

FabPartGeometry.cs

它继承自接口IExternalCommand并实现Execute方法。该类向用户展示了如何访问制作零件网格几何信息。

PartInfo.cs

包含 PartInfo 类,该类继承自接口 IExternalCommand 并实现了 Execute 方法。该类向用户展示如何获取各种加工部件属性。

HangerRods.cs:

 包含 DetachRodsDoubleRodLengthHalveRodLengthIncreaseRodStructureExtension DecreaseRodStructureExtension 类,它们都继承自接口 IExternalCommand 并实现了 Execute 方法。这些类向用户展示如何从所属的结构中分离悬挂杆,如何更改杆长和杆结构扩展的大小。

SplitStraight.cs:

 包含 SplitStraight 类,该类继承自接口 IExternalCommand 并实现了 Execute 方法。该类向用户展示如何将一条直线拆分为两个线段。

ItemFile.cs:

包含 LoadAndPlaceNextItemFile UnloadUnusedItemFiles 类,它们都继承自接口 IExternalCommand 并实现了 Execute 方法。这些类向用户展示如何从配置的项目文件夹结构中加载和卸载加工项目文件。

SaveAsFabricationJob.cs :

该类继承自接口 IExternalCommand 并实现了 Execute 方法。该类向用户展示如何将选定的加工部件导出为 MAJ 文件。

描述:

该示例提供以下功能:

- 从加工部件创建样本布局

- 访问加工配置信息

- 将加工龙头连接到加工直通管件

- 绕其连接端旋转加工部件

- 修改规范

- 修改隔热规范

- 修改加工材料

- 修改加工连接器

- 在加工直通管件上放置加工龙头

- 在加工直通管件上放置加工吊架

- 根据用户选择优化加工直通管件的长度

- 将加工部件拉伸和适应目标连接器。

- 将设计元素转换为加工部件

- 对一组加工部件重新编号

- 查看辅助数据

- 设置服务组和按钮排除

- 查看和设置零件自定义数据

- 将选定的加工部件导出为 PCF 文件

- 查看加工部件数据

- 调整悬挂杆长度,结构扩展并从其结构主机中分离

- 将直线分成两个相等的线段

- 加载和放置附加部件

- 卸载未使用的附加部件

- 将选定的加工部件导出为 MAJ 文件

- 访问加工部件网格几何形状。

说明:

打开 Revit 应用程序。

1. 加工部件布局

a. 加载 FabricationPartLayout.rvt

b. 执行命令。

预期结果:绘制一个包含连接到现有 Revit AHU 实例的加工部件的样本布局。

 

2. 优化直管部件

a. 加载 FabricationPartLayout.rvt 或添加一些加工部件直管。

b. 对加工部件进行选择,确保选择了一些较长的加工部件直管。

c. 执行命令

 

预期结果:长度超过标准长度的加工部件将被优化为更小的直管部件。

 

3. 拉伸和适配

a. 加载 StretchAndFit.rvt

b. 在模型中选择两个半径弯曲部件。

c. 执行命令

 

预期结果:半径弯曲部分应该拉伸和适应,并连接到所选直管。

 

4. 转换为加工部件

a. 加载 ConvertToFabrication.rvt

b. 选择管道布局

c. 执行命令

 

预期结果:管道布局应转换为加工部件。

 

5. 部件重新编号

a. 选择加工部件

b. 执行命令

 

预期结果:加工部件的 Item Number 属性应该被填充,所有相同的部件应具有相同的编号。忽略的字段基于忽略零件的注释,订单号和服务。

 

6. 按钮和组排除

a. 加载 ButtonGroupExclusions.rvt

b. 执行命令

 

预期结果:排除了 Square Bend 按钮和 Round Bought Out 服务组。

 

7. 辅助数据

a. 执行命令

b. 选择一个加工部件

 

预期结果:应该出现一个对话框,显示选择的加工部件的辅助信息。

 

8. 获取自定义数据

a. 加载 CustomData.rvt

b. 执行命令

c. 选择一个加工部件

 

预期结果:应该出现一个对话框,显示选择的加工部件的自定义数据信息。

 

9. 设置自定义数据

a. 加载 CustomData.rvt

b. 执行命令

c. 选择一个加工部件

 

预期结果:应该出现一个对话框,显示选择的加工部件的自定义数据信息,详细列出了修改前后的值。

 

10. 导出为 PCF

a. 选择一些加工部件

b. 执行命令

 

预期结果:在指定的位置创建一个 PCF 文件。

 

11. 显示部件信息

a. 执行命令

b. 选择一个加工部件

 

预期结果:应该出现一个对话框,显示加工部件信息。

 

12. 分离悬挂杆

a. 加载 HangerRods.rvt

b. 执行命令

c. 选择一个加工部件悬挂杆

 

预期结果:悬挂杆不再由其结构主机托管。

 

13. 倍增杆长

a. 加载 HangerRods.rvt

b. 执行命令

c. 选择一个未连接到结构的加工部件悬挂杆。如有必要,请先使用“分离悬挂杆”命令。

 

预期结果:悬挂杆的长度应该加倍。

 

14. 将吊杆长度减半

a. 加载 HangerRods.rvt

b. 执行命令

c. 选择一个未连接到结构的预制部件吊杆。如果需要,首先使用“分离吊杆”命令。

 

预期结果:吊杆长度应减半。

 

15. 增加吊杆结构延伸

a. 加载 HangerRods.rvt

b. 执行命令

c. 选择一个连接到结构的预制部件吊杆。

 

预期结果:吊杆长度将增加 1 英尺,仍连接到结构。

 

16. 减少吊杆结构延伸

a. 加载 HangerRods.rvt

b. 执行命令

c. 选择一个连接到结构的预制部件吊杆。

 

预期结果:吊杆长度将缩短 1 英尺,仍连接到结构。

 

17. 分割成两段直线

a. 执行命令

b. 选择一个预制部件直线段

 

预期结果:直线段将被分割成两个相等的线段。

 

18. 加载并放置下一个物品文件

a. 加载一个具有有效预制配置且您可以访问源的项目。

b. 执行命令。

 

预期结果:之前未加载的第一个物品文件将被加载到配置中。一个新的预制部件将从加载的物品文件创建、放置并选择到模型中。

 

19. 卸载未使用的物品文件

a. 加载已将物品文件加载到配置中但未使用的项目。

b. 执行命令。

 

预期结果:未使用的已加载物品文件将从配置中卸载。

 

20. 导出到 MAJ

a. 选择一些预制部件。

b. 执行命令

 

预期结果:在指定位置创建一个 MAJ 文件。

 

21. 访问预制部件几何图形

a. 选择一些预制部件

b. 执行命令

 

预期结果:网格三角面几何图形将导出到 CSV 文件中。

源代码:

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

Ancillaries.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using Autodesk.Revit.UI.Selection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class Ancillaries : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {

         Document doc = commandData.Application.ActiveUIDocument.Document;
         UIDocument uidoc = commandData.Application.ActiveUIDocument;

         FabricationPart fabPart = null;
         FabricationConfiguration config = null;

         try
         {
            // check for a load fabrication config
            config = FabricationConfiguration.GetFabricationConfiguration(doc);

            if (config == null)
            {
               message = "No fabrication configuration loaded.";
               return Result.Failed;
            }

            // pick a fabrication part
            Reference refObj = uidoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part to start.");
            fabPart = doc.GetElement(refObj) as FabricationPart;
         }
         catch (Autodesk.Revit.Exceptions.OperationCanceledException)
         {
            return Result.Cancelled;
         }

         if (fabPart == null)
         {
            message = "The selected element is not a fabrication part.";
            return Result.Failed;
         }
         else
         {
            // get ancillary data from selected part and report to user
            IList<FabricationAncillaryUsage> ancillaries = fabPart.GetPartAncillaryUsage();
            List<string> ancillaryDescriptions = new List<string>();

            // create list of ancillary descriptions using the Ancillary UseageType and Name
            foreach (var ancillaryUsage in ancillaries)
            {
               FabricationAncillaryType ancilType = ancillaryUsage.Type;
               FabricationAncillaryUsageType usageType = ancillaryUsage.UsageType;
               ancillaryDescriptions.Add($"{ancilType.ToString()}: {usageType.ToString()} - "
                                + $"{config.GetAncillaryName(ancillaryUsage.AncillaryId)}");
            }

            string results = string.Empty;

            // group and quantify 
            if (ancillaryDescriptions.Count > 0)
            {
               ancillaryDescriptions.Sort();
               StringBuilder resultsBuilder = new StringBuilder();
               string currentAncillary = string.Empty;

               foreach (var ancillaryName in ancillaryDescriptions)
               {
                  if (ancillaryName != currentAncillary)
                  {
                     resultsBuilder.AppendLine($"{ancillaryName} x {ancillaryDescriptions.Count(x => x == ancillaryName)}");
                     currentAncillary = ancillaryName;
                  }
               }
               results = resultsBuilder.ToString();
            }

            TaskDialog td = new TaskDialog("Ancillaries")
            {
               MainIcon = TaskDialogIcon.TaskDialogIconInformation,
               TitleAutoPrefix = false,
               MainInstruction = ancillaryDescriptions.Count > 0 ?
                                   $"{ancillaryDescriptions.Count} ancillaries found on selected part"
                                 : $"No ancillaries found on selected part",
               MainContent = results
            };

            td.Show();
         }

         return Result.Succeeded;
      }
   }
}

ButtonGroupExclusions.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class ButtonGroupExclusions : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            Document doc = commandData.Application.ActiveUIDocument.Document;

            using (Transaction tr = new Transaction(doc, "Set button and group exclusions"))
            {
               tr.Start();

               FabricationConfiguration config = FabricationConfiguration.GetFabricationConfiguration(doc);

               if (config == null)
               {
                  message = "No fabrication configuration loaded.";
                  return Result.Failed;
               }

               // get all loaded fabrication services
               IList<FabricationService> allLoadedServices = config.GetAllLoadedServices();
               // get the "ADSK - HVAC:Supply Air" service
               string serviceName = "ADSK - HVAC: Supply Air";
               FabricationService selectedService = allLoadedServices.FirstOrDefault(x => x.Name == serviceName);

               if (selectedService == null)
               {
                  message = $"Could not find fabrication service {serviceName}";
                  return Result.Failed;
               }

               string rectangularGroupName = "Rectangular";
               string roundGroupName = "Round Bought Out";
               string excludeButtonName = "Square Bend";

               int rectangularGroupIndex = -1;
               int roundGroupIndex = -1;

               // find Rectangular and Round groups in service
               for (int i = 0; i < selectedService.GroupCount; i++)
               {
                  if (selectedService.GetGroupName(i) == rectangularGroupName)
                  {
                     rectangularGroupIndex = i;
                  }

                  if (selectedService.GetGroupName(i) == roundGroupName)
                  {
                     roundGroupIndex = i;
                  }

                  if (rectangularGroupIndex > -1 && roundGroupIndex > -1)
                  {
                     break;
                  }
               }

               if (rectangularGroupIndex > -1)
               {
                  // exclude square bend in Rectangular group
                  for (int i = 0; i < selectedService.GetButtonCount(rectangularGroupIndex); i++)
                  {
                     if (selectedService.GetButton(rectangularGroupIndex, i).Name == excludeButtonName)
                     {
                        selectedService.OverrideServiceButtonExclusion(rectangularGroupIndex, i, true);
                        break;
                     }
                  }
               }
               else
               {
                  message = $"Unable to locate {excludeButtonName} button to exclude.";
                  return Result.Failed;
               }

               // exclude entire Round Bought Out service group
               if (roundGroupIndex > -1)
               {
                  selectedService.SetServiceGroupExclusions(new List<int>() { roundGroupIndex });
               }
               else
               {
                  message = $"Unable to locate {roundGroupName} service group to exclude.";
                  return Result.Failed;
               }

               tr.Commit();

               TaskDialog td = new TaskDialog("Button and Group Exclsuions")
               {
                  MainIcon = TaskDialogIcon.TaskDialogIconInformation,
                  TitleAutoPrefix = false,
                  MainInstruction = "Operation Successful",
                  MainContent = $"Excluded {excludeButtonName} button from {serviceName} {rectangularGroupName} Group {Environment.NewLine}"
                                 + $"Excluded {roundGroupName} Group from {serviceName}"
               };

               td.Show();
            }


            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

ChangeService.cs

//
// (C) Copyright 2003-2011 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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class ChangeService : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            Document doc = commandData.Application.ActiveUIDocument.Document;

            // get the user selection
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            ICollection<ElementId> collection = uidoc.Selection.GetElementIds();

            if (collection.Count > 0)
            {
               // FabricationNetworkChangeService needs an ISet<ElementId>
               ISet<ElementId> selIds = new HashSet<ElementId>();
               foreach (ElementId id in collection)
               {
                  selIds.Add(id);
               }

               using (Transaction tr = new Transaction(doc, "Change Service of Fabrication Parts"))
               {
                  tr.Start();

                  FabricationConfiguration config = FabricationConfiguration.GetFabricationConfiguration(doc);

                  // Get all loaded fabrication services
                  IList<FabricationService> allLoadedServices = config.GetAllLoadedServices();

                  FabricationNetworkChangeService changeservice = new FabricationNetworkChangeService(doc);

                  // Change the fabrication parts to the first loaded service and group
                  FabricationNetworkChangeServiceResult result = changeservice.ChangeService(selIds, allLoadedServices[0].ServiceId, 0);

                  if (result != FabricationNetworkChangeServiceResult.Success)
                  {
                     message = "There was a problem with the change service.";
                     return Result.Failed;
                  }

                  doc.Regenerate();

                  tr.Commit();
               }

               return Result.Succeeded;
            }
            else
            {
               // inform user they need to select at least one element
               message = "Please select at least one element.";
            }

            return Result.Failed;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }

   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class ChangeSize : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            Document doc = commandData.Application.ActiveUIDocument.Document;

            // get the user selection
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            ICollection<ElementId> collection = uidoc.Selection.GetElementIds();

            if (collection.Count > 0)
            {
               // FabricationNetworkChangeService needs an ISet<ElementId>
               ISet<ElementId> selIds = new HashSet<ElementId>();
               foreach (ElementId id in collection)
               {
                  selIds.Add(id);
               }

               using (Transaction tr = new Transaction(doc, "Change Size of Fabrication Parts"))
               {
                  tr.Start();

                  FabricationConfiguration config = FabricationConfiguration.GetFabricationConfiguration(doc);

                  // Get all loaded fabrication services
                  IList<FabricationService> allLoadedServices = config.GetAllLoadedServices();

                  // Create a map of sizes to swap the current sizes to a new size
                  var sizeMappings = new HashSet<Autodesk.Revit.DB.Fabrication.FabricationPartSizeMap>();
                  var mapping = new Autodesk.Revit.DB.Fabrication.FabricationPartSizeMap("12x12", 1.0, 1.0, false, ConnectorProfileType.Rectangular, allLoadedServices[0].ServiceId, 0 );
                  mapping.MappedWidthDiameter = 1.5;
                  mapping.MappedDepth = 1.5;
                  sizeMappings.Add( mapping );
                  var mapping1 = new Autodesk.Revit.DB.Fabrication.FabricationPartSizeMap("18x18", 1.5, 1.5, false, ConnectorProfileType.Rectangular, allLoadedServices[0].ServiceId, 0 );
                  mapping1.MappedWidthDiameter = 2.0;
                  mapping1.MappedDepth = 2.0;
                  sizeMappings.Add( mapping1 );

                  FabricationNetworkChangeService changesize = new FabricationNetworkChangeService(doc);

                  // Change the size of the fabrication parts in the selection to the new sizes
                  FabricationNetworkChangeServiceResult result = changesize.ChangeSize(selIds, sizeMappings );

                  if (result != FabricationNetworkChangeServiceResult.Success)
                  {
                     // Get the collection of element identifiers for parts that had errors posted against them
                     ICollection<ElementId> errorIds = changesize.GetElementsThatFailed();
                     if ( errorIds.Count > 0 )
                     {
                        message = "There was a problem with the change size.";
                        return Result.Failed;
                     }
                  }

                  doc.Regenerate();

                  tr.Commit();
               }

               return Result.Succeeded;
            }
            else
            {
               // inform user they need to select at least one element
               message = "Please select at least one element.";
            }

            return Result.Failed;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
   
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class ApplyChange : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            Document doc = commandData.Application.ActiveUIDocument.Document;

            // get the user selection
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            ICollection<ElementId> collection = uidoc.Selection.GetElementIds();

            if (collection.Count > 0)
            {
               // FabricationNetworkChangeService needs an ISet<ElementId>
               ISet<ElementId> selIds = new HashSet<ElementId>();
               foreach (ElementId id in collection)
               {
                  selIds.Add(id);
               }

               using (Transaction tr = new Transaction(doc, "Appply Change Service and Size of Fabrication Parts"))
               {
                  tr.Start();

                  FabricationConfiguration config = FabricationConfiguration.GetFabricationConfiguration(doc);

                  // Get all loaded fabrication services
                  IList<FabricationService> allLoadedServices = config.GetAllLoadedServices();

                  FabricationNetworkChangeService applychange = new FabricationNetworkChangeService(doc);

                  // Set the selection of element identifiers to be changed
                  applychange.SetSelection( selIds );
                  // Set the service to the second service in the list (ductwork exhaust service)
                  applychange.SetServiceId( allLoadedServices[1].ServiceId );
                  // Set the group to the second in the list (round)
                  applychange.SetGroupId( 1 );

                  // Get the sizes of all the straights that was in the selection of elements that was added to FabricationNetworkChangeService
                  ISet<Autodesk.Revit.DB.Fabrication.FabricationPartSizeMap> sizeMappings = applychange.GetMapOfAllSizesForStraights();
                  foreach (Autodesk.Revit.DB.Fabrication.FabricationPartSizeMap sizemapping in sizeMappings)
                  {
                     if (sizemapping != null)
                     {
                        // Testing round so ignoring the depth and adding 6" to the current size so all straights will be updated to a new size
                        var widthDia = sizemapping.WidthDiameter + 0.5;
                        sizemapping.MappedWidthDiameter = widthDia;
                     }
                  }
                  applychange.SetMapOfSizesForStraights( sizeMappings );

                  // Get the in-line element type identiers
                  var inlineRevIds = new HashSet<Autodesk.Revit.DB.ElementId>();
                  ISet<Autodesk.Revit.DB.ElementId> inlineIds = applychange.GetInLinePartTypes();
                  for ( var ii = inlineIds.Count() - 1; ii > -1; ii-- )
                  {
                     var elemId = inlineIds.ElementAt( ii );
                     if (elemId != null)
                        inlineRevIds.Add(elemId);
                  }
                  // Set the in-line element type identiers by swapping them out by reversing the order to keep it simple but still exercise the code
                  IDictionary<ElementId, ElementId> swapinlineIds = new Dictionary<ElementId, ElementId>();
                  for ( var ii = inlineIds.Count() - 1; ii > -1; ii-- )
                  {
                     var elemId = inlineIds.ElementAt( ii );
                     var elemIdother = inlineRevIds.ElementAt( ii );
                     if ((elemId != null) && (elemId != null))
                        swapinlineIds.Add(elemId, elemIdother);
                  }
                  applychange.SetMapOfInLinePartTypes( swapinlineIds );

                  // Apply the changes
                  FabricationNetworkChangeServiceResult result = applychange.ApplyChange();

                  if (result != FabricationNetworkChangeServiceResult.Success)
                  {
                     message = "There was a problem with the apply change.";
                     return Result.Failed;
                  }

                  doc.Regenerate();

                  tr.Commit();
               }

               return Result.Succeeded;
            }
            else
            {
               // inform user they need to select at least one element
               message = "Please select at least one element.";
            }

            return Result.Failed;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

ConvertToFabrication.cs

//
// (C) Copyright 2003-2011 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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class ConvertToFabrication : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            Document doc = commandData.Application.ActiveUIDocument.Document;

            // get the user selection
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            ICollection<ElementId> collection = uidoc.Selection.GetElementIds();

            if (collection.Count > 0)
            {
               // DesignToFabrication needs an ISet<ElementId>
               ISet<ElementId> selIds = new HashSet<ElementId>();
               foreach (ElementId id in collection)
               {
                  selIds.Add(id);
               }

               using (Transaction tr = new Transaction(doc, "Convert To Fabrication Parts"))
               {
                  tr.Start();

                  FabricationConfiguration config = FabricationConfiguration.GetFabricationConfiguration(doc);

                  // get all loaded fabrication services and attempt to convert the design elements 
                  // to the first loaded service
                  IList<FabricationService> allLoadedServices = config.GetAllLoadedServices();

                  DesignToFabricationConverter converter = new DesignToFabricationConverter(doc);
                  DesignToFabricationConverterResult result = converter.Convert(selIds, allLoadedServices[0].ServiceId);

                  if (result != DesignToFabricationConverterResult.Success)
                  {
                     message = "There was a problem with the conversion.";
                     return Result.Failed;
                  }

                  doc.Regenerate();

                  tr.Commit();
               }

               return Result.Succeeded;
            }
            else
            {
               // inform user they need to select at least one element
               message = "Please select at least one element.";
            }

            return Result.Failed;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

CustomData.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using Autodesk.Revit.UI.Selection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Helper class to report custom data from fabrication part selection.
   /// </summary>
   public class CustomDataHelper
   {
      /// <summary>
      /// Report the custom data.
      /// </summary>
      /// <param name="doc"></param>
      /// <param name="uiDoc"></param>
      /// <param name="setNewValues"></param>
      /// <param name="message"></param>
      /// <returns></returns>
      public static Result ReportCustomData(Document doc, UIDocument uiDoc, bool setNewValues, ref string message)
      {
         FabricationPart fabPart = null;
         FabricationConfiguration config = null;

         try
         {
            // check for a load fabrication config
            config = FabricationConfiguration.GetFabricationConfiguration(doc);

            if (config == null)
            {
               message = "No fabrication configuration loaded.";
               return Result.Failed;
            }

            // pick a fabrication part
            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part to start.");
            fabPart = doc.GetElement(refObj) as FabricationPart;
         }
         catch (Autodesk.Revit.Exceptions.OperationCanceledException)
         {
            return Result.Cancelled;
         }

         if (fabPart == null)
         {
            message = "The selected element is not a fabrication part.";
            return Result.Failed;
         }
         else
         {
            // get custom data from loaded fabrication config
            IList<int> customDataIds = config.GetAllPartCustomData();
            int customDataCount = customDataIds.Count;
            string results = string.Empty;

            // report custom data info
            if (customDataCount > 0)
            {
               StringBuilder resultsBuilder = new StringBuilder();
               resultsBuilder.AppendLine($"Fabrication config contains {customDataCount} custom data entries {Environment.NewLine}");

               foreach (var customDataId in customDataIds)
               {
                  FabricationCustomDataType customDataType = config.GetPartCustomDataType(customDataId);
                  string customDataName = config.GetPartCustomDataName(customDataId);

                  resultsBuilder.AppendLine($"Type: {customDataType.ToString()} Name: {customDataName}");
                  // check custom data exists on selected part
                  if (fabPart.HasCustomData(customDataId))
                  {
                     string fabPartCurrentValue = string.Empty;
                     string fabPartNewValue = string.Empty;

                     switch (customDataType)
                     {
                        case FabricationCustomDataType.Text:

                           fabPartCurrentValue = $"\"{fabPart.GetPartCustomDataText(customDataId)}\"";

                           if (setNewValues)
                           {
                              string installDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
                              fabPart.SetPartCustomDataText(customDataId, installDateTime);
                              fabPartNewValue = installDateTime;
                           }

                           break;
                        case FabricationCustomDataType.Integer:

                           fabPartCurrentValue = fabPart.GetPartCustomDataInteger(customDataId).ToString();

                           if (setNewValues)
                           {
                              int installHours = new Random().Next(1, 10);
                              fabPart.SetPartCustomDataInteger(customDataId, installHours);
                              fabPartNewValue = installHours.ToString();
                           }

                           break;
                        case FabricationCustomDataType.Real:

                           fabPartCurrentValue = $"{fabPart.GetPartCustomDataReal(customDataId):0.##}";

                           if (setNewValues)
                           {
                              double installCost = new Random().NextDouble() * new Random().Next(100, 1000);
                              fabPart.SetPartCustomDataReal(customDataId, installCost);
                              fabPartNewValue = $"{installCost:0.##}";
                           }

                           break;
                     }

                     resultsBuilder.AppendLine("Current custom data entry value = "
                                             + $"{fabPartCurrentValue} {Environment.NewLine}");

                     if (setNewValues)
                     {
                        resultsBuilder.AppendLine("New custom data entry value = "
                                             + $"{fabPartNewValue} {Environment.NewLine}");
                     }
                  }
                  else
                  {
                     resultsBuilder.AppendLine($"Custom data entry is not set on the part {Environment.NewLine}");
                  }
               }
               results = resultsBuilder.ToString();
            }

            TaskDialog td = new TaskDialog("Custom Data")
            {
               MainIcon = TaskDialogIcon.TaskDialogIconInformation,
               TitleAutoPrefix = false,
               MainInstruction = $"{customDataCount} custom data entries found in the loaded fabrication config",
               MainContent = results
            };

            td.Show();
         }

         return Result.Succeeded;
      }
   }
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class GetCustomData : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {

         Document doc = commandData.Application.ActiveUIDocument.Document;
         UIDocument uidoc = commandData.Application.ActiveUIDocument;

         return CustomDataHelper.ReportCustomData(doc, uidoc, false, ref message);
      }
   }

   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class SetCustomData : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {

         Document doc = commandData.Application.ActiveUIDocument.Document;
         UIDocument uidoc = commandData.Application.ActiveUIDocument;
         Result result;

         using (Transaction tr = new Transaction(doc, "Setting Custom Data"))
         {
            tr.Start();
            result = CustomDataHelper.ReportCustomData(doc, uidoc, true, ref message);

            if (result == Result.Succeeded)
            {
               tr.Commit();
            }
            else
            {
               tr.RollBack();
            }
         }

         return result;
      }
   }
}

ExportToMAJ.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.IO;
using System.Reflection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class ExportToMAJ : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uidoc = commandData.Application.ActiveUIDocument;
            var doc = uidoc.Document;
            var elementIds = new HashSet<ElementId>();
            uidoc.Selection.GetElementIds().ToList().ForEach( x => elementIds.Add(x) );

            var hasFabricationParts = false;
            foreach (var elementId in elementIds)
            {
               var part = doc.GetElement(elementId) as FabricationPart;
               if (part != null)
               {
                  hasFabricationParts = true;
                  break;
               }
            }

            if (hasFabricationParts == false)
            {
               message = "Select at least one fabrication part";
               return Result.Failed;
            }

            var callingFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var saveAsDlg = new FileSaveDialog("MAJ Files (*.maj)|*.maj");
            saveAsDlg.InitialFileName = callingFolder + "\\majExport";
            saveAsDlg.Title = "Export To MAJ";
            var result = saveAsDlg.Show();

            if (result == ItemSelectionDialogResult.Canceled)
               return Result.Cancelled;

            string filename = ModelPathUtils.ConvertModelPathToUserVisiblePath(saveAsDlg.GetSelectedModelPath());

            ISet<ElementId> exported = FabricationPart.SaveAsFabricationJob(doc, elementIds, filename, new FabricationSaveJobOptions(true));
            if (exported.Count > 0)
            {
               TaskDialog td = new TaskDialog("Export to MAJ")
               {
                  MainIcon = TaskDialogIcon.TaskDialogIconInformation,
                  TitleAutoPrefix = false,
                  MainInstruction = string.Concat("Export to MAJ was successful - ", exported.Count.ToString(), " Parts written"),
                  MainContent = filename,
                  AllowCancellation = false,
                  CommonButtons = TaskDialogCommonButtons.Ok
               };

               td.Show();
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

ExportToPCF.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.Windows.Forms;
using System.IO;
using System.Reflection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class ExportToPCF : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uidoc = commandData.Application.ActiveUIDocument;
            var doc = uidoc.Document;
            var collection = uidoc.Selection.GetElementIds();
            var hasFabricationPart = false;

            using (var trans = new Transaction(doc, "Change Spool Name"))
            {
               trans.Start();

               foreach (var elementId in collection)
               {
                  var part = doc.GetElement(elementId) as FabricationPart;
                  if (part != null)
                  {
                     hasFabricationPart = true;
                     part.SpoolName = "My Spool";
                  }
               }

               trans.Commit();
            }

            if (hasFabricationPart == false)
            {
               message = "Select at least one fabrication part";
               return Result.Failed;
            }

            var callingFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            var saveAsDlg = new FileSaveDialog("PCF Files (*.pcf)|*.pcf");

            saveAsDlg.InitialFileName = callingFolder + "\\pcfExport";
            saveAsDlg.Title = "Export To PCF";
            var result = saveAsDlg.Show();

            if (result == ItemSelectionDialogResult.Canceled)
               return Result.Cancelled;

            var fabParts = collection.ToList();

            string filename = ModelPathUtils.ConvertModelPathToUserVisiblePath(saveAsDlg.GetSelectedModelPath());

            FabricationUtils.ExportToPCF(doc, fabParts, filename);

            TaskDialog td = new TaskDialog("Export to PCF")
            {
               MainIcon = TaskDialogIcon.TaskDialogIconInformation,
               TitleAutoPrefix = false,
               MainInstruction = "Export to PCF was successful",
               MainContent = filename,
               AllowCancellation = false,
               CommonButtons = TaskDialogCommonButtons.Ok
            };

            td.Show();

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

FabPartGeometry.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.Windows.Forms;
using System.IO;
using System.Reflection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class FabPartGeometry : IExternalCommand
   {
      private IList<Mesh> getMeshes(GeometryElement ge)
      {
         IList<Mesh> rv = new List<Mesh>();
         if (ge != null)
         {
            foreach (GeometryObject g in ge)
            {
               GeometryInstance i = g as GeometryInstance;
               if (i != null)
               {
                  GeometryElement ge2 = i.GetInstanceGeometry();
                  if (ge2 != null)
                     rv = rv.Concat(getMeshes(ge2)).ToList();
               }
               else
               {
                  Mesh mesh = g as Mesh;
                  if (mesh != null && mesh.Vertices.Count > 0)
                     rv.Add(mesh);
               }
            }
         }
         return rv;
      }

      private bool exportMesh(String filename, Mesh mesh)
      {
         bool bSaved = false;

         try
         {
            StreamWriter sout = new StreamWriter(filename, false);
            sout.WriteLine("P1X, P1Y, P1Z, P2X, P2Y, P2Z, P3X, P3Y, P3Z");
            for (int tlp = 0; tlp < mesh.NumTriangles; tlp++)
            {
               MeshTriangle tri = mesh.get_Triangle(tlp);

               XYZ p1 = mesh.Vertices[(int)tri.get_Index(0)];
               XYZ p2 = mesh.Vertices[(int)tri.get_Index(1)];
               XYZ p3 = mesh.Vertices[(int)tri.get_Index(2)];

               String tstr = String.Format("{0:0.000}, {1:0.000}, {2:0.000}, {3:0.000}, {4:0.000}, {5:0.000}, {6:0.000}, {7:0.000}, {8:0.000}", new object[] { p1.X, p1.Y, p1.Z, p2.X, p2.Y, p2.Z, p3.X, p3.Y, p3.Z });
               sout.WriteLine(tstr);
            }
            sout.Close();

            bSaved = true;
         }
         catch (Exception ex)
         {
            MessageBox.Show(ex.Message, "Unable to write file", MessageBoxButtons.OK, MessageBoxIcon.Error);
         }

         return bSaved;
      }


      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uidoc = commandData.Application.ActiveUIDocument;
            var doc = uidoc.Document;

            ISet<ElementId> parts = null;

            using (Transaction tr = new Transaction(doc, "Optimise Preselection"))
            {
               tr.Start();
               ICollection<ElementId> selElems = uidoc.Selection.GetElementIds();
               if (selElems.Count > 0)
               {
                  parts = new HashSet<ElementId>(selElems);
               }

               tr.Commit();
            }

            if (parts == null)
            {
               MessageBox.Show("Select parts to export.");
               return Result.Failed;
            }

            var callingFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            var saveAsDlg = new FileSaveDialog("CSV Files (*.csv)|*.csv");

            saveAsDlg.InitialFileName = callingFolder + "\\geomExport";
            saveAsDlg.Title = "Save Part Geometry As";
            var result = saveAsDlg.Show();

            if (result == ItemSelectionDialogResult.Canceled)
               return Result.Cancelled;

            string filename = ModelPathUtils.ConvertModelPathToUserVisiblePath(saveAsDlg.GetSelectedModelPath());
            string ext = Path.GetExtension(filename);
            filename = Path.GetFileNameWithoutExtension(filename);

            int partcount = 1, exported = 0;

            foreach (ElementId eid in parts)
            {
               // get all rods and kist with rods 
               FabricationPart part = doc.GetElement(eid) as FabricationPart;
               if (part != null)
               {
                  Options options = new Options();
                  options.DetailLevel = ViewDetailLevel.Coarse;

                  IList<Mesh> main = getMeshes(part.get_Geometry(options));
                  IList<Mesh> ins = getMeshes(part.GetInsulationLiningGeometry());

                  int mlp = 0;
                  foreach (Mesh mesh in main)
                  {
                     String file = String.Concat( filename, partcount.ToString(), "-main-", (++mlp).ToString(), ext);
                     if (exportMesh(file, mesh))
                        exported++;
                  }

                  int ilp = 0;
                  foreach (Mesh mesh in ins)
                  {
                     String file = String.Concat(filename, partcount.ToString(), "-ins-", (++ilp).ToString(), ext);
                     if (exportMesh(file, mesh))
                        exported++;
                  }
               }
               partcount++;
            }

            String res = (exported > 0) ? "Export was successful" : "Nothing was exported";
            String manywritten = String.Format("{0} Parts were exported", exported);

            TaskDialog td = new TaskDialog("Export Part Mesh Geometry")
            {
               MainIcon = TaskDialogIcon.TaskDialogIconInformation,
               TitleAutoPrefix = false,
               MainInstruction = res,
               MainContent = manywritten,
               AllowCancellation = false,
               CommonButtons = TaskDialogCommonButtons.Ok
            };

            td.Show();

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

FabricationPartLayout.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.Windows.Forms;
using System.Collections;
using System.Collections.Generic;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.Linq;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class FabricationPartLayout : IExternalCommand
   {
      Document m_doc { get; set; }
      IList<FabricationService> m_services { get; set; }

      // kept in sync
      IList<int> m_materialIds { get; set; }
      IList<string> m_materialGroups { get; set; }
      IList<string> m_materialNames { get; set; }

      // kept in sync
      IList<int> m_specIds { get; set; }
      IList<string> m_specGroups { get; set; }
      IList<string> m_specNames { get; set; }

      // kept in sync
      IList<int> m_insSpecIds { get; set; }
      IList<string> m_insSpecGroups { get; set; }
      IList<string> m_insSpecNames { get; set; }

      // kept in sync
      IList<int> m_connIds { get; set; }
      IList<string> m_connGroups { get; set; }
      IList<string> m_connNames { get; set; }

      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            m_doc = commandData.Application.ActiveUIDocument.Document;

            FilteredElementCollector cl = new FilteredElementCollector(m_doc);
            cl.OfClass(typeof(Level));
            IList<Element> levels = cl.ToElements();
            Level levelOne = null;
            foreach (Level level in levels)
            {
               if (level != null && level.Name.Equals("Level 1"))
               {
                  levelOne = level;
                  break;
               }
            }

            if (levelOne == null)
               return Result.Failed;

            // locate the AHU in the model - should only be one instance in the model.
            FilteredElementCollector c2 = new FilteredElementCollector(m_doc);
            c2.OfClass(typeof(FamilyInstance));
            IList<Element> families = c2.ToElements();
            if (families.Count != 1)
               return Result.Failed;

            FamilyInstance fam_ahu = families[0] as FamilyInstance;
            if (fam_ahu == null)
               return Result.Failed;

            // locate the proper connector - rectangular 40"x40" outlet
            Connector conn_ahu = null;
            ConnectorSet conns_ahu = fam_ahu.MEPModel.ConnectorManager.UnusedConnectors;
            double lengthInFeet = 40.0 / 12.0;
            foreach (Connector conn in conns_ahu)
            {
               // Revit units measured in feet, so dividing the width and height by 12

               if (conn.Shape == ConnectorProfileType.Rectangular && conn.Width == lengthInFeet && conn.Height == lengthInFeet)
                  conn_ahu = conn;
            }

            if (conn_ahu == null)
               return Result.Failed;

            // get the current fabrication configuration
            FabricationConfiguration config = FabricationConfiguration.GetFabricationConfiguration(m_doc);
            if (config == null)
               return Result.Failed;

            // create materials look-up tables
            GetMaterials(config);

            // create specs look-up tables
            GetSpecs(config);

            // create insulation specs look-up tables
            GetInsulationSpecs(config);

            // create fabrication configuration look-up tables
            GetFabricationConnectors(config);

            // get all the loaded services
            m_services = config.GetAllLoadedServices();
            if (m_services.Count == 0)
               return Result.Failed;

            FabricationService havcService = m_services.FirstOrDefault(x => x.Name.Contains("HVAC"));
            FabricationService pipeService = m_services.FirstOrDefault(x => x.Name.Contains("Plumbing"));

            FabricationServiceButton bt_transition = locateButton(havcService, 0, "Transition");
            FabricationServiceButton bt_sqBend = locateButton(havcService, 0, "Square Bend");
            FabricationServiceButton bt_tap = locateButton(havcService, 0, "Tap");
            FabricationServiceButton bt_rectStraight = locateButton(havcService, 0, "Straight");
            FabricationServiceButton bt_radBend = locateButton(havcService, 0, "Radius Bend");
            FabricationServiceButton bt_flatShoe = locateButton(havcService, 1, "Flat Shoe");
            FabricationServiceButton bt_tube = locateButton(havcService, 1, "Tube");
            FabricationServiceButton bt_90bend = locateButton(havcService, 1, "Bend - 90");
            FabricationServiceButton bt_45bend = locateButton(havcService, 1, "Bend - 45");
            FabricationServiceButton bt_rectTee = locateButton(havcService, 0, "Tee");
            FabricationServiceButton bt_sqToRound = locateButton(havcService, 0, "Square to Round");
            FabricationServiceButton bt_reducer = locateButton(havcService, 1, "Reducer - C");
            FabricationServiceButton bt_curvedBoot = locateButton(havcService, 1, "Curved Boot");
            FabricationServiceButton bt_hangerBearer = locateButton(havcService, 4, "Rectangular Bearer");
            FabricationServiceButton bt_hangerRound = locateButton(havcService, 4, "Round Duct Hanger");

            FabricationServiceButton bt_valve = locateButton(pipeService, 2, "Globe Valve");
            FabricationServiceButton bt_groovedPipe = locateButton(pipeService, 1, "Type L Hard Copper");
            FabricationServiceButton bt_90elbow = locateButton(pipeService, 1, "No610 - 90 Elbow");

            using (Transaction tr = new Transaction(m_doc, "Create Layout"))
            {
               tr.Start();

               // connect a square bend to the ahu
               FabricationPart pt_sqBend1 = FabricationPart.Create(m_doc, bt_sqBend, 0, levelOne.Id);
               Connector conn1_sqBend1 = GetPrimaryConnector(pt_sqBend1.ConnectorManager);
               Connector conn2_sqBend1 = GetSecondaryConnector(pt_sqBend1.ConnectorManager);
               SizeAlignCoupleConnect(conn1_sqBend1, conn_ahu, 3.0 * Math.PI / 2.0);

               // add a 15' straight to the square bend
               FabricationPart pt_rectStraight1 = CreateStraightPart(bt_rectStraight, 0, levelOne.Id, 15.0);
               Connector conn1_straight1 = GetPrimaryConnector(pt_rectStraight1.ConnectorManager);
               Connector conn2_straight1 = GetSecondaryConnector(pt_rectStraight1.ConnectorManager);
               SizeAlignCoupleConnect(conn1_straight1, conn2_sqBend1, 0);

               // add two Rectangular Bearer hangers at 5' to each end of the 15' straight
               FabricationPart.CreateHanger(m_doc, bt_hangerBearer, pt_rectStraight1.Id, conn1_straight1, 5.0, true);
               FabricationPart.CreateHanger(m_doc, bt_hangerBearer, pt_rectStraight1.Id, conn2_straight1, 5.0, true);

               // connect a tap to the straight half way along
               FabricationPart pt_tap1 = FabricationPart.Create(m_doc, bt_tap, 0, levelOne.Id);
               Connector conn1_tap1 = GetPrimaryConnector(pt_tap1.ConnectorManager);
               Connector conn2_tap1 = GetSecondaryConnector(pt_tap1.ConnectorManager);
               FabricationPart.PlaceAsTap(m_doc, conn1_tap1, conn1_straight1, 7.5, 3.0 * Math.PI / 2.0, 0);

               // connect a square to round to the tap, with an outlet of 10"
               FabricationPart pt_sqToRound1 = FabricationPart.Create(m_doc, bt_sqToRound, 0, levelOne.Id);
               Connector conn1_sqToRound1 = GetPrimaryConnector(pt_sqToRound1.ConnectorManager);
               Connector conn2_sqToRound1 = GetSecondaryConnector(pt_sqToRound1.ConnectorManager);
               conn2_sqToRound1.Radius = 5.0 / 12.0; // convert to feet
               SizeAlignCoupleConnect(conn1_sqToRound1, conn2_tap1, 0);

               // connect a bend 90, based on the condition (converting 10" into feet)
               FabricationPart pt_90bend1 = FabricationPart.Create(m_doc, bt_90bend, 10.0 / 12.0, 10.0 / 12.0, levelOne.Id);
               Connector conn1_90bend1 = GetPrimaryConnector(pt_90bend1.ConnectorManager);
               Connector conn2_90bend1 = GetSecondaryConnector(pt_90bend1.ConnectorManager);
               SizeAlignCoupleConnect(conn1_90bend1, conn2_sqToRound1, 0);

               FabricationPart pt_90bend2 = FabricationPart.Create(m_doc, bt_90bend, 0, levelOne.Id);
               Connector conn1_90bend2 = GetPrimaryConnector(pt_90bend2.ConnectorManager);
               Connector conn2_90bend2 = GetSecondaryConnector(pt_90bend2.ConnectorManager);
               SizeAlignCoupleConnect(conn1_90bend2, conn2_90bend1, 0);

               // now let's add a tube in
               FabricationPart pt_tube1 = CreateStraightPart(bt_tube, 0, levelOne.Id, 5.0);
               Connector conn1_tube1 = GetPrimaryConnector(pt_tube1.ConnectorManager);
               Connector conn2_tube1 = GetSecondaryConnector(pt_tube1.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube1, conn2_90bend2, 0);

               // and now add a square to round, connecting by round end
               // change the spec to undefined, change material, add insulation, change a connector
               FabricationPart pt_sqToRound2 = FabricationPart.Create(m_doc, bt_sqToRound, 0, levelOne.Id);
               Connector conn1_sqToRound2 = GetPrimaryConnector(pt_sqToRound2.ConnectorManager);
               Connector conn2_sqToRound2 = GetSecondaryConnector(pt_sqToRound2.ConnectorManager); // round end
               SizeAlignCoupleConnect(conn2_sqToRound2, conn2_tube1, 0);

               // set the spec to none           
               pt_sqToRound2.Specification = 0; // none         

               // now locate specific material, insulation spec and fabrication connector
               //int specId = config.LocateSpecification("Ductwork", "+6 WG");
               int materialId = config.LocateMaterial("Ductwork", "Mild Steel");
               int insSpecId = config.LocateInsulationSpecification("Ductwork", "Acoustic Liner 1''");
               int connId = config.LocateFabricationConnector("Duct - S&D", "S&D", ConnectorDomainType.Undefined, ConnectorProfileType.Rectangular);

               // now set the material, insulation spec and one of the connectors
               if (materialId >= 0)
                  pt_sqToRound2.Material = materialId;
               if (insSpecId >= 0)
                  pt_sqToRound2.InsulationSpecification = insSpecId;
               if (connId >= 0)
                  conn1_sqToRound2.GetFabricationConnectorInfo().BodyConnectorId = connId;

               // connect a 2' 6" transition to the square bend
               FabricationPart pt_transition1 = FabricationPart.Create(m_doc, bt_transition, 0, levelOne.Id);
               SetDimValue(pt_transition1, "Length", 2.5); // set length of transition to 2' 6"
               Connector conn1_transition1 = GetPrimaryConnector(pt_transition1.ConnectorManager);
               Connector conn2_transition1 = GetSecondaryConnector(pt_transition1.ConnectorManager);
               conn2_transition1.Width = 2.0;
               conn2_transition1.Height = 2.0;
               SizeAlignCoupleConnect(conn1_transition1, conn2_straight1, 0);

               // connect a rising square bend to the transition
               FabricationPart pt_sqBend2 = FabricationPart.Create(m_doc, bt_sqBend, 0, levelOne.Id);
               Connector conn1_sqBend2 = GetPrimaryConnector(pt_sqBend2.ConnectorManager);
               Connector conn2_sqBend2 = GetSecondaryConnector(pt_sqBend2.ConnectorManager);
               SizeAlignCoupleConnect(conn1_sqBend2, conn2_transition1, 0);

               // connect a 4' 5" straight to the square bend
               FabricationPart pt_rectStraight2 = CreateStraightPart(bt_rectStraight, 0, levelOne.Id, (4.0 + (5.0 / 12.0)));
               Connector conn1_straight2 = GetPrimaryConnector(pt_rectStraight2.ConnectorManager);
               Connector conn2_straight2 = GetSecondaryConnector(pt_rectStraight2.ConnectorManager);
               SizeAlignCoupleConnect(conn1_straight2, conn2_sqBend2, 0);

               // connect a square bend to the straight
               FabricationPart pt_sqBend3 = FabricationPart.Create(m_doc, bt_sqBend, 0, levelOne.Id);
               Connector conn1_sqBend3 = GetPrimaryConnector(pt_sqBend3.ConnectorManager);
               Connector conn2_sqBend3 = GetSecondaryConnector(pt_sqBend3.ConnectorManager);
               SizeAlignCoupleConnect(conn1_sqBend3, conn2_straight2, Math.PI);

               // add a 5' straight 
               FabricationPart pt_rectStraight3 = CreateStraightPart(bt_rectStraight, 0, levelOne.Id, 5.0);
               Connector conn1_straight3 = GetPrimaryConnector(pt_rectStraight3.ConnectorManager);
               Connector conn2_straight3 = GetSecondaryConnector(pt_rectStraight3.ConnectorManager);
               SizeAlignCoupleConnect(conn1_straight3, conn2_sqBend3, 0);

               //add a Bearer hanger in middle of straight3 
               FabricationPart.CreateHanger(m_doc, bt_hangerBearer, pt_rectStraight3.Id, conn1_straight3, 2.5, true);


               // add a 45 degree radius bend 
               FabricationPart pt_radBend1 = FabricationPart.Create(m_doc, bt_radBend, 0, levelOne.Id);
               Connector conn1_radBend1 = GetPrimaryConnector(pt_radBend1.ConnectorManager);
               Connector conn2_radBend1 = GetSecondaryConnector(pt_radBend1.ConnectorManager);
               SizeAlignCoupleConnect(conn1_radBend1, conn2_straight3, 3.0 * Math.PI / 2.0);
               SetDimValue(pt_radBend1, "Angle", Math.PI / 4.0);

               // add a 1' 8" straight 
               FabricationPart pt_rectStraight4 = CreateStraightPart(bt_rectStraight, 0, levelOne.Id, 1.0 + (8.0 / 12.0));
               Connector conn1_straight4 = GetPrimaryConnector(pt_rectStraight4.ConnectorManager);
               Connector conn2_straight4 = GetSecondaryConnector(pt_rectStraight4.ConnectorManager);
               SizeAlignCoupleConnect(conn1_straight4, conn2_radBend1, 0);

               // add a 45 degree radius bend 
               FabricationPart pt_radBend2 = FabricationPart.Create(m_doc, bt_radBend, 0, levelOne.Id);
               Connector conn1_radBend2 = GetPrimaryConnector(pt_radBend2.ConnectorManager);
               Connector conn2_radBend2 = GetSecondaryConnector(pt_radBend2.ConnectorManager);
               SizeAlignCoupleConnect(conn1_radBend2, conn2_straight4, Math.PI);
               SetDimValue(pt_radBend2, "Angle", Math.PI / 4.0);

               // add a 5' straight 
               FabricationPart pt_rectStraight5 = CreateStraightPart(bt_rectStraight, 0, levelOne.Id, 5.0);
               Connector conn1_straight5 = GetPrimaryConnector(pt_rectStraight5.ConnectorManager);
               Connector conn2_straight5 = GetSecondaryConnector(pt_rectStraight5.ConnectorManager);
               SizeAlignCoupleConnect(conn1_straight5, conn2_radBend2, 0);

               //add a Bearer hanger in middle of straight5 
               FabricationPart.CreateHanger(m_doc, bt_hangerBearer, pt_rectStraight5.Id, conn1_straight5, 2.5, true);

               // add a 2' 6" straight 
               FabricationPart pt_rectStraight6 = CreateStraightPart(bt_rectStraight, 0, levelOne.Id, 2.5);
               Connector conn1_straight6 = GetPrimaryConnector(pt_rectStraight6.ConnectorManager);
               Connector conn2_straight6 = GetSecondaryConnector(pt_rectStraight6.ConnectorManager);
               SizeAlignCoupleConnect(conn1_straight6, conn2_straight5, 0);

               // add an 8" tap to the last straight - half way along the straight - using parameter to set the product entry
               // could also set the radius directly.
               FabricationPart pt_flatShoe1 = FabricationPart.Create(m_doc, bt_flatShoe, 0, levelOne.Id);
               Parameter prodEntry_flatShoe1 = pt_flatShoe1.get_Parameter(BuiltInParameter.FABRICATION_PRODUCT_ENTRY);
               prodEntry_flatShoe1.Set("8''");
               Connector conn1_flatShoe1 = GetPrimaryConnector(pt_flatShoe1.ConnectorManager);
               Connector conn2_flatShoe1 = GetSecondaryConnector(pt_flatShoe1.ConnectorManager);
               FabricationPart.PlaceAsTap(m_doc, conn1_flatShoe1, conn1_straight6, 1.25, Math.PI, 0);

               // add a 16' 8 long tube
               double length_tube2 = 16.0 + (8.0 / 12.0);
               FabricationPart pt_tube2 = CreateStraightPart(bt_tube, 0, levelOne.Id, length_tube2);
               Connector conn1_tube2 = GetPrimaryConnector(pt_tube2.ConnectorManager);
               Connector conn2_tube2 = GetSecondaryConnector(pt_tube2.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube2, conn2_flatShoe1, 0);
               //add 3 hangers for tube2 , with specified button condition
               for (int i = 0; i < 3; i++)
               {
                  FabricationPart.CreateHanger(m_doc, bt_hangerRound, 1, pt_tube2.Id, conn1_tube2, (i + 1) * length_tube2 / 4, true);
               }

               // add a 90 degree bend
               FabricationPart pt_90bend3 = FabricationPart.Create(m_doc, bt_90bend, 0, levelOne.Id);
               Connector conn1_90bend3 = GetPrimaryConnector(pt_90bend3.ConnectorManager);
               Connector conn2_90bend3 = GetSecondaryConnector(pt_90bend3.ConnectorManager);
               SizeAlignCoupleConnect(conn1_90bend3, conn2_tube2, Math.PI);

               // add a 10' long tube
               FabricationPart pt_tube3 = CreateStraightPart(bt_tube, 0, levelOne.Id, 10.0);
               Connector conn1_tube3 = GetPrimaryConnector(pt_tube3.ConnectorManager);
               Connector conn2_tube3 = GetSecondaryConnector(pt_tube3.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube3, conn2_90bend3, 0);

               //add one hangers in middle of tube3, by default button condition 
               FabricationPart.CreateHanger(m_doc, bt_hangerRound, pt_tube3.Id, conn1_tube3, 5.0, true);

               // add a 45 degree bend 
               FabricationPart pt_45Bend1 = FabricationPart.Create(m_doc, bt_45bend, 0, levelOne.Id);
               Connector conn1_45Bend1 = GetPrimaryConnector(pt_45Bend1.ConnectorManager);
               Connector conn2_45Bend1 = GetSecondaryConnector(pt_45Bend1.ConnectorManager);
               SizeAlignCoupleConnect(conn1_45Bend1, conn2_tube3, Math.PI);

               // add a 2' long tube
               FabricationPart pt_tube4 = CreateStraightPart(bt_tube, 0, levelOne.Id, 2.0);
               Connector conn1_tube4 = GetPrimaryConnector(pt_tube4.ConnectorManager);
               Connector conn2_tube4 = GetSecondaryConnector(pt_tube4.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube4, conn2_45Bend1, 0);

               // add a 45 degree bend 
               FabricationPart pt_45Bend2 = FabricationPart.Create(m_doc, bt_45bend, 0, levelOne.Id);
               Connector conn1_45Bend2 = GetPrimaryConnector(pt_45Bend2.ConnectorManager);
               Connector conn2_45Bend2 = GetSecondaryConnector(pt_45Bend2.ConnectorManager);
               SizeAlignCoupleConnect(conn1_45Bend2, conn2_tube4, Math.PI);

               // add a 10' long tube
               FabricationPart pt_tube5 = CreateStraightPart(bt_tube, 0, levelOne.Id, 10.0);
               Connector conn1_tube5 = GetPrimaryConnector(pt_tube5.ConnectorManager);
               Connector conn2_tube5 = GetSecondaryConnector(pt_tube5.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube5, conn2_45Bend2, 0);

               //add one hangers in middle of tube5, by default button condition 
               FabricationPart.CreateHanger(m_doc, bt_hangerRound, pt_tube5.Id, conn1_tube5, 5.0, true);

               // now go back to the straight (pt_rectStraight5) with the tap and add a square bend
               FabricationPart pt_sqBend4 = FabricationPart.Create(m_doc, bt_sqBend, 0, levelOne.Id);
               Connector conn1_sqBend4 = GetPrimaryConnector(pt_sqBend4.ConnectorManager);
               Connector conn2_sqBend4 = GetSecondaryConnector(pt_sqBend4.ConnectorManager);
               SizeAlignCoupleConnect(conn1_sqBend4, conn2_straight6, Math.PI);

               // add a 5' straight
               FabricationPart pt_rectStraight7 = CreateStraightPart(bt_rectStraight, 0, levelOne.Id, 5.0);
               Connector conn1_straight7 = GetPrimaryConnector(pt_rectStraight7.ConnectorManager);
               Connector conn2_straight7 = GetSecondaryConnector(pt_rectStraight7.ConnectorManager);
               SizeAlignCoupleConnect(conn1_straight7, conn2_sqBend4, 0);

               //add a Bearer hanger in middle of straight7, by default condition
               FabricationPart.CreateHanger(m_doc, bt_hangerBearer, pt_rectStraight7.Id, conn1_straight7, 2.5, true);

               // add a modified tee
               FabricationPart pt_rectTee1 = FabricationPart.Create(m_doc, bt_rectTee, 0, levelOne.Id);

               // Set the size prior to connecting the part. Template parts with more than 2 connectors will disable editing of sizes once one of the connectors is connected to something.
               SetDimValue(pt_rectTee1, "Right Width", 16.0 / 12.0); // set right width dimension to 16" (converted to feet)
               SetDimValue(pt_rectTee1, "Btm Width", 20.0 / 12.0);   // set bottom width dimension to 20" (converted to feet)

               Connector conn1_rectTee1 = GetPrimaryConnector(pt_rectTee1.ConnectorManager);
               Connector conn2_rectTee1 = GetSecondaryConnector(pt_rectTee1.ConnectorManager);
               Connector conn3_rectTee1 = GetFirstNonPrimaryOrSecondaryConnector(pt_rectTee1.ConnectorManager);
               SizeAlignCoupleConnect(conn3_rectTee1, conn2_straight7, Math.PI);

               // add a square to round to the tee (conn2)
               FabricationPart pt_sqToRound3 = FabricationPart.Create(m_doc, bt_sqToRound, 0, levelOne.Id);
               Connector conn1_sqToRound3 = GetPrimaryConnector(pt_sqToRound3.ConnectorManager);
               Connector conn2_sqToRound3 = GetSecondaryConnector(pt_sqToRound3.ConnectorManager);
               SizeAlignCoupleConnect(conn1_sqToRound3, conn2_rectTee1, 0);
               SetDimValue(pt_sqToRound3, "Length", 1.0 + (8.5 / 12.0));   // set length dimension to 1' 8 1/2" (converted to feet)
               SetDimValue(pt_sqToRound3, "Diameter", 1.0);                // set diameter dimension to 1'

               // add a 22' 4" long tube
               double length_tube6 = 22.0 + (4.0 / 12.0);
               FabricationPart pt_tube6 = CreateStraightPart(bt_tube, 0, levelOne.Id, length_tube6);
               Connector conn1_tube6 = GetPrimaryConnector(pt_tube6.ConnectorManager);
               Connector conn2_tube6 = GetSecondaryConnector(pt_tube6.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube6, conn2_sqToRound3, 0);

               //add 3 hangers for tube6, by default condition
               for (int i = 0; i < 3; i++)
               {
                  FabricationPart.CreateHanger(m_doc, bt_hangerRound, 1, pt_tube6.Id, conn1_tube6, (i + 1) * length_tube6 / 4, true);
               }

               // add a reducer, reducing to 8"
               FabricationPart pt_reducer1 = FabricationPart.Create(m_doc, bt_reducer, 0, levelOne.Id);
               Connector conn1_reducer1 = GetPrimaryConnector(pt_reducer1.ConnectorManager);
               Connector conn2_reducer1 = GetSecondaryConnector(pt_reducer1.ConnectorManager);
               SizeAlignCoupleConnect(conn1_reducer1, conn2_tube6, 0);
               Parameter prodEntry_reducer1 = pt_reducer1.get_Parameter(BuiltInParameter.FABRICATION_PRODUCT_ENTRY);
               prodEntry_reducer1.Set("12''x8''");

               // add a 10' long tube
               FabricationPart pt_tube7 = CreateStraightPart(bt_tube, 0, levelOne.Id, 10.0);
               Connector conn1_tube7 = GetPrimaryConnector(pt_tube7.ConnectorManager);
               Connector conn2_tube7 = GetSecondaryConnector(pt_tube7.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube7, conn2_reducer1, 0);

               //add one hangers in middle of tube7, by default button condition 
               FabricationPart.CreateHanger(m_doc, bt_hangerRound, pt_tube7.Id, conn1_tube7, 5.0, true);

               // add a curved boot tap to the 22' 4" tube (pt_tube6) 1' 2" from the end, reducing to 8"
               FabricationPart pt_curvedBoot1 = FabricationPart.Create(m_doc, bt_curvedBoot, 0, levelOne.Id);
               Parameter prodEntry_curvedBoot1 = pt_curvedBoot1.get_Parameter(BuiltInParameter.FABRICATION_PRODUCT_ENTRY);
               prodEntry_curvedBoot1.Set("12''x8''");
               Connector conn1_curvedBoot1 = GetPrimaryConnector(pt_curvedBoot1.ConnectorManager);
               Connector conn2_curvedBoot1 = GetSecondaryConnector(pt_curvedBoot1.ConnectorManager);
               FabricationPart.PlaceAsTap(m_doc, conn1_curvedBoot1, conn2_tube6, 1.0 + (2.0 / 12.0), Math.PI, Math.PI);

               // add a 16' 8" long tube to the curved boot
               double length_tube8 = 16.0 + (8.0 / 12.0);
               FabricationPart pt_tube8 = CreateStraightPart(bt_tube, 0, levelOne.Id, length_tube8);
               Connector conn1_tube8 = GetPrimaryConnector(pt_tube8.ConnectorManager);
               Connector conn2_tube8 = GetSecondaryConnector(pt_tube8.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube8, conn2_curvedBoot1, 0);
               //add 3 hangers for tube8, with specified condition 
               for (int i = 0; i < 3; i++)
               {
                  FabricationPart.CreateHanger(m_doc, bt_hangerRound, 0, pt_tube8.Id, conn1_tube8, (i + 1) * length_tube8 / 4, true);
               }

               // going back to the modified tee
               // add a square to round to the tee (conn2)
               FabricationPart pt_sqToRound4 = FabricationPart.Create(m_doc, bt_sqToRound, 0, levelOne.Id);
               Connector conn1_sqToRound4 = GetPrimaryConnector(pt_sqToRound4.ConnectorManager);
               Connector conn2_sqToRound4 = GetSecondaryConnector(pt_sqToRound4.ConnectorManager);
               SizeAlignCoupleConnect(conn1_sqToRound4, conn1_rectTee1, 0);
               SetDimValue(pt_sqToRound4, "Length", 1.0 + (8.5 / 12.0));  // set length dimension to 1' 8 1/2" (converted to feet)
               SetDimValue(pt_sqToRound4, "Diameter", 1.0);               // set diameter dimension to 1'

               // add a 10' long tube
               FabricationPart pt_tube9 = CreateStraightPart(bt_tube, 0, levelOne.Id, 10.0);
               Connector conn1_tube9 = GetPrimaryConnector(pt_tube9.ConnectorManager);
               Connector conn2_tube9 = GetSecondaryConnector(pt_tube9.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube9, conn2_sqToRound4, 0);

               //add one hangers in middle of tube9, by default button condition 
               FabricationPart.CreateHanger(m_doc, bt_hangerRound, pt_tube9.Id, conn1_tube9, 5.0, true);

               // add a curved boot to the tube 3/4 way along, reducing to 10"
               FabricationPart pt_curvedBoot2 = FabricationPart.Create(m_doc, bt_curvedBoot, 0, levelOne.Id);
               Parameter prodEntry_curvedBoot2 = pt_curvedBoot2.get_Parameter(BuiltInParameter.FABRICATION_PRODUCT_ENTRY);
               prodEntry_curvedBoot2.Set("12''x8''");
               Connector conn1_curvedBoot2 = GetPrimaryConnector(pt_curvedBoot2.ConnectorManager);
               Connector conn2_curvedBoot2 = GetSecondaryConnector(pt_curvedBoot2.ConnectorManager);
               FabricationPart.PlaceAsTap(m_doc, conn1_curvedBoot2, conn1_tube9, 7.5, Math.PI, 0);

               // add 8' 1" long tube to the curved boot
               double length_tube10 = 8.0 + (1.0 / 12.0);
               FabricationPart pt_tube10 = CreateStraightPart(bt_tube, 0, levelOne.Id, length_tube10);
               Connector conn1_tube10 = GetPrimaryConnector(pt_tube10.ConnectorManager);
               Connector conn2_tube10 = GetSecondaryConnector(pt_tube10.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube10, conn2_curvedBoot2, 0);

               //add one hangers in middle of tube10, by default button condition 
               FabricationPart.CreateHanger(m_doc, bt_hangerRound, pt_tube10.Id, conn1_tube10, length_tube10 / 2, true);

               // add a 45 degree bend 
               FabricationPart pt_45Bend3 = FabricationPart.Create(m_doc, bt_45bend, 0, levelOne.Id);
               Connector conn1_45Bend3 = GetPrimaryConnector(pt_45Bend3.ConnectorManager);
               Connector conn2_45Bend3 = GetSecondaryConnector(pt_45Bend3.ConnectorManager);
               SizeAlignCoupleConnect(conn1_45Bend3, conn2_tube10, 0);

               // add 20' long tube                
               FabricationPart pt_tube11 = CreateStraightPart(bt_tube, 0, levelOne.Id, 20.0);
               Connector conn1_tube11 = GetPrimaryConnector(pt_tube11.ConnectorManager);
               Connector conn2_tube11 = GetSecondaryConnector(pt_tube11.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube11, conn2_45Bend3, 0);

               // add a 45 degree bend 
               FabricationPart pt_45Bend4 = FabricationPart.Create(m_doc, bt_45bend, 0, levelOne.Id);
               Connector conn1_45Bend4 = GetPrimaryConnector(pt_45Bend4.ConnectorManager);
               Connector conn2_45Bend4 = GetSecondaryConnector(pt_45Bend4.ConnectorManager);
               SizeAlignCoupleConnect(conn1_45Bend4, conn2_tube11, 0);

               // add 1' 8" long tube 
               FabricationPart pt_tube12 = CreateStraightPart(bt_tube, 0, levelOne.Id, 1.0 + (8.0 / 12.0));
               Connector conn1_tube12 = GetPrimaryConnector(pt_tube12.ConnectorManager);
               Connector conn2_tube12 = GetSecondaryConnector(pt_tube12.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube12, conn2_45Bend4, 0);

               // add a reducer (to 10") on the tube with the curved boot (pt_tube12)
               FabricationPart pt_reducer2 = FabricationPart.Create(m_doc, bt_reducer, 0, levelOne.Id);
               Connector conn1_reducer2 = GetPrimaryConnector(pt_reducer2.ConnectorManager);
               Connector conn2_reducer2 = GetSecondaryConnector(pt_reducer2.ConnectorManager);
               SizeAlignCoupleConnect(conn1_reducer2, conn2_tube9, 0);
               Parameter prodEntry_reducer2 = pt_reducer2.get_Parameter(BuiltInParameter.FABRICATION_PRODUCT_ENTRY);
               prodEntry_reducer2.Set("12''x10''");

               // add a 10' long tube
               FabricationPart pt_tube13 = CreateStraightPart(bt_tube, 0, levelOne.Id, 10.0);
               Connector conn1_tube13 = GetPrimaryConnector(pt_tube13.ConnectorManager);
               Connector conn2_tube13 = GetSecondaryConnector(pt_tube13.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube13, conn2_reducer2, 0);

               //add one hangers for tube13, by default button condition 
               FabricationPart.CreateHanger(m_doc, bt_hangerRound, pt_tube13.Id, conn1_tube13, 5.0, true);


               // add a 90 bend, going 45 degrees down
               FabricationPart pt_90bend4 = FabricationPart.Create(m_doc, bt_90bend, 0, levelOne.Id);
               Connector conn1_90bend4 = GetPrimaryConnector(pt_90bend4.ConnectorManager);
               Connector conn2_90bend4 = GetSecondaryConnector(pt_90bend4.ConnectorManager);
               SizeAlignCoupleConnect(conn1_90bend4, conn2_tube13, 3.0 * Math.PI / 4.0);

               // add a 1' 2.5" long tube
               FabricationPart pt_tube14 = CreateStraightPart(bt_tube, 0, levelOne.Id, 1.0 + (2.5 / 12.0));
               Connector conn1_tube14 = GetPrimaryConnector(pt_tube14.ConnectorManager);
               Connector conn2_tube14 = GetSecondaryConnector(pt_tube14.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube14, conn2_90bend4, 0);

               // add a 45 bend
               FabricationPart pt_45bend5 = FabricationPart.Create(m_doc, bt_45bend, 0, levelOne.Id);
               Connector conn1_45bend5 = GetPrimaryConnector(pt_45bend5.ConnectorManager);
               Connector conn2_45bend5 = GetSecondaryConnector(pt_45bend5.ConnectorManager);
               SizeAlignCoupleConnect(conn1_45bend5, conn2_tube14, Math.PI / 2.0);

               // add a 20' long tube
               FabricationPart pt_tube15 = CreateStraightPart(bt_tube, 0, levelOne.Id, 20.0);
               Connector conn1_tube15 = GetPrimaryConnector(pt_tube15.ConnectorManager);
               Connector conn2_tube15 = GetSecondaryConnector(pt_tube15.ConnectorManager);
               SizeAlignCoupleConnect(conn1_tube15, conn2_45bend5, 0);
               //add 4 hangers for tube15, by default button condition 
               for (int i = 0; i < 4; i++)
               {
                  FabricationPart.CreateHanger(m_doc, bt_hangerRound, pt_tube15.Id, conn1_tube15, (i + 1) * (20.0 / 5), true);
               }

               // now let's place a 6" valve by its insertion point in free space
               FabricationPart pt_valve1 = FabricationPart.Create(m_doc, bt_valve, 1, levelOne.Id);
               Parameter prodEntry_pt_valve1 = pt_valve1.get_Parameter(BuiltInParameter.FABRICATION_PRODUCT_ENTRY);
               prodEntry_pt_valve1.Set("6''");
               FabricationPart.AlignPartByInsertionPoint(m_doc, pt_valve1.Id, new XYZ(16, -10, 0), 0, 0, 0, FabricationPartJustification.Middle, null);
               m_doc.Regenerate();
               Connector conn2_valve1 = GetSecondaryConnector(pt_valve1.ConnectorManager);

               // add 10' copper pipe to the valve
               FabricationPart pt_pipe1 = CreateStraightPart(bt_groovedPipe, 0, levelOne.Id, 10.0);
               Connector conn1_pipe1 = GetPrimaryConnector(pt_pipe1.ConnectorManager);
               Connector conn2_pipe1 = GetSecondaryConnector(pt_pipe1.ConnectorManager);
               SizeAlignSlopeJustifyCoupleConnect(conn1_pipe1, conn2_valve1, 0, 0, FabricationPartJustification.Middle);

               // insert a valve into the middle of the copper pipe - it will size automatically
               XYZ pipe1_pos = (conn1_pipe1.Origin + conn2_pipe1.Origin) / 2.0;
               FabricationPart pt_valve2 = FabricationPart.Create(m_doc, bt_valve, 1, levelOne.Id);
               FabricationPart.AlignPartByInsertionPointAndCutInToStraight(m_doc, pt_pipe1.Id, pt_valve2.Id, pipe1_pos, Math.PI / 2.0, 0, false);
               m_doc.Regenerate();

               // add a 90 elbow and slope it
               FabricationPart pt_90elbow1 = FabricationPart.Create(m_doc, bt_90elbow, 0, levelOne.Id);
               Connector conn1_90elbow1 = GetPrimaryConnector(pt_90elbow1.ConnectorManager);
               Connector conn2_90elbow1 = GetSecondaryConnector(pt_90elbow1.ConnectorManager);
               SizeAlignSlopeJustifyCoupleConnect(conn1_90elbow1, conn2_pipe1, Math.PI, 0.02, FabricationPartJustification.Middle);

               // add a copper pipe
               FabricationPart pt_pipe2 = CreateStraightPart(bt_groovedPipe, 0, levelOne.Id, 10.0);
               Connector conn1_pipe2 = GetPrimaryConnector(pt_pipe2.ConnectorManager);
               Connector conn2_pipe2 = GetSecondaryConnector(pt_pipe2.ConnectorManager);
               SizeAlignSlopeJustifyCoupleConnect(conn1_pipe2, conn2_90elbow1, 0, 0, FabricationPartJustification.Middle);

               tr.Commit();
            }
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }

         return Result.Succeeded;
      }

      /// <summary>
      /// Convenience method to get fabrication part's dimension value, specified by the dimension name.
      /// </summary>
      /// <param name="part">
      /// The fabrication part to be queried.
      /// </param>
      /// <param name="dimName">
      /// The name of the fabrication dimension.
      /// </param>
      /// <returns>
      /// Returns the fabrication dimension value for the fabrication part, as specified by the dimension name.
      /// </returns>
      double GetDimValue(FabricationPart part, string dimName)
      {
         double value = 0;
         if (part != null)
         {
            IList<FabricationDimensionDefinition> dims = part.GetDimensions();
            foreach (FabricationDimensionDefinition def in dims)
            {
               if (def.Name.Equals(dimName))
               {
                  value = part.GetDimensionValue(def);
                  break;
               }
            }
         }

         return value;
      }

      /// <summary>
      /// Convenience method to set fabrication part's dimension value, specified by the dimension name.
      /// </summary>
      /// <param name="part">
      /// The fabrication part.
      /// </param>
      /// <param name="dimName">
      /// The name of the fabrication dimension.
      /// </param>
      /// <param name="dimValue">
      /// The value of the fabrication dimension to set to.
      /// </param>
      /// <returns>
      /// Returns the fabrication dimension value for the fabrication part, as specified by the dimension name.
      /// </returns>
      bool SetDimValue(FabricationPart part, string dimName, double dimValue)
      {
         IList<FabricationDimensionDefinition> dims = part.GetDimensions();
         FabricationDimensionDefinition dim = null;
         foreach (FabricationDimensionDefinition def in dims)
         {
            if (def.Name.Equals(dimName))
            {
               dim = def;
               break;
            }
         }

         if (dim == null)
            return false;

         part.SetDimensionValue(dim, dimValue);
         m_doc.Regenerate();
         return true;
      }

      /// <summary>
      /// Convenience method to automatically size, align, couple (if needed) and connect two fabrication part
      /// by the specified connectors.
      /// </summary>
      /// <param name="conn_from">
      /// The connector to align by of the fabrication part to move.
      /// </param>
      /// <param name="conn_to">
      /// The connector to align to.
      /// </param>
      /// <param name="rotation">
      /// Rotation around the direction of connection - angle between width vectors in radians. 
      /// </param>
      /// <returns>
      /// Returns the fabrication dimension value for the fabrication part, as specified by the dimension name.
      /// </returns>
      void SizeAlignCoupleConnect(Connector conn_from, Connector conn_to, double rotation)
      {
         if (conn_from.Shape == ConnectorProfileType.Rectangular || conn_from.Shape == ConnectorProfileType.Oval)
         {
            conn_from.Height = conn_to.Height;
            conn_from.Width = conn_to.Width;
         }
         else
         {
            conn_from.Radius = conn_to.Radius;
         }

         m_doc.Regenerate();

         FabricationPart.AlignPartByConnectors(m_doc, conn_from, conn_to, rotation);
         m_doc.Regenerate();

         FabricationPart.ConnectAndCouple(m_doc, conn_from, conn_to);
         m_doc.Regenerate();
      }

      /// <summary>
      /// Convenience method to automatically size, align, slope, justification couple (if needed) and connect two fabrication parts
      /// by the specified connectors.
      /// </summary>
      /// <param name="conn_from">
      /// The connector to align by of the fabrication part to move.
      /// </param>
      /// <param name="conn_to">
      /// The connector to align to.
      /// </param>
      /// <param name="rotation">
      /// Rotation around the direction of connection - angle between width vectors in radians. 
      /// </param>
      /// <param name="slope">
      /// The slope value to flex to match if possible in fractional units (eg.1/50). Positive values are up, negative are down. Slopes can only be applied
      /// to fittings, whilst straights will inherit the slope from the piece it is connecting to.
      /// </param>
      /// <param name="justification">
      /// The justification to align eccentric parts.
      /// </param>
      /// <returns>
      /// Returns the fabrication dimension value for the fabrication part, as specified by the dimension name.
      /// </returns>
      void SizeAlignSlopeJustifyCoupleConnect(Connector conn_from, Connector conn_to, double rotation, double slope, FabricationPartJustification justification)
      {
         if (conn_from.Shape == ConnectorProfileType.Rectangular || conn_from.Shape == ConnectorProfileType.Oval)
         {
            conn_from.Height = conn_to.Height;
            conn_from.Width = conn_to.Width;
         }
         else
         {
            conn_from.Radius = conn_to.Radius;
         }

         m_doc.Regenerate();

         FabricationPart.AlignPartByConnectorToConnector(m_doc, conn_from, conn_to, rotation, slope, justification);
         m_doc.Regenerate();

         FabricationPart.ConnectAndCouple(m_doc, conn_from, conn_to);
         m_doc.Regenerate();
      }

      /// <summary>
      /// Convenience method to locate a fabrication service button specified by group and name.
      /// </summary>
      /// <param name="service">
      /// The fabrication service.
      /// </param>
      /// <param name="group">
      /// The fabrication service group index.
      /// </param>
      /// <param name="name">
      /// The fabrication service button name.
      /// </param>
      /// <returns>
      /// Returns the fabrication service button as specified by the fabrication service, group and name.
      /// </returns>
      FabricationServiceButton locateButton(FabricationService service, int group, string name)
      {
         FabricationServiceButton button = null;
         if (service != null && group >= 0 && group < service.GroupCount)
         {
            int buttonCount = service.GetButtonCount(group);
            for (int i = 0; button == null && i < buttonCount; i++)
            {
               FabricationServiceButton bt = service.GetButton(group, i);
               if (bt != null && bt.Name.Equals(name))
                  button = bt;
            }
         }

         return button;
      }

      /// <summary>
      /// Convenience method to create a straight fabrication part.
      /// </summary>
      /// <param name="fsb">
      /// The FabricationServiceButton used to create the fabrication part from.
      /// </param>
      /// <param name="condition">
      /// The condition index of the fabrication service button.
      /// </param>
      /// <param name="levelId">
      /// The element identifier belonging to the level on which to create this fabrication part.
      /// </param>
      /// <param name="length">
      /// The length, in feet, of the fabrication part to be created. 
      /// </param>
      /// <returns>
      /// Returns a straight fabrication part, as specified by the fabrication service button, condition, level id and length.
      /// </returns>
		FabricationPart CreateStraightPart(FabricationServiceButton fsb, int condition, ElementId levelId, double length)
      {
         FabricationPart straight = FabricationPart.Create(m_doc, fsb, condition, levelId);

         Parameter length_option = straight.LookupParameter("Length Option");
         length_option.Set("Value");

         Parameter lengthParam = straight.LookupParameter("Length");
         lengthParam.Set(length);

         m_doc.Regenerate();

         return straight;
      }

      /// <summary>
      /// Convenience method to get the primary connector from the specified connector manager.
      /// </summary>
      /// <param name="cm">
      /// The connector manager.
      /// </param>
      /// <returns>
      /// Returns the primary connector from the connector manager.
      /// </returns>
		Connector GetPrimaryConnector(ConnectorManager cm)
      {
         foreach (Connector cn in cm.Connectors)
         {
            MEPConnectorInfo info = cn.GetMEPConnectorInfo();
            if (info.IsPrimary)
               return cn;
         }
         return null;
      }

      /// <summary>
      /// Convenience method to get the secondary connector from the specified connector manager.
      /// </summary>
      /// <param name="cm">
      /// The connector manager.
      /// </param>
      /// <returns>
      /// Returns the secondary connector from the connector manager.
      /// </returns>
		Connector GetSecondaryConnector(ConnectorManager cm)
      {
         foreach (Connector cn in cm.Connectors)
         {
            MEPConnectorInfo info = cn.GetMEPConnectorInfo();
            if (info.IsSecondary)
               return cn;
         }
         return null;
      }

      /// <summary>
      /// Convenience method to get the first non-primary and non-secondary connector from the specified connector manager.
      /// </summary>
      /// <param name="cm">
      /// The connector manager.
      /// </param>
      /// <returns>
      /// Returns the first non-primary and non-secondary connector from the connector manager.
      /// </returns>
      Connector GetFirstNonPrimaryOrSecondaryConnector(ConnectorManager cm)
      {
         foreach (Connector cn in cm.Connectors)
         {
            MEPConnectorInfo info = cn.GetMEPConnectorInfo();
            if (!info.IsPrimary && !info.IsSecondary)
               return cn;
         }
         return null;
      }

      /// <summary>
      /// Convenience method to get all fabrication material identifiers from the
      /// specified fabrication configuration.
      /// </summary>
      /// <param name="config">
      /// The fabrication configuration.
      /// </param>
      /// <returns>
      /// Returns a list of all the fabrication material identifiers for this
      /// fabrication configuration.
      /// </returns>
      void GetMaterials(FabricationConfiguration config)
      {
         m_materialIds = config.GetAllMaterials(null);

         m_materialGroups = new List<string>();
         m_materialNames = new List<string>();

         for (int i = 0; i < m_materialIds.Count; i++)
         {
            m_materialGroups.Add(config.GetMaterialGroup(m_materialIds[i]));
            m_materialNames.Add(config.GetMaterialName(m_materialIds[i]));
         }
      }

      /// <summary>
      /// Convenience method to get all fabrication specification identifiers from the
      /// specified fabrication configuration.
      /// </summary>
      /// <param name="config">
      /// The fabrication configuration.
      /// </param>
      /// <returns>
      /// Returns a list of all the fabrication specification identifiers for this
      /// fabrication configuration.
      /// </returns>
      void GetSpecs(FabricationConfiguration config)
      {
         m_specIds = config.GetAllSpecifications(null);

         m_specGroups = new List<string>();
         m_specNames = new List<string>();

         for (int i = 0; i < m_specIds.Count; i++)
         {
            m_specGroups.Add(config.GetSpecificationGroup(m_specIds[i]));
            m_specNames.Add(config.GetSpecificationName(m_specIds[i]));
         }
      }

      /// <summary>
      /// Convenience method to get all fabrication insulation specification identifiers from the
      /// specified fabrication configuration.
      /// </summary>
      /// <param name="config">
      /// The fabrication configuration.
      /// </param>
      /// <returns>
      /// Returns a list of all the fabrication insulation specification identifiers for this
      /// fabrication configuration.
      /// </returns>
      void GetInsulationSpecs(FabricationConfiguration config)
      {
         m_insSpecIds = config.GetAllInsulationSpecifications(null);

         m_insSpecGroups = new List<string>();
         m_insSpecNames = new List<string>();

         for (int i = 0; i < m_insSpecIds.Count; i++)
         {
            m_insSpecGroups.Add(config.GetInsulationSpecificationGroup(m_insSpecIds[i]));
            m_insSpecNames.Add(config.GetInsulationSpecificationName(m_insSpecIds[i]));
         }
      }

      /// <summary>
      /// Convenience method to get all fabrication connector identifiers from the
      /// specified fabrication configuration.
      /// </summary>
      /// <param name="config">
      /// The fabrication configuration.
      /// </param>
      /// <returns>
      /// Returns a list of all the fabrication connector identifiers for this
      /// fabrication configuration.
      /// </returns>
      void GetFabricationConnectors(FabricationConfiguration config)
      {
         m_connIds = config.GetAllFabricationConnectorDefinitions(ConnectorDomainType.Undefined, ConnectorProfileType.Invalid);

         m_connGroups = new List<string>();
         m_connNames = new List<string>();

         for (int i = 0; i < m_connIds.Count; i++)
         {
            m_connGroups.Add(config.GetFabricationConnectorGroup(m_connIds[i]));
            m_connNames.Add(config.GetFabricationConnectorName(m_connIds[i]));
         }
      }
   }
}

HangerRods.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.Windows.Forms;
using Autodesk.Revit.UI.Selection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   #region Detach Rods
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class DetachRods : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;
            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part hanger to start.");
            var part = doc.GetElement(refObj) as FabricationPart;

            if (part == null || part.IsAHanger() == false)
            {
               message = "The selected element is not a fabrication part hanger.";
               return Result.Failed;
            }

            var rodInfo = part.GetRodInfo();
            using (var trans = new Transaction(doc, "Detach Rods"))
            {
               trans.Start();

               rodInfo.CanRodsBeHosted = false;

               trans.Commit();
            }

            message = "Detach successful";
            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
   #endregion

   #region Increase Rod Length (x2)
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class DoubleRodLength : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;
            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part hanger to start.");
            var part = doc.GetElement(refObj) as FabricationPart;

            if (part == null || part.IsAHanger() == false)
            {
               message = "The selected element is not a fabrication part hanger.";
               return Result.Failed;
            }

            var rodInfo = part.GetRodInfo();
            if (rodInfo.IsAttachedToStructure == true)
            {
               message = "The hanger rods must be detached from their host first";
               return Result.Failed;
            }

            using (var trans = new Transaction(doc, "Double Rod Length"))
            {
               trans.Start();

               for (int i = 0; i < rodInfo.RodCount; i++)
               {
                  var originalLength = rodInfo.GetRodLength(i);
                  rodInfo.SetRodLength(i, originalLength * 2.0);
               }

               trans.Commit();
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
   #endregion

   #region Decrease Rod Length (x2)
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class HalveRodLength : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;
            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part hanger to start.");
            var part = doc.GetElement(refObj) as FabricationPart;

            if (part == null || part.IsAHanger() == false)
            {
               message = "The selected element is not a fabrication part hanger.";
               return Result.Failed;
            }

            var rodInfo = part.GetRodInfo();
            if (rodInfo.IsAttachedToStructure == true)
            {
               message = "The hanger rods must be detached from their host first";
               return Result.Failed;
            }

            using (var trans = new Transaction(doc, "Halve Rod Length"))
            {
               trans.Start();

               for (int i = 0; i < rodInfo.RodCount; i++)
               {
                  var originalLength = rodInfo.GetRodLength(i);
                  rodInfo.SetRodLength(i, originalLength / 2.0);
               }
    
               trans.Commit();
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
   #endregion

   #region Increase Rod Strcuture Extension (by 1ft)
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class IncreaseRodStructureExtension : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;
            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part hanger to start.");
            var part = doc.GetElement(refObj) as FabricationPart;

            if (part == null || part.IsAHanger() == false)
            {
               message = "The selected element is not a fabrication part hanger.";
               return Result.Failed;
            }

            var rodInfo = part.GetRodInfo();
            if (rodInfo.IsAttachedToStructure == false)
            {
               message = "The hanger rods must be attached to structure.";
               return Result.Failed;
            }

            using (var trans = new Transaction(doc, "Increase Rod Structure Extension"))
            {
               trans.Start();

               for (int i = 0; i < rodInfo.RodCount; i++)
               {
                  var originalExtension = rodInfo.GetRodStructureExtension(i);
                  rodInfo.SetRodStructureExtension(i, originalExtension + 1.0);
               }

               trans.Commit();
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
   #endregion

   #region Decrease Rod Strcuture Extension (by 1ft)
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class DecreaseRodStructureExtension : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;
            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part hanger to start.");
            var part = doc.GetElement(refObj) as FabricationPart;

            if (part == null || part.IsAHanger() == false)
            {
               message = "The selected element is not a fabrication part hanger.";
               return Result.Failed;
            }

            var rodInfo = part.GetRodInfo();
            if (rodInfo.IsAttachedToStructure == false)
            {
               message = "The hanger rods must be attached to structure.";
               return Result.Failed;
            }

            using (var trans = new Transaction(doc, "Increase Rod Structure Extension"))
            {
               trans.Start();

               for (int i = 0; i < rodInfo.RodCount; i++)
               {
                  var originalExtension = rodInfo.GetRodStructureExtension(i);
                  rodInfo.SetRodStructureExtension(i, originalExtension - 1.0);
               }

               trans.Commit();
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
   #endregion
}

ItemFile.cs

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class LoadAndPlaceNextItemFile : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;

            FilteredElementCollector cl = new FilteredElementCollector(doc);
            cl.OfClass(typeof(Level));
            IList<Element> levels = cl.ToElements();

            Level levelOne = null;
            foreach (Level level in levels)
            {
               if (level != null && level.Name.Equals("Level 1"))
               {
                  levelOne = level;
                  break;
               }
            }

            if (levelOne == null)
               return Result.Failed;

            using (var config = FabricationConfiguration.GetFabricationConfiguration(doc))
            {
               if (config == null)
               {
                  message = "No fabrication configuration in use";
                  return Result.Failed;
               }

               using (var configInfo = config.GetFabricationConfigurationInfo())
               {
                  using (var source = FabricationConfigurationInfo.FindSourceFabricationConfiguration(configInfo))
                  {
                     if (source == null)
                     {
                        message = "Source fabrication configuration not found";
                        return Result.Failed;
                     }

                     using (var trans = new Autodesk.Revit.DB.Transaction(doc, "Load And Place Next Item File"))
                     {
                        trans.Start();

                        // reload the configuration
                        config.ReloadConfiguration();

                        // get the item folders
                        var itemFolders = config.GetItemFolders();

                        // get the next unloaded item file from the item folders structure
                        var nextFile = GetNextUnloadedItemFile(itemFolders);
                        if (nextFile == null)
                        {
                           message = "Could not locate the next unloaded item file";
                           return Result.Failed;
                        }

                        var itemFilesToLoad = new List<FabricationItemFile>();
                        itemFilesToLoad.Add(nextFile);

                        // load the item file into the config
                        var failedItems = config.LoadItemFiles(itemFilesToLoad);
                        if (failedItems != null && failedItems.Count > 0)
                        {
                           message = "Could not load the item file: " + nextFile.Identifier;
                           return Result.Failed;
                        }

                        // create a part from the item file
                        using (var part = FabricationPart.Create(doc, nextFile, levelOne.Id))
                        {
                           doc.Regenerate();

                           var selectedElements = new List<ElementId>() { part.Id };

                           uiDoc.Selection.SetElementIds(selectedElements);
                           uiDoc.ShowElements(selectedElements);

                           trans.Commit();
                        }
                     }
                  }
               }
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }

      private FabricationItemFile GetNextUnloadedItemFile(IList<FabricationItemFolder> itemFolders)
      {
         if (itemFolders == null)
            return null;

         foreach (var folder in itemFolders)
         {
            var file = GetNextUnloadedItemFileRecursive(folder);
            if (file != null)
               return file;
         }

         return null;
      }

      private FabricationItemFile GetNextUnloadedItemFileRecursive(FabricationItemFolder folder)
      {
         if (folder == null)
            return null;

         var files = folder.GetItemFiles();
         if (files != null && files.Count > 0)
         {
            foreach (var file in files)
            {
               if (file != null && file.IsLoaded() == false && file.IsValid() == true)
                  return file;
            }
         }

         var subFolders = folder.GetSubFolders();
         if (subFolders != null && subFolders.Count > 0)
         {
            foreach (var subFolder in subFolders)
            {
               var file = GetNextUnloadedItemFileRecursive(subFolder);
               if (file != null)
                  return file;
            }
         }

         return null;
      }
   }

   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class UnloadUnusedItemFiles : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;

            using (var config = FabricationConfiguration.GetFabricationConfiguration(doc))
            {
               if (config == null)
               {
                  message = "No fabrication configuration in use";
                  return Result.Failed;
               }

               using (var trans = new Transaction(doc, "Unload unused item files"))
               {
                  trans.Start();

                  config.ReloadConfiguration();

                  var loadedFiles = config.GetAllLoadedItemFiles();
                  var unusedFiles = loadedFiles.Where(x => x.IsUsed == false).ToList();
                  if (unusedFiles.Count == 0)
                  {
                     message = "No unuseed item files found";
                     return Result.Failed;
                  }

                  if (config.CanUnloadItemFiles(unusedFiles) == false)
                  {
                     message = "Cannot unload item files";
                     return Result.Failed;
                  }

                  config.UnloadItemFiles(unusedFiles);

                  trans.Commit();

                  var builder = new StringBuilder();
                  unusedFiles.ForEach(x => builder.AppendLine(System.IO.Path.GetFileNameWithoutExtension(x.Identifier)));

                  TaskDialog td = new TaskDialog("Unload Unused Item Files")
                  {
                     MainIcon = TaskDialogIcon.TaskDialogIconInformation,
                     TitleAutoPrefix = false,
                     MainInstruction = "The following item files were unloaded:",
                     MainContent = builder.ToString()
                  };

                  td.Show();
               }
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }

}

OptimizeStraights.cs

//
// (C) Copyright 2003-2011 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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class OptimizeStraights : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            Document doc = commandData.Application.ActiveUIDocument.Document;

            // check user selection
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            ICollection<ElementId> collection = uidoc.Selection.GetElementIds();
            if (collection.Count > 0)
            {
               ISet<ElementId> selIds = new HashSet<ElementId>();
               foreach (ElementId id in collection)
                  selIds.Add(id);

               using (Transaction tr = new Transaction(doc, "Optimize Straights"))
               {
                  tr.Start();

                  // optimize lengths method will take a set of elements and any fabrication straight parts
                  // within this set that have been optimized will be returned.
                  ISet<ElementId> affectedPartIds = FabricationPart.OptimizeLengths(doc, selIds);
                  if (affectedPartIds.Count == 0)
                  {
                     message = "No fabrication straight parts were optimized.";
                     return Result.Cancelled;
                  }

                  doc.Regenerate();

                  tr.Commit();
               }

               return Result.Succeeded;
            }
            else
            {
               // inform user they need to select at least one element
               message = "Please select at least one element.";
            }

            return Result.Failed;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

PartInfo.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.Windows.Forms;
using Autodesk.Revit.UI.Selection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class PartInfo : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;

            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part to start.");
            var part = doc.GetElement(refObj) as FabricationPart;

            if (part == null)
            {
               message = "The selected element is not a fabrication part.";
               return Result.Failed;
            }

            var config = FabricationConfiguration.GetFabricationConfiguration(doc);
            if (config == null)
            {
               message = "no valid fabrication configuration";
               return Result.Failed;
            }

            var builder = new StringBuilder();

            // alias
            builder.AppendLine("Alias: " + part.Alias);

            // cid
            builder.AppendLine("CID: " + part.ItemCustomId.ToString());

            // domain type
            builder.AppendLine("Domain Type: " + part.DomainType.ToString());

            // hanger rod kit
            if (part.IsAHanger())
            {
               string rodKitName = "None";
               var rodKit = part.HangerRodKit;
               if (rodKit > 0)
                  rodKitName = config.GetAncillaryGroupName(part.HangerRodKit) + ": " + config.GetAncillaryName(part.HangerRodKit);

               builder.AppendLine("Hanger Rod Kit: " + rodKitName);
            }

            // insulation specification
            var insSpec = config.GetInsulationSpecificationGroup(part.InsulationSpecification)
                + ": " + config.GetInsulationSpecificationName(part.InsulationSpecification);
            builder.AppendLine("Insulation Specification: " + insSpec);

            // has no connections
            builder.AppendLine("Has No Connections: " + part.HasNoConnections().ToString());

            // item number
            builder.AppendLine("Item Number: " + part.ItemNumber);

            // material
            var material = config.GetMaterialGroup(part.Material) + ": " + config.GetMaterialName(part.Material);
            builder.AppendLine("Material: " + material);

            // part guid
            builder.AppendLine("Part Guid: " + part.PartGuid.ToString());

            // part status
            builder.AppendLine("Part Status: " + config.GetPartStatusDescription(part.PartStatus));

            // product code
            builder.AppendLine("Product Code: " + part.ProductCode);

            // service
            builder.AppendLine("Service Name: " + part.ServiceName);

            // get the service type name
            builder.AppendLine("Service Type: " + config.GetServiceTypeName(part.ServiceType));

            // specification
            var spec = config.GetSpecificationGroup(part.Specification) + ": " + config.GetSpecificationName(part.Specification);
            builder.AppendLine("Specification: " + spec);

            // centerline length
            builder.AppendLine("Centerline Length: " + GetStringFromNumber(doc, part.CenterlineLength, UnitType.UT_Length));

            TaskDialog.Show("Fabrication Part [" + part.Id.IntegerValue + "]", builder.ToString());

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }

      private string GetStringFromNumber(Document doc, double number, UnitType unitType)
      {
         return UnitFormatUtils.Format(doc.GetUnits(), unitType, number, true, false);
      }
   }
}

PartRenumber.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.Windows.Forms;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class PartRenumber : IExternalCommand
   {
      #region region Member Variables
      private int m_ductNum = 1;
      private int m_ductCouplingNum = 1;
      private int m_pipeNum = 1;
      private int m_pipeCouplingNum = 1;
      private int m_hangerNum = 1;
      private int m_otherNum = 1;
      #endregion

      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uidoc = commandData.Application.ActiveUIDocument;
            var doc = uidoc.Document;
            var collection = uidoc.Selection.GetElementIds();

            using (var trans = new Transaction(doc, "Part Renumber"))
            {
               trans.Start();

               var fabParts = new List<FabricationPart>();
               foreach (var elementId in collection)
               {
                  var part = doc.GetElement(elementId) as FabricationPart;
                  if (part != null)
                  {
                     part.ItemNumber = string.Empty; // wipe the item number
                     fabParts.Add(part);
                  }
               }

               if (fabParts.Count == 0)
               {
                  message = "Select at least one fabrication part";
                  return Result.Failed;
               }

               // ignore certain fields
               var ignoreFields = new List<FabricationPartCompareType>();
               ignoreFields.Add(FabricationPartCompareType.Notes);
               ignoreFields.Add(FabricationPartCompareType.OrderNo);
               ignoreFields.Add(FabricationPartCompareType.Service);

               for (int i = 0; i < fabParts.Count; i++)
               {
                  var part1 = fabParts[i];
                  if (string.IsNullOrWhiteSpace(part1.ItemNumber))
                  {
                     // part has not already been checked
                     if (IsADuct(part1))
                     {
                        if (IsACoupling(part1))
                           part1.ItemNumber = "DUCT COUPLING: " + m_ductCouplingNum++;
                        else
                           part1.ItemNumber = "DUCT: " + m_ductNum++;
                     }
                     else if (IsAPipe(part1))
                     {
                        if (IsACoupling(part1))
                           part1.ItemNumber = "PIPE COUPLING: " + m_pipeCouplingNum++;
                        else
                           part1.ItemNumber = "PIPE: " + m_pipeNum++;
                     }
                     else if (part1.IsAHanger())
                        part1.ItemNumber = "HANGER: " + m_hangerNum++;
                     else
                        part1.ItemNumber = "MISC: " + m_otherNum++;

                  }

                  for (int j = i + 1; j < fabParts.Count; j++)
                  {
                     var part2 = fabParts[j];
                     if (string.IsNullOrWhiteSpace(part2.ItemNumber))
                     {
                        // part2 has not been checked
                        if (part1.IsSameAs(part2, ignoreFields))
                        {
                           // items are the same, so give them the same item number
                           part2.ItemNumber = part1.ItemNumber;
                        }
                     }
                  }
               }

               trans.Commit();
            }

            TaskDialog td = new TaskDialog("Fabrication Part Renumber")
            {
               MainIcon = TaskDialogIcon.TaskDialogIconInformation,
               TitleAutoPrefix = false,
               MainInstruction = "Renumber was successful",
               AllowCancellation = false,
               CommonButtons = TaskDialogCommonButtons.Ok
            };

            td.Show();

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }

      /// <summary>
      /// Checks if the given part is fabrication ductwork.
      /// </summary>
      /// <param name="fabPart">The part to check.</param>
      /// <returns>True if the part is fabrication ductwork.</returns>
      private bool IsADuct(FabricationPart fabPart)
      {
         return (fabPart != null && (fabPart.Category.Id.IntegerValue == (int)BuiltInCategory.OST_FabricationDuctwork));
      }

      /// <summary>
      /// Checks if the part is fabrication pipework.
      /// </summary>
      /// <param name="fabPart">The part to check.</param>
      /// <returns>True if the part is fabrication pipework.</returns>
      private bool IsAPipe(FabricationPart fabPart)
      {
         return (fabPart != null && (fabPart.Category.Id.IntegerValue == (int)BuiltInCategory.OST_FabricationPipework));
      }

      /// <summary>
      /// Checks if the part is a coupling. 
      /// The CID's (the fabrication part item customer Id) that are recognized internally as couplings are:
      ///   CID 522, 1112 - Round Ductwork
      ///   CID 1522 - Oval Ductwork
      ///   CID 4522 - Rectangular Ductwork
      ///   CID 2522 - Pipe Work
      ///   CID 3522 - Electrical
      /// </summary>
      /// <param name="fabPart">The part to check.</param>
      /// <returns>True if the part is a coupling.</returns>
      private bool IsACoupling(FabricationPart fabPart)
      {
         if (fabPart != null)
         {
            int CID = fabPart.ItemCustomId;
            if (CID == 522 || CID == 1522 || CID == 2522 || CID == 3522 || CID == 1112)
            {
               return true;
            }
         }
         return false;
      }
   }
}

SplitStraight.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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;
using System.Windows.Forms;
using Autodesk.Revit.UI.Selection;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class SplitStraight : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            // check user selection
            var uiDoc = commandData.Application.ActiveUIDocument;
            var doc = uiDoc.Document;

            Reference refObj = uiDoc.Selection.PickObject(ObjectType.Element, "Pick a fabrication part straight to start.");
            var part = doc.GetElement(refObj) as FabricationPart;

            if (part == null || part.IsAStraight() == false)
            {
               message = "The selected element is not a fabrication part straight.";
               return Result.Failed;
            }

            // get the 2 end connectors
            var connectors = new List<Connector>();
            foreach (Connector c in part.ConnectorManager.Connectors)
            {
               if (c.ConnectorType == ConnectorType.End)
                  connectors.Add(c);
            }

            if (connectors.Count != 2)
            {
               message = "There are not 2 end connectors on this straight.";
               return Result.Failed;
            }

            var conn1 = connectors[0];
            var conn2 = connectors[1];

            var x = (conn1.Origin.X + conn2.Origin.X) / 2.0;
            var y = (conn1.Origin.Y + conn2.Origin.Y) / 2.0;
            var z = (conn1.Origin.Z + conn2.Origin.Z) / 2.0;

            var midpoint = new XYZ(x, y, z);

            if (part.CanSplitStraight(midpoint) == false)
            {
               message = "straight cannot be split at its mid-point";
               return Result.Failed;
            }

            using (var trans = new Transaction(doc, "split straight"))
            {
               trans.Start();

               part.SplitStraight(midpoint);

               trans.Commit();
            }

            return Result.Succeeded;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }
   }
}

StretchAndFit.cs

//
// (C) Copyright 2003-2011 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.Linq;
using System.Text;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Fabrication;

namespace Revit.SDK.Samples.FabricationPartLayout.CS
{
   /// <summary>
   /// Implements the Revit add-in interface IExternalCommand
   /// </summary>
   [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
   [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
   public class StretchAndFit : IExternalCommand
   {
      /// <summary>
      /// Implement this method as an external command for Revit.
      /// </summary>
      /// <param name="commandData">An object that is passed to the external application 
      /// which contains data related to the command, 
      /// such as the application object and active view.</param>
      /// <param name="message">A message that can be set by the external application 
      /// which will be displayed if a failure or cancellation is returned by 
      /// the external command.</param>
      /// <param name="elements">A set of elements to which the external application 
      /// can add elements that are to be highlighted in case of failure or cancellation.</param>
      /// <returns>Return the status of the external command. 
      /// A result of Succeeded means that the API external method functioned as expected. 
      /// Cancelled can be used to signify that the user cancelled the external operation 
      /// at some point. Failure should be returned if the application is unable to proceed with 
      /// the operation.</returns>
      public virtual Result Execute(ExternalCommandData commandData
          , ref string message, ElementSet elements)
      {
         try
         {
            Document doc = commandData.Application.ActiveUIDocument.Document;

            // check user selection
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            ICollection<ElementId> collection = uidoc.Selection.GetElementIds();
            if (collection.Count > 0)
            {
               List<ElementId> selIds = new List<ElementId>();
               foreach (ElementId id in collection)
                  selIds.Add(id);

               if (selIds.Count != 2)
               {
                  message = "Select a fabrication part to stretch and fit from and an element to connect to.";
                  return Result.Cancelled;
               }

               Connector connFrom = GetValidConnectorToStretchAndFitFrom(doc, selIds.ElementAt(0));
               Connector connTo = GetValidConnectorToStretchAndFitTo(doc, selIds.ElementAt(1));

               FabricationPartRouteEnd toEnd = FabricationPartRouteEnd.CreateFromConnector(connTo);

               if (connFrom == null || connTo == null)
               {
                  message = "Invalid fabrication parts to stretch and fit";
                  return Result.Cancelled;
               }

               using (Transaction tr = new Transaction(doc, "Stretch and Fit"))
               {
                  tr.Start();

                  ISet<ElementId> newPartIds;
                  FabricationPartFitResult result = FabricationPart.StretchAndFit(doc, connFrom, toEnd, out newPartIds);
                  if (result != FabricationPartFitResult.Success)
                  {
                     message = result.ToString();
                     return Result.Failed;
                  }

                  doc.Regenerate();

                  tr.Commit();
               }

               return Result.Succeeded;
            }
            else
            {
               // inform user they need to select at least one element
               message = "Select a fabrication part to stretch and fit from and an element to connect to.";
            }

            return Result.Failed;
         }
         catch (Exception ex)
         {
            message = ex.Message;
            return Result.Failed;
         }
      }

      private Connector GetValidConnectorToStretchAndFitFrom(Document doc, ElementId elementId)
      {
         // must be a fabrication part
         FabricationPart part = doc.GetElement(elementId) as FabricationPart;
         if (part == null)
            return null;

         // must not be a straight, hanger or tap
         if (part.IsAStraight() || part.IsATap() || part.IsAHanger())
            return null;

         // part must be connected at one end and have one unoccupied connector
         int numUnused = part.ConnectorManager.UnusedConnectors.Size;
         int numConns = part.ConnectorManager.Connectors.Size;

         if (numConns - numUnused != 1)
            return null;

         foreach (Connector conn in part.ConnectorManager.UnusedConnectors)
         {
            // return the first unoccupied connector
            return conn;
         }

         return null;
      }

      private Connector GetValidConnectorToStretchAndFitTo(Document doc, ElementId elementId)
      {
         // connect to another fabrication part - will work also with families.
         FabricationPart part = doc.GetElement(elementId) as FabricationPart;
         if (part == null)
            return null;

         // must not be a fabrication part hanger
         if (part.IsAHanger())
            return null;

         foreach (Connector conn in part.ConnectorManager.UnusedConnectors)
         {
            // return the first unoccupied connector
            return conn;
         }

         return null;
      }
   }
}