Saving a Solid to a SAT File Implementation

During my recent vacation, I published a description by my colleague Akira Kudo describing how to research the Revit SDK samples to find all the required bits and pieces to solve the task of saving a temporary in-memory Revit solid to an external SAT file.

Victor Chekalin, or Виктор Чекалин, reacted to that and provides a complete Visual Studio project to demonstrate the full implementation of that functionality.

You can download it as a zip archive or clone the project from GitHub.

I grabbed Victor's sample, added a couple of trivial enhancements and included it as a new command CmdExportSolidToSat to The Building Coder sample collection.

It demonstrates the following steps:

Determining the Full Path of a Family Template

One of the steps implements functionality to find the full file path of a given family template file using a recursive directory search method:

  /// <summary>
  /// Return the full path of the first file 
  /// found matching the given filename pattern
  /// in a recursive search through all 
  /// subdirectories of the given starting folder.
  /// </summary>
  string DirSearch(
    string start_dir,
    string filename_pattern )
  {
    foreach( string d in Directory.GetDirectories(
      start_dir ) )
    {
      foreach( string f in Directory.GetFiles(
        d, filename_pattern ) )
      {
        return f;
      }
 
      string f2 = DirSearch( d, filename_pattern );
 
      if( null != f2 )
      {
        return f2;
      }
    }
    return null;
  }

With this method in place, I can determine the full path of the standard metric mass family template using a single statement like this:

  // Search for the metric mass family template file
 
  string template_path = DirSearch(
    app.FamilyTemplatePath,
    "Metric Mass.rft" );

CmdExportSolidToSat External Command Implementation

The other steps listed above are all pretty standard.

Here is the complete code of the external command:

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Application app = uiapp.Application;
  Document doc = uidoc.Document;
  Selection sel = uidoc.Selection;
 
  // Retrieve all floors from the model
 
  var floors
    = new FilteredElementCollector( doc )
      .OfClass( typeof( Floor ) )
      .ToElements()
      .Cast<Floor>()
      .ToList();
 
  if( 2 != floors.Count )
  {
    message = "Please create two intersected floors";
    return Result.Failed;
  }
 
  // Retrieve the floor solids
 
  Options opt = new Options();
 
  var geometry1 = floors[0].get_Geometry( opt );
  var geometry2 = floors[1].get_Geometry( opt );
 
  var solid1 = geometry1.FirstOrDefault() as Solid;
  var solid2 = geometry2.FirstOrDefault() as Solid;
 
  // Calculate the intersection solid
 
  var intersectedSolid = BooleanOperationsUtils
    .ExecuteBooleanOperation( solid1, solid2,
      BooleanOperationsType.Intersect );
 
  // Search for the metric mass family template file
 
  string template_path = DirSearch(
    app.FamilyTemplatePath,
    "Metric Mass.rft" );
 
  // Create a new temporary family
 
  var family_doc = app.NewFamilyDocument(
    template_path );
 
  // Create a free form element 
  // from the intersection solid
 
  using( var t = new Transaction( family_doc ) )
  {
    t.Start( "Add Free Form Element" );
 
    var freeFormElement = FreeFormElement.Create(
      family_doc, intersectedSolid );
 
    t.Commit();
  }
 
  string dir = Path.GetTempPath();
 
  string filepath = Path.Combine( dir,
    "floor_intersection_family.rfa" );
 
  SaveAsOptions sao = new SaveAsOptions()
  {
    OverwriteExistingFile = true
  };
 
  family_doc.SaveAs( filepath, sao );
 
  // Create 3D View
 
  var viewFamilyType
    = new FilteredElementCollector( family_doc )
    .OfClass( typeof( ViewFamilyType ) )
    .OfType<ViewFamilyType>()
    .FirstOrDefault( x =>
      x.ViewFamily == ViewFamily.ThreeDimensional );
 
  View3D threeDView;
 
  using( var t = new Transaction( family_doc ) )
  {
    t.Start( "Create 3D View" );
 
    threeDView = View3D.CreateIsometric(
      family_doc, viewFamilyType.Id );
 
    t.Commit();
  }
 
  // Export to SAT
 
  var viewSet = new List<ElementId>()
  {
    threeDView.Id
  };
 
  SATExportOptions exportOptions
    = new SATExportOptions();
 
  var res = family_doc.Export( dir,
    "SolidFile.sat", viewSet, exportOptions );
 
  return Result.Succeeded;
}

Here is the SAT file SolidFile.sat generated by the two intersecting floors example that I used to test the RvtClipper Boolean operations for 2D polygons:

SAT file reimported into Revit

It generates the following warning when reimported into Revit, but the missing pieces do not make any visibly detectable difference:

SAT file import warning

They do however affect what Revit considers the size of imported object, because if I zoom the model containing nothing but this SAT import to its extents, the actual geometry appears pretty miniscular:

SAT file import extents

That need not worry us here, though.

Here is version 2014.0.104.0 of The Building Coder samples source code, Visual Studio solution and RvtSamples include file including the new CmdExportSolidToSat command.

Very many thanks to Victor for providing this nice implementation!