A recurring topic in the Revit API discussion forum concerns problems with solid Boolean operations:
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
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.
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:
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.