How to Re-attach a Detached Connection to the Side of a FabricationPart?

How to Re-attach a Detached Connection to the Side of a FabricationPart?

JC_BL
Advocate Advocate
269 Views
2 Replies
Message 1 of 3

How to Re-attach a Detached Connection to the Side of a FabricationPart?

JC_BL
Advocate
Advocate

I need to use my program to re-attach a connection that my program has just detached.  I would like to know how to do this using Revit API.

 

I am having a problem exporting duct from Revit 2024 to a job file (MAJ file) if it has a tap attached to the side of the duct body.  The problem is currently being investigated in Autodesk development team.  But I cannot wait for them to figure this out and send out a patch.  I need to find a workaround soon.  One possible workaround is to detach the tap (such as Boot Tap or STO/STOD) before exporting it, and then re-attach the tap after exporting it.

 

This is easy to detach the tap from the duct body.  But I don't know how to re-attach the tap back to the duct body.

 

I thought I could save the original connection, and simply used Connector.ConnectTo() command to re-attach the original connection.  But this doesn't work.  The reason is that the original connection is already destroyed, and the original connection that I have saved is not valid anymore.

 

The following is a sample program to select pieces, detaching any connection in the side of the duct body, exporting the pieces, and attempting (and failing) to re-attach the original connections:

 

[Transaction(TransactionMode.Manual)]
public class CCmdTestExport : IExternalCommand
   {
   public Result Execute( ExternalCommandData commandData, ref String refMsg, ElementSet elements )
      {
      UIDocument uidoc = commandData.Application.ActiveUIDocument;
      Document   doc   = commandData.Application.ActiveUIDocument.Document;

      // Let the user to select duct.
      List<ElementId> lisSelectElementIds = (List<ElementId>)uidoc.Selection.GetElementIds();
      try
         {
         String sInstruction = "Select duct and hit 'Finish' button in Options Bar";
         ISelectionFilter oSelectOnlyDuct = new CDuctSelectionFilter();
         List<Reference> lisSelectReferences =
            lisSelectElementIds.Select( x => new Reference( doc.GetElement( x ) ) ).ToList();
         lisSelectReferences = (List<Reference>)uidoc.Selection.PickObjects( ObjectType.Element, oSelectOnlyDuct,
                                                                             sInstruction, lisSelectReferences );
         lisSelectElementIds = lisSelectReferences.Select( x => x.ElementId ).ToList();

         // HANDLE_PHANTOM_SELECTION_AFTER_PICKOBJECT
         List<ElementId> lisEmpty = new List<ElementId>();
         uidoc.Selection.SetElementIds( lisEmpty );
         }
      catch( Autodesk.Revit.Exceptions.OperationCanceledException )
         {
         return Result.Cancelled;
         }

      // Convert the selected elements into a list of FabricationPart.
      List<FabricationPart> lisFabPcMayHaveNull = lisSelectElementIds.Select( x => doc.GetElement(x) as FabricationPart ).ToList();
      List<FabricationPart> lisFabPc            = lisFabPcMayHaveNull.Where( x => x != null ).ToList();
      if ( lisFabPc.Count == 0 )
         return Result.Cancelled;

      // Detach connection from the side of the duct body, export the duct, and then attempt to re-attach the
      // detached connections.
      using( Transaction trans = new Transaction( doc ) )
         {
         if ( trans.Start( "Test" ) == TransactionStatus.Started )
            {
            try
               {
               // Disconnect anything attached to the side of the duct bodies in the selected pieces.
               List<Connector> lisDetachedDuctBodySideConnector = new List<Connector>();
               List<Connector> lisDetachedOutsidePcConnector    = new List<Connector>();
               foreach( FabricationPart curPc in lisFabPc )
                  {
                  while( true )
                     {
                     Connector cnSide = cnGetFirstNonPrimaryOrSecondaryConnector( curPc.ConnectorManager );
                        // GetFirstNonPrimaryOrSecondaryConnector() is from sample
                        // program FabricationPartLayout in Revit SDK.
                     if ( cnSide == null )
                        break;

                     // Here, cnSide != null
                     Connector cnOriginalConnectToSideCn = cnDisconnectSideConnection( cnSide );
                     lisDetachedDuctBodySideConnector.Add( cnSide                    );
                     lisDetachedOutsidePcConnector   .Add( cnOriginalConnectToSideCn );
                     } //while
                  } //foreach

               // Export the selected pieces to a job file.

               var lisSelectedPcElementId = new HashSet<ElementId>();
               foreach( FabricationPart curPc in lisFabPc )
                  {
                  lisSelectedPcElementId.Add( curPc.Id );
                  }
               Debug.Assert( lisSelectedPcElementId.Count > 0 );

               String sFullPathJobFileName = "C:\\Temp\\TestExport.maj";

               ISet<ElementId> lisExportedFabPcElementId =
                  FabricationPart.SaveAsFabricationJob( doc, lisSelectedPcElementId, sFullPathJobFileName,
                                                        new FabricationSaveJobOptions(true) );
               if ( lisExportedFabPcElementId.Count == 0 )
                  {
                  // Does this situation ever happen?
                  }

               // Re-attach the pieces that were detached.
               if ( lisDetachedDuctBodySideConnector.Count > 0 )
                  {
                  ////////////////////////
                  // Doesn't work because the saved connections have already been destroyed.
                  ////////////////////////
                  for( int i = 0; i < lisDetachedDuctBodySideConnector.Count; i++ )
                     {
                     Connector cnDuctBodySideConnector = lisDetachedDuctBodySideConnector[ i ];
                     Connector cnOutsidePcConnector    = lisDetachedOutsidePcConnector   [ i ];
                     cnDuctBodySideConnector.ConnectTo( cnOutsidePcConnector );
                     }
                  ////////////////////////
                  }

               // Done with all the selected pieces. Need to commit the change.
               if ( trans.Commit() != TransactionStatus.Committed )
                  return Result.Failed;

               // OK - Fall through to exit.
               } //try
            catch( InvalidOperationException ex )
               {
               trans.RollBack();
               return Result.Failed;
               }
            } //if
         } //using

      return Result.Succeeded;
      }

   static Connector cnGetFirstNonPrimaryOrSecondaryConnector( ConnectorManager cm )
      // Get the first connector that is neither the primary connector nor the secondary connector.
      {
      foreach( Connector cn in cm.Connectors )
         {
         MEPConnectorInfo info = cn.GetMEPConnectorInfo();
         if ( ! info.IsPrimary   &&
              ! info.IsSecondary )
            return cn;
         }
      return null;
      }

   static Connector cnDisconnectSideConnection( Connector cnDuctBodySide )
      // Disconnect the connection in the specified connector.
      {
      if ( cnDuctBodySide == null )
         return null;

      Connector cnOriginalConnectTo = null;

      // This code is copied from the sample in Connector definition in Revit API.
      ConnectorSet connectorSetOfCnn = cnDuctBodySide.AllRefs;
      ConnectorSetIterator csi = connectorSetOfCnn.ForwardIterator();
      while( csi.MoveNext() )
         // This connection is in the outside piece (may be a boot-tap or a STO/STOD) that is
         // connected to the side of the duct body.
         {
         Connector cnConnected = csi.Current as Connector;
         if ( cnConnected != null )
            {
            // Look for physical connection and disconnect it.
            if ( cnConnected.ConnectorType == ConnectorType.End      ||
                 cnConnected.ConnectorType == ConnectorType.Curve    ||
                 cnConnected.ConnectorType == ConnectorType.Physical )
               {
               cnOriginalConnectTo = cnConnected;
               cnDuctBodySide.DisconnectFrom( cnOriginalConnectTo );
               break;
               }
            } //if
         } //while

      return( cnOriginalConnectTo );
      }

   } // class CCmdTestExport

public class CDuctSelectionFilter : ISelectionFilter
   // We use this class to create a Selection-Filter that only selects duct.
   {
   public bool AllowElement( Element element )
      {
      return( (BuiltInCategory)element.Category.Id.Value == BuiltInCategory.OST_FabricationDuctwork );
         // Note: ElementId.IntegerValue doesn't exist in Revit 2024.
         //       We have to use ElementId.Value.
      }

   public bool AllowReference( Reference refer, XYZ point )
      {
      return false;
      }
   } // class CDuctSelectionFilter

 

 

Please let me know if there is a way to re-attach the detached connections using Revit API.  Thanks in advance!

 

By the way, for people who cannot wait to have this problem fix.  They can use the "Exporter" in VM (a third party add-in).  Somehow the Exporter in VM doesn't have the problem, and they don't need to use the trick of detach and re-attach to work around this problem.

 

JC_BL

0 Likes
270 Views
2 Replies
Replies (2)
Message 2 of 3

jeremy_tammik
Alumni
Alumni

Is the following idea maybe a possible way to avoid the problem?

  

  • Instead of detaching the duct in order to export it, make a copy of it and export the copy instead.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 3 of 3

JC_BL
Advocate
Advocate

Thanks for the suggestion.   But making a copy of the duct has one problem.  The copy in the exported job file will have a different unique-ID.  When CAMduct nests the duplicated piece in the job file, the nesting info of the duplicated piece will contain a different unique-ID.  This means I cannot link the nesting info of the duplicated copy with the duct on a Revit project file.  I am not saying that this cannot be done.  But this will create many ripple effects downstream from exporting the pieces to a job file.

 

The sure way to work around this problem is to detach the piece before exporting it and then re-attach the piece afterward.  Unfortunately, I am not knowledgeable enough to re-attach the piece using Revit API.

 

The only workaround that I can come up with is to use the Exporter in VM -- a third party add-in.

 

Thanks for the suggestion anyway.

 

JC_BL

0 Likes