Final Rolling Offset Using Pipe.Create

I am writing this on the way back to Switzerland from my hiking trip on La Palma. Actually, lacking good bandwidth during my travels, I actually arrived back before being able to post this...

Even before my return, I already have some good news to share:

Victor Chekalin provided a working sample of using the static Pipe.Create method, so I can update my rolling offset series to make use of that more modern and flexible method rather than the older NewPipe one.

Here are the steps so far in the rolling offset exploration:

My last words in the last post stated that it just about concludes this project, except for the question on how to use Pipe.Create instead of Document.Create.NewPipe, which is still open.

When exploring the pipe creation, I had a look at using the static Pipe.Create method but quickly resorted to NewPipe instead.

Victor very kindly provided a solution and an explanation that forms a useful basis for exploring and solving this in more depth:

A Working Sample Using Pipe.Create

Victor responded to my post, saying "I was able to create a pipe using new method with first attempt :-)"

Here is Victor's initial code:

  // Extract all pipe system types
 
  var mepSystemTypes
    = new FilteredElementCollector( doc )
      .OfClass( typeof( PipingSystemType ) )
      .OfType<PipingSystemType>()
      .ToList();
 
  // Get the Domestic hot water type
 
  var domesticHotWaterSystemType =
    mepSystemTypes.FirstOrDefault(
      st => st.SystemClassification ==
        MEPSystemClassification.DomesticHotWater );
 
  if( domesticHotWaterSystemType == null )
  {
    message = "Could not found Domestic Hot Water System Type";
    return Result.Failed;
  }
 
  // Looking for the PipeType
 
  var pipeTypes =
    new FilteredElementCollector( doc )
      .OfClass( typeof( PipeType ) )
      .OfType<PipeType>()
      .ToList();
 
  // Get the first type from the collection
 
  var firstPipeType =
      pipeTypes.FirstOrDefault();
 
  if( firstPipeType == null )
  {
    message = "Could not found Pipe Type";
    return Result.Failed;
  }
 
  var level = uidoc.ActiveView.GenLevel;
 
  if( level == null )
  {
    message = "Wrong Active View";
    return Result.Failed;
  }
 
  var startPoint = XYZ.Zero;
 
  var endPoint = new XYZ( 100, 0, 0 );
 
  using( var t = new Transaction( doc ) )
  {
    t.Start( "Create pipe using Pipe.Create" );
 
    var pipe = Pipe.Create( doc,
      domesticHotWaterSystemType.Id,
      firstPipeType.Id,
      level.Id,
      startPoint,
      endPoint );
 
    t.Commit();
  }

A Sample of Using the View.GenLevel Property

The View.GenLevel property is not very richly documented.

This is all the Revit API help file has to say about it: "The level for the view. This will obtain the level for views related to levels, such as Plan Views. If this View is not generated by a level, this property is null."

Note that Victor's code above shows a nice example of making use of that property.

See below for a discussion on how to retrieve the proper level from an existing pipe element.

An Explanation of the Pipe.Create Piping System Type Argument

Victor's explanation: The reason for your problem is the systemTypeId parameter. You passed the wrong id.

In the help file (and in your post) the description for systemTypeId says it requires "the id of the piping system type".

The keyword here is type, i.e., you should pass the element id of the MEPSystemType or PipingSystemType class for the selected pipe.

Your code attempts to use the system, not the type:

  ElementId idSystem = pipe.MEPSystem.Id;

If you want to create the rolling offset pipe with the same system type as the original pipe, you should use the following code:

  ElementId systemIdTypeId
    = pipe.MEPSystem.GetTypeId();

In a real project, you would presumably first check pipe.MEPSystem for null:

  ElementId systemIdTypeId;
 
  if( pipe.MEPSystem != null )
  {
    systemIdTypeId = pipe.MEPSystem.GetTypeId();
  }
  else
  {
    // Select some default systemTypeId
    // Extract all pipe system types
    var mepSystemTypes
      = new FilteredElementCollector( doc )
        .OfClass( typeof( PipingSystemType ) )
        .OfType<PipingSystemType>()
        .ToList();
 
    // Get the Domestic hot water type
    systemIdTypeId = mepSystemTypes.FirstOrDefault(
      st => st.SystemClassification ==
        MEPSystemClassification.DomesticHotWater );
  }

Updated Rolling Offset Using Pipe.Create

Once I had retrieved the MEP system and pipe system types as suggested by Victor, I assumed that I could grab the level from the existing pipe.

My initial attempt, however, using the value of the existing pipe's Element.LevelId property, did that in the wrong manner as well.

It returns an invalid element id for the level, causing the call to Pipe.Create to throw an exception saying, "The level id levelId is not valid. Parameter name: levelId".

I used the BipChecker to look more closely at the existing pipe parameters and discovered a built-in parameter RBS_START_LEVEL_PARAM in there with ElementId storage type that returns its 'Reference Level' instead and is not invalid.

As soon as I used that instead, the pipe creation worked.

Retrieving All Pipe.Create Argument Values from an Existing Pipe

I used the BipChecker on all three pipes to more closely examine their differences and similarities; here are the results for the two original ones, pipe 1, pipe 2 and from the rolling offset pipe 3.

It is illuminating to compare these three, so I suggest you do so :-)

I still need to adjust the new pipe diameter, and I ought to use the same hydronic supply piping system type before trying to connect them.

To summarise my findings and extract all the argument values for the call to Pipe.Create from existing pipe parameters, here are some of your options:

Updated Rolling Offset Using Pipe.Create

Putting all of this together, we arrive at the following:

  tx.Start( "Rolling Offset" );
 
  if( _place_model_line )
  {
    // . . .
  }
  else if( _place_fittings )
  {
    // . . .
  }
  else
  {
    if( _use_static_pipe_create )
    {
      // Element id arguments to Pipe.Create.
 
      ElementId idSystem;
      ElementId idType;
      ElementId idLevel;
 
      // All these values are invalid for idSystem:
 
      ElementId idSystem1 = pipe.MEPSystem.Id;
      ElementId idSystem2 = ElementId.InvalidElementId;
      ElementId idSystem3 = PipingSystem.Create(
        doc, pipe.MEPSystem.GetTypeId(), "Tbc" )
          .Id;
 
      // This throws an argument exception saying
      // The systemTypeId is not valid piping system type.
      // Parameter name: systemTypeId
 
      //pipe = Pipe.Create( doc, idSystem,
      //  idType, idLevel, q0, q1 );
 
      // Retrieve pipe system type, e.g. 
      // hydronic supply.
 
      PipingSystemType pipingSystemType
        = new FilteredElementCollector( doc )
          .OfClass( typeof( PipingSystemType ) )
          .OfType<PipingSystemType>()
          .FirstOrDefault( st
            => st.SystemClassification
              == MEPSystemClassification
                .SupplyHydronic );
 
      if( null == pipingSystemType )
      {
        message = "Could not find hydronic supply piping system type";
        return Result.Failed;
      }
 
      idSystem = pipingSystemType.Id;
 
      Debug.Assert( pipe.get_Parameter(
        BuiltInParameter.RBS_PIPING_SYSTEM_TYPE_PARAM )
          .AsElementId().IntegerValue.Equals(
            idSystem.IntegerValue ),
          "expected same piping system element id" );
 
      // Retrieve the PipeType.
 
      PipeType pipeType =
        new FilteredElementCollector( doc )
          .OfClass( typeof( PipeType ) )
          .OfType<PipeType>()
          .FirstOrDefault();
 
      if( null == pipeType )
      {
        message = "Could not find pipe type";
        return Result.Failed;
      }
 
      idType = pipeType.Id;
 
      Debug.Assert( pipe.get_Parameter(
        BuiltInParameter.ELEM_TYPE_PARAM )
          .AsElementId().IntegerValue.Equals(
            idType.IntegerValue ),
        "expected same pipe type element id" );
 
      Debug.Assert( pipe.PipeType.Id.IntegerValue
        .Equals( idType.IntegerValue ),
        "expected same pipe type element id" );
 
      // Retrieve the reference level.
      // pipe.LevelId is not the correct source!
 
      idLevel = pipe.get_Parameter(
        BuiltInParameter.RBS_START_LEVEL_PARAM )
          .AsElementId();
 
      // Create the rolling offset pipe.
 
      pipe = Pipe.Create( doc,
        idSystem, idType, idLevel, q0, q1 );
    }
    else
    {
      pipe = doc.Create.NewPipe( q0, q1,
        pipe_type_standard );
    }
 
    pipe.get_Parameter( bipDiameter )
      .Set( diameter );
 
    // Connect rolling offset pipe segment
    // directly with the neighbouring original
    // pipes
    //
    //Util.Connect( q0, pipes[0], pipe );
    //Util.Connect( q1, pipe, pipes[1] );
 
    // NewElbowFitting performs the following:
    // - select appropriate fitting family and type
    // - place and orient a family instance
    // - set its parameters appropriately
    // - connect it with its neighbours
 
    Connector con0 = Util.GetConnectorClosestTo(
      pipes[0], q0 );
 
    Connector con = Util.GetConnectorClosestTo(
      pipe, q0 );
 
    doc.Create.NewElbowFitting( con0, con );
 
    Connector con1 = Util.GetConnectorClosestTo(
      pipes[1], q1 );
 
    con = Util.GetConnectorClosestTo(
      pipe, q1 );
 
    doc.Create.NewElbowFitting( con, con1 );
  }
 
  tx.Commit();

Once again, the resulting model looks exactly the same as the one presented previously, generated by explicitly placing the elbow fittings.

The hopefully final solution to generate the rolling offset pipe and properly place and connect the elbow fittings is included in The Building Coder samples GitHub repository, in the external command CmdRollingOffset, and the version discussed here is release 2014.0.106.6.

Now back to work again, and catching up with the backlog...

Addendum: Victor already posted on this topic in Russian: Создание трубы с помощью метода Pipe.Create (Making tube method using Pipe.Create).

The built-in Firefox Tools > Translate Page rendering of this looks useful and interesting to me as well :-)

Many thanks to Victor for all his help on this!