Creating and Inserting an Extrusion Family

Today is the

Pentecost
or

Whit Monday
holiday
in Neuchâtel, which gave me some time to explore an interesting and important issue in a bit more depth.

Several developers recently asked about creating families programmatically, so here is a first simple example to demonstrate this.
It creates a new structural stiffener family as an extrusion of a profile based on a list of XYZ points and inserts an instance of it into the current document.
Here are the required steps one by one:

  1. Create an extrusion element from a profile based on a list of XYZ points.
  2. Create a new structural stiffener family using the extrusion element.
  3. Insert an instance of the new family into the current document.

1. Creating an Extrusion for a Profile

The extrusion profile is defined by a list of XYZ points, in millimetres.
In this example, we just use a very trivial rectangular shape.
The thickness is also given in millimetres:


  static int n = 4;
 
  static List<XYZ> _countour = new List<XYZ>( n )
  {
    new XYZ( 0 , -75 , 0 ),
    new XYZ( 508, -75 , 0 ),
    new XYZ( 508, 75 , 0 ),
    new XYZ( 0, 75 , 0 )
  };
 
  const double _thicknessMm = 20.0;

Since all the input data is given in millimetres, and the extrusion has to be defined in the internal
Revit database unit for
length, which is
feet,
we need a conversion method for that, e.g.:


  /// <summary>
  /// Conversion factor from millimetre to foot
  /// </summary>
  const double _mm_to_foot = 1 / 304.8;
 
  /// <summary>
  /// Convert a given length to feet
  /// </summary>
  double MmToFoot( double length_in_mm )
  {
    return _mm_to_foot * length_in_mm;
  }
 
  /// <summary>
  /// Convert a given point defined in millimetre units to feet
  /// </summary>
  XYZ MmToFootPoint( XYZ p )
  {
    return p.Multiply( _mm_to_foot );
  }

In order to create an extrusion from the contour data, we need a sketch plane and a CurveArrArray instance.
The curve array array contains a list of loops, since the extrusion may require an outer loop and optional inner loops representing cavities.

Here is a helper method to create CurveArray from our XYZ point list:


  /// <summary>
  /// Convert a given list of XYZ points 
  /// to a CurveArray instance. 
  /// The points are defined in millimetres, 
  /// the returned CurveArray in feet.
  /// </summary>
  CurveArray CreateProfile(
    List<XYZ> pts,
    CreationApplication creapp )
  {
    CurveArray profile = new CurveArray();
 
    int n = _countour.Count;
 
    for( int i = 0; i < n; ++i )
    {
      int j = (0 == i) ? n - 1 : i - 1;
 
      profile.Append( creapp.NewLineBound(
        MmToFootPoint( pts[j] ),
        MmToFootPoint( pts[i] ) ) );
    }
    return profile;
  }

In this case, the method is designed to work in a family based on the structural stiffener family template, which contains a sketch plane in the XY plane named “Ref. Level” that we can work with.
Actually, it redundantly contains three of them, and we can simply pick the first one.
One way to select the existing sketch plane if we know the exact structure of the document we are working with is to explicitly use a hard coded element id for it:


  SketchPlane sketch = doc.get_Element(
    new ElementId( 501 ) ) as SketchPlane;

That has the advantage of not being language dependent, but makes us completely dependent on the internal database structure of the family template file instead.
A slightly more flexible approach is to search for the element by name using a helper function such as this:


  /// <summary>
  /// Return the first element found of the 
  /// specific target type with the given name.
  /// </summary>
  Element FindElement(
    Document doc,
    Type targetType,
    string targetName )
  {
    return new FilteredElementCollector( doc )
      .OfClass( targetType )
      .First<Element>( e => e.Name.Equals( targetName ) );
  }

This method is making use of LINQ.
A more efficient approach would use a filtered element collector based on a
parameter
filter to
check the name instead.
The advantage of that approach is that the filtering then happens on the internal Revit side, before the retrieved element data is passed out from Revit to the managed .NET environment.
In our case, there are not many sketch planes in the document to examine, so the overhead is negligible.

Using this method to find the sketch plane, and with the curve array and thickness in hand, the call to create the extrusion element in a family document can be set up like this:


  /// <summary>
  /// Create an extrusion from a given thickness 
  /// and list of XYZ points defined in millimetres
  /// in the given family document, which  must 
  /// contain a sketch plane named "Ref. Level".
  /// </summary>
  Extrusion CreateExtrusion(
    Document doc,
    List<XYZ> pts,
    double thickness )
  {
    FamilyItemFactory factory = doc.FamilyCreate;
 
    CreationApplication creapp = doc.Application.Create;
 
    SketchPlane sketch = FindElement( doc,
      typeof( SketchPlane ), "Ref. Level" )
        as SketchPlane;
 
    CurveArrArray curveArrArray = new CurveArrArray();
 
    curveArrArray.Append( CreateProfile( pts, creapp ) );
 
    double extrusionHeight = MmToFoot( thickness );
 
    return factory.NewExtrusion( true,
      curveArrArray, sketch, extrusionHeight );
  }

Next, let us see how to create a new family document in the background to make use of this method.

2. Creating an Extrusion Family

Creating a new family to contain our extrusion is a simple process:

  • Open the appropriate family template file.
  • Create the extrusion.
  • Save and close the file.

In theory, it should be possible to avoid the last step of saving the newly generated family document to a disk file, since it is possible to load the generated family document into a project environment straight from memory.
Unfortunately, I was unable to get this to work, because the call to ‘fdoc.LoadFamily( doc )’ caused a “serious error”.
Saving the family to a disk file and using ‘doc.LoadFamily( filename, out family )’ instead works, though.

It would also not be necessary to require a different file to be open to start with, but if it is not, it is impossible to close the family document.
It has to be closed, though, in order to use the new Revit 2012 API OpenAndActivateDocument method on it, in case we want to see the results of our new family definition in the family environment instead of looking at an insertion in the project environment.


  string templateFileName = Path.Combine( _path,
    _family_template_name + _family_template_ext );
 
  Document fdoc = app.NewFamilyDocument(
    templateFileName );
 
  if( null == fdoc )
  {
    message = "Cannot create family document.";
    return Result.Failed;
  }
 
  Transaction t = new Transaction( fdoc,
    "Create structural stiffener family" );
 
  t.Start();
 
  CreateExtrusion( fdoc, _countour, _thicknessMm );
 
  t.Commit();
 
  // save our new family background document
  // and reopen it in the Revit user interface:
 
  string filename = Path.Combine(
    Path.GetTempPath(), _family_name + _rfa_ext );
 
  SaveAsOptions opt = new SaveAsOptions();
  opt.OverwriteExistingFile = true;
 
  fdoc.SaveAs( filename, opt );
 
  // cannot close the newly generated family file
  // if it is the only open document; that throws 
  // an exception saying "The active document may 
  // not be closed from the API."
 
  fdoc.Close( false );

Now that the family has been created and save to a file, we can either add a call to open it programmatically in the foreground to view it using ‘uiapp.OpenAndActivateDocument( filename )’, or, assuming that we have an open active project document in the Revit user interface, immediately insert an instance of it.
In this case, let’s go for the latter.

3. Inserting an Instance of the New Family

Inserting a new family instance is something that we already covered numerous times in the past.
The last contribution on

placing a line based detail item instance
points
to some previous posts on this topic.

Here is an example of how we can place an instance of our newly created stiffener family into the current project at a selected point.
We make use of one of the simplest overloads of the NewFamilyInstance method taking just a point, the family symbol, and a structural type argument.

Before inserting the instance, we need to ensure that the family is in fact loaded at all.

Before loading the family, we need to check whether it has already been loaded.
This may happen, for instance, if the command is run multiple times.

Once loaded, we extract the family symbol from its container family, prompt the user for an insertion point, and create the new instance:

  • Start a transaction.
  • Retrieve all families.
  • Check whether the one we need has been loaded already.
  • If not, load it.
  • Extract its one and only symbol.
  • Insert an instance of it.

Here is the code to achieve these steps:


  t = new Transaction( doc,
    "Insert structural stiffener family instance" );
 
  t.Start();
 
  // load the family ... but check whether 
  // it was already loaded first
 
  Family family = null;
 
  FilteredElementCollector a
    = new FilteredElementCollector( doc )
      .OfClass( typeof( Family ) );
 
  int n = a.Count<Element>(
    e => e.Name.Equals( _family_name ) );
 
  if( 0 < n )
  {
    family = a.First<Element>(
      e => e.Name.Equals( _family_name ) )
        as Family;
  }
  else
  {
    // calling this without a prior call 
    // to SaveAs causes a "serious error":
    //family = fdoc.LoadFamily( doc ); 
 
    doc.LoadFamily( filename, out family );
  }
 
  FamilySymbol symbol = null;
 
  foreach( FamilySymbol s in family.Symbols )
  {
    symbol = s;
 
    // our family only contains one
    // symbol, so pick it and leave
 
    break;
  }
 
  XYZ p = uidoc.Selection.PickPoint(
    "Please pick a point for family instance insertion" );
  StructuralType st = StructuralType.UnknownFraming;
  doc.Create.NewFamilyInstance( p, symbol, st );
  t.Commit();

The steps presented above answer a whole bunch of questions and should provide a good starting point for your own further explorations.

Here is
Stiffener.zip containing
the complete source code and Visual Studio solution of the external command implementing them.

Another very common question in this area is how to perform Boolean operations, both in the family and project context.
That is a topic that I would like to take a look at in the near future, expanding on the example presented here.


Comments

16 responses to “Creating and Inserting an Extrusion Family”

  1. Hi, sir, how are you?
    Could you please tell me how to set a camera in Revit Architecture by using its API. Thanks

  2. Rudolf Honke Avatar
    Rudolf Honke

    Hi Jeremy,
    is it possible to get a list of all hard-coded ElementIds ?
    By the way, is it really a good idea to rely on hard-coded ElementIds ?
    Autodesk’s Revit developers may change them without any documentation…
    Bye, Rudolf

  3. Rudolf Honke Avatar
    Rudolf Honke

    Hi again,
    it says in your text that the ElementId (501) is defined in your own RFT file, so it may not be changed by Autodesk’s engineers, of course.
    But what about other Revit files?
    Many Elements in a RVT file with an ID < 1000 have an GUID beginning with “cad…-“, is this just a little in-joke ?
    In all of my (German) RVTs, ID 0 is given to the Phase “Neu”, and ID 3 is assigned to Phase “Bestand”, which may mean “New” and “Construction” in English Revits.
    So, besides the fact that IDs are depending on the template file, is it possible that there are IDs which are that fundamental that they are used in every Revit file ?
    Bye,
    Rudolf

  4. Dear Rudolf,
    There is no such list that I am aware of, and I certainly would not rely on any element id anywhere except in a file that I have complete control over myself.
    Cheers, Jeremy.

  5. Dear Jian,
    Nope, sorry, this is an open wish list item, as far as I know.
    Cheers, Jeremy.

  6. Rudolf Honke Avatar
    Rudolf Honke

    Hi Jeremy,
    yes, of course, you shouldn’t rely on fixed IDs, but was just curious about whether there is such a list or not.
    Now I’ve learned:
    Even “special” IDs like “0” (zero) are not reserved, there is no such list.
    Thanks for your reply,
    Rudolf
    I’m looking forward to the boolean operations theme ;-)

  7. Dear Rudolf,
    As always, there is no guarantee that I speak the truth. There may be reserved or fixed values that I don’t know about.
    Glad I raised you interest :-)
    Cheers, Jeremy.

  8. Hi Jeremy
    I have inserted family mass. I want to change the mass color. Do I need to create new parameter(color) into material? Or there are a way to change the color?
    Thanks
    Abby

  9. Dear Abby,
    Interesting question. I don’t really know off-hand. There are a lot off possibilities. Some are mentioned here:
    http://thebuildingcoder.typepad.com/blog/2010/01/model-and-detail-curve-colour.html
    http://thebuildingcoder.typepad.com/blog/2011/03/change-element-colour.html
    But as you point out, there are other ways to handle materials and colour as well.
    Also be sure to look at the developer guide, e.g.
    http://wikihelp.autodesk.com/Revit/enu/2012/Help/API_Dev_Guide/0074-Revit_Ge74/0118-Material118
    Please let us know what you find out. Thank you!
    Cheers, Jeremy.

  10. Hi Jeremy, just like to point out a little mistake in the sample code on the blog:
    Where it is:
    int n = a.Count(
    e => e.Name.Equals( _family_name ) );
    if( 0 () as Family;
    }
    Should be:
    int n = a.Count(
    e => e.Name.Equals( _family_name ) );
    if( 0 e.Name.Equals( _family_name )).First() as Family;
    }
    You need to repeat the Equals condition to find the correct family.
    And thanks for this post. It helped a LOT!

  11. Dear Israel,
    Thank you ever so much for joining the small and illustrious circle of people who ever bothered to point out a mistake on The Building Coder blog.
    I really appreciate it a lot!
    I corrected the code above and updated the zip file to download as well.
    I am very glad that you found this useful, and thank you for your appreciation!
    Cheers, Jeremy.

  12. Hi Jeremy, thanks for the compliments.
    I kept on going with this code, but I ran into a problem today and I need help. I need to Pick a host Element to Create a Door/Window instance in it. I created this code, but it is throwing a null exception. How can I achieve this?
    Here is my current code:
    Reference r = uidoc.Selection.PickObject(ObjectType.Element);
    Wall w = uidoc.Document.get_Element(r.ElementId) as Wall;
    var lc = w.Location as LocationCurve;
    var ponto0 = lc.Curve.get_EndPoint(0);
    var ponto1 = lc.Curve.get_EndPoint(1);
    Line linha = uiapp.Application.Create.NewLineBound(ponto0, ponto1);
    uidoc.Document.Create.NewFamilyInstance(r, linha, simbolo);
    Thanks,
    Israel Rodriguez

  13. I’ve tried this other variation, and I don’t get the null exception, but still doesn’t work:
    Reference r = uidoc.Selection.PickObject(ObjectType.Element);
    Wall w = uidoc.Document.get_Element(r.ElementId) as Wall;
    var lc = w.Location as LocationCurve;
    Line linha = uiapp.Application.Create.NewLineBound(ponto0, ponto1);
    uidoc.Document.Create.NewFamilyInstance(lc.Curve, simbolo, w.Level, st);

  14. Dear Israel,
    Simple. Please look at the explanation in
    http://thebuildingcoder.typepad.com/blog/2011/01/newfamilyinstance-overloads.html
    Specifically, you can use the code provided in
    http://thebuildingcoder.typepad.com/blog/2010/06/set-tag-type.html
    Cheers, Jeremy.

  15. Hi Jeremy,
    I tried to create extrusion based on room’s bounding and most of that work great, but in some case while creating the extrusion, i got some error message from revit. The error message is “Autodesk.Revit.Exceptions.ApplicationException:Unexpected internal error:code:20”, Is there any way to solve this error?
    Thanks
    Johnny

  16. Dear Johnny,
    Sorry, no idea off-hand.
    Try creating model curves from the boundary instead of an extrusion.
    Then you can look at them in the user interface and try to create the extrusion manually.
    That should provide more information on the problem, cf.
    http://thebuildingcoder.typepad.com/blog/2009/07/debug-geometric-form-creation.html
    Cheers, Jeremy.

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading