Revit families from Direct Shapes with materials with Rhino.Inside and C#

Hello!

I’m trying to create a set of Revit families (Mass category) from a set of Rhino files (each Rhino file is used as a block in another Rhino file, and we want to recreate the same structure in Revit, so we have one Revit family per Rhino block).

All objects in our Rhino files are closed polysurfaces, so we’ve used already the Rhino.Inside workflow of ComponentFamilyForm - NewComponentFamily - SaveComponentFamily to create all the families at once. The problem is that each Rhino file has many objects inside, and the Revit families and the Revit project file end up being so heavy that we can’t open it with Enscape, being that a major problem for the team. We have aprox. 43 different Revit families, each of them sometimes with over 200 elements inside, and in the project we have 402 instances in total of these families altogether.

Therefore we decided to transform our closed polysurfaces into meshes before we migrate them into Revit. We need them to keep the material they have in Rhino, so the only way we found to migrate them from Rhino into the Revit family is with AddGeometryDirectShape. The problem is that to use that component - correct me if I’m wrong please! - I need to have the Revit family open and as my active view. That means we need to do that process over 40 times every time we need to update the families. We tried using AddDirectShapeType, but I can’t give it a family name because I’m using Revit 2020 and it’s only available for Revit 2022 or above.

That’s why now I’m trying to automate the process with a C# component. So far I got to headless open the Revit family and select the material Id in it, create a TessellatedShape with the material assigned to each face, and place it as a DirectShape into the Revit family. The problem comes when I try to save and/or close the family, because it says I can’t use that method having another transaction, sub-transaction or group transaction open. The only transaction I create is the one to place the DirectShape in the file, and I check that it’s successfully committed, so I can’t figure out which other transaction I have open? I tried creating a separate transaction to open the Revit family and committing it, but it doesn’t work. I’ve also tried calling RhinoInside_AddGeometryDirectShape with NodeInCode but apparently it doesn’t find it.

I’ve attached the GH script and the Rhino and the Revit family files that I’m using to test it. See below the C# script too.

Thank you very much in advance!

RhinoBlockToRevitFamily_00.gh (15.6 KB)
TestFamily.3dm (160.0 KB)
TestFamily.rfa (492 KB)

using System;
using System.Collections;
using System.Collections.Generic;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

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

using RIR = RhinoInside.Revit;
using RhinoInside.Revit.Convert.Geometry;
using Rhino.NodeInCode;

 private void RunScript(DataTree<Point3d> meshPts, string Document, string MaterialName, ref object A)
  {

    //Lists
    List<DB.XYZ> RvtPts = new List<DB.XYZ>();
    List<DB.ElementId> materialsId = new List<DB.ElementId>();

    //Call the category to be used
    DB.BuiltInCategory BuiltCategory = DB.BuiltInCategory.OST_Roofs;

    //Open the family document
    DB.ModelPath filepath = new DB.FilePath(Document);
    DB.Document familyDoc = RIR.Revit.ActiveDBApplication.OpenDocumentFile(Document); //document class

    //Find material in familyDoc
    SelectRevitMaterials(familyDoc, MaterialName, materialsId);

    //Start a new TessellatedShapeBuilder
    DB.TessellatedShapeBuilder builder = new DB.TessellatedShapeBuilder();
    builder.OpenConnectedFaceSet(true);


    //Add Faces to TessellatedShapeBuilder
    //1.Transform Rhino 3DPoint into Revit XYZ
    //2.Create TessellatedFace with XYZ vertices and add to builder
    for (int i = 0; i < meshPts.BranchCount; i++)
    {
      for(int j = 0; j < meshPts.Branch(i).Count; j++)
      {
        Point3d pt = meshPts.Branch(i)[j];
        RvtPts.Add(RIR.Convert.Geometry.GeometryEncoder.ToXYZ(pt));
      }

      DB.TessellatedFace newFace = new DB.TessellatedFace(RvtPts, materialsId[0]);
      builder.AddFace(newFace);
      RvtPts.Clear();
    }

    //Close set and build builder
    builder.CloseConnectedFaceSet();

    builder.Target = DB.TessellatedShapeBuilderTarget.AnyGeometry;
    builder.Fallback = DB.TessellatedShapeBuilderFallback.Mesh;
    builder.Build();

    DB.TessellatedShapeBuilderResult result = builder.GetBuildResult();



    //Open a transaction in the familyDoc to bake in it
    bool autoTransaction = false;
    using(var t = new DB.Transaction(familyDoc, "Create DirectShape"))
    {
      if(!familyDoc.IsModifiable)
      {
        t.Start();
        autoTransaction = true;
        try
        {
          DB.DirectShape ds = DB.DirectShape.CreateElement(familyDoc, new DB.ElementId(BuiltCategory));
          ds.ApplicationId = "Application id";
          ds.ApplicationDataId = "Geometry object id";
          ds.SetShape(result.GetGeometricalObjects());
          familyDoc.Regenerate();
        }
        catch(Exception ex)
        {
          throw new Exception("Error info:" + ex.Message);
        }
        finally
        {
          if(autoTransaction)
          {
            t.Commit();
          }
        }
      }
    }

//Save and close family doc
familyDoc.Close(true); //gives error !!!!



  }

  // <Custom additional code> 
  public static void SelectRevitMaterials(DB.Document doc, string materialName, List<DB.ElementId> materialsId)
  {
    DB.FilteredElementCollector collector = new DB.FilteredElementCollector(doc);
    var materials = collector.WherePasses(new DB.ElementClassFilter(typeof(DB.Material)));


    foreach(DB.Material mat in materials)
    {
      string docMatName = mat.Name;

      if(docMatName == materialName)
      {
        DB.ElementId docMatId = mat.Id;
        materialsId.Add(docMatId);
      }
    }
  }

1 Like

Hi Esther,

Creating Families via NewComponentFamily will give you the best Material control and Editability.

With “over 200 elements” in the family you may want to consider breaking those up using or a different Revit Element, either a linked file or model group even. Hard to say without seeing what those contain.

Is this the error you are encountering in your Revit API script? See the link on the last post for more details.

Hi Japhy,

Thank you so much for your fast reply!

Sorry but unfortunately I can’t post the real geometry with the 200 elements because of privacy policy. The problem is that it’s a very detailed model, which is not ideal. Creating the families via NewComponentFamily works perfectly, but the model won’t load in Enscape.

Thanks for sending that link through - it was very useful! I’ve run the script with no documents open in Revit and it seems to work! So I guess the problem was that I had a Revit Project open, even though I wasn’t doing anything with the script in it.

Hi again,

just an update here - the problem wasn’t the fact of having another Revit project open. It does work with other Revit projects/families open.

The problem was that I was running the script several times when writing it to test it, and if the script doesn’t get to the part where we commit the transaction, the transaction seems to remain open until we entirely close Revit and open it again.