Solid Boolean Operation Alternatives

A recurring topic in the Revit API discussion forum concerns problems with solid Boolean operations:

Revit Booleans and OpenCascade

The question came up once again recently in the query on BooleanOperationsUtils ExecuteBooleanOperation InvalidOperationException cause:

Question: BooleanOperationsUtils.ExecuteBooleanOperation: When I use this method, I often get an InvalidOperationException. I would like to know if there are specific criteria for causing that error. For example, it occurs when elements intersect at an angle below a certain angle, etc. I hope there are rules for this or some documentation I can refer to.

Answer: Sorry about that. It has been mentioned before that ExecuteBooleanOperation can run into issues. Unfortunately, afaik, there is no list of the exact criteria which might cause a problem. Various ways of handling the situation and some possible workarounds have been discussed here in the past. You can search the Revit API discussion forum for ExecuteBooleanOperation or just Boolean to find some of them.

In Revit, one also encounters situations like this:

Red object to subtract from white object

Red object to subtract from white object

Zoom to the corner

Zoom to the corner

The development team are aware of these issues. Boolean operation fail is an exhaustive and long-ongoing discussion of the topic including a suggestion by Tommy @tommy.stromhaug Strømhaug for a non-trivial workaround using OpenCascade.

CGAL Solid Booleans

Andrey @ankofl Kolesov recently shared a solution using the Computational Geometry Algorithms Library CGAL and the OFF file format to perform Boolean operations on solids, presented in the Revit API discussion forum thread on how to execute Boolean operations on Revit solid by AutoCAD:

After extensive discussion and in-depth research on transferring the solids to AutoCAD, OpenCascade, or some other library (check out the discussion threads mentioned above for that), Andrey opted for a different solution, saying:

Well, it seems I'm finally close to a solution that suits me:

Create .off file from Revit solid:

public static bool WriteOff(
  this Solid solid,
  out List<string> listString)
  {
    listString = ["OFF"];

    if(solid.CreateMesh(out var listVectors, out var listTri))
    {
      listString.Add($"{listVectors.Count} {listTri.Count} 0");
      listString.Add($"");

      foreach (var p in listVectors)
      {
        listString.Add(p.Write());
      }

      foreach (var v in listTri)
      {
        listString.Add($"3  {v.iA} {v.iB} {v.iC}");
      }

      return true;
    }
    return false;
  }

And this:

public static bool CreateMesh(
  this Solid solid,
  out List<XYZ> listVectors,
  out List<Tri> listTri)
  {
    double k = UnitUtils.ConvertFromInternalUnits(1, UnitTypeId.Meters);
    listVectors = [];
    listTri = [];

    bool allPlanar = true;

    int indV = 0;
    foreach (Face face in solid.Faces)
    {
      if (face is PlanarFace pFace)
      {
        Mesh mesh = pFace.Triangulate();
        for (int tN = 0; tN < mesh.NumTriangles; tN++)
        {
          var tri = mesh.get_Triangle(tN);

          var pT = new int[3];

          for (int vN = 0; vN < 3; vN++)
          {
            var p = tri.get_Vertex(vN) * k;

            if (p.Contain(listVectors, out XYZ pF, out int index))
            {
              pT[vN] = index;
            }
            else
            {
              pT[vN] = indV;
              listVectors.Add(p);
              indV++;
            }
          }

          listTri.Add(new(pT[2], pT[1], pT[0]));
        }
      }
      else
      {
        allPlanar = false;
      }
    }

    return allPlanar;
  }

Load .off file

#pragma once

bool load_from(const char* path, Mesh& output) {
  output.clear();
  std::ifstream input;
  input.open(path);
  if (!input) {
      return false;
  }
  else if (!(input >> output)) {
      return false;
  }

  input.close();
  return true;
}>

Execute Boolean

#pragma once

bool boolean_simple(Mesh m1, Mesh m2, b_t type, Mesh& out) {
  out.clear();
  int code = 0;
  if (!CGAL::is_triangle_mesh(m1)) {
    PMP::triangulate_faces(m1);
  }
  if (!CGAL::is_triangle_mesh(m2)) {
    PMP::triangulate_faces(m2);
  }
  if (type == b_t::join) {
    if (!PMP::corefine_and_compute_union(m1, m2, out)){
      std::cout << "fail_join ";
      return false;
    }
  }
  else if (type == b_t::inter) {
    if (!PMP::corefine_and_compute_intersection(m1, m2, out)){
      std::cout << "fail_inter ";
      return false;
    }
  }
  else if (type == b_t::dif) {
    if (!PMP::corefine_and_compute_difference(m1, m2, out)) {
      std::cout << "fail_dif ";
      return false;
    }
  }
  else {
    throw;
  }
  return true;
}

Save .off file:

#pragma once
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>

bool save_to(const std::string path, Mesh input) {
  if (!CGAL::is_valid_polygon_mesh(input)) {
    return false;
  }
  try {
    if (CGAL::IO::write_polygon_mesh(path + ".off", input,
      CGAL::parameters::stream_precision(17)))
    {
      return true;
    }
    else {
      return false;
    }
  }
  catch (const std::exception& e) {
    std::cout << "save_to: exception!" << std::endl;
  }
  return false;
}

Then you can upload the .off file back to Revit, or do other manipulations with it. However, as far as I know, API Revit does not allow you to create a full-fledged Solid object, but only a triangular grid, i.e. you can upload the grid obtained through CGAL to Revit for viewing, but you will not be able to perform further operations on solid with it, but only view its geometry through DirectShape.

My main task is to create an energy model of a building and calculate heat loss, the required amount of energy to maintain the internal temperature in rooms at a given temperature outside, taking into account the thermal resistance to heat transfer of building structures. I implemented the intersection of 3D surfaces in 2D and converting the result back to 3D to determin the intersection of indoor surfaces with outdoor space:

Intersection of indoor surfaces with outdoor space

Many thanks to Andrey for sharing this solution that will hopefully help many others struggling with problematic solid Boolean operations.

Looking at the history of CGAL, I see that it also includes the very powerful LEDA Library of Efficient Data types and Algorithms that I looked into myself a long time ago, before it was merged into CGAL. LEDA is very impressive in itself, so CGAL must be quite something.