UPD: 16.10.2024
At the moment, I have managed to union 100% of 1717 solids (screenshot below)

by uploading to off-files and union them using CGAL. The result is an output off-file (it is in the screenshot below)

P.S: When trying to combine the Solids data through Revit, several dozens exceptions occurred about the inability to perform a Boolean operation on solid.
I checked in CGAL that the resulting mesh is correct, i.e. it does not have self-intersections, limits the positive volume, is correctly oriented, and is also a triangular, not a polygonal mesh.
Then I decided to create a Solid based on the off file using the following methods:
public static bool ReadOFF(string offFilePath, out List<XYZ> vertices, out List<int[]> triangles)
{
// 1. Чтение файла OFF
vertices = [];
triangles = [];
if (!File.Exists(offFilePath))
{
return false;
}
double k = UnitUtils.ConvertToInternalUnits(1, UnitTypeId.Meters);
var strs = File.ReadAllLines(offFilePath);
// Чтение количества вершин и треугольников
string[] counts = strs[1].Split();
int vertexCount = int.Parse(counts[0]);
int faceCount = int.Parse(counts[1]);
// Чтение координат вершин
for (int i = 3; i < 3 + vertexCount; i++)
{
string[] vertexData = strs[i].Split(' ');
double x = double.Parse(vertexData[0]) * k;
double y = double.Parse(vertexData[1]) * k;
double z = double.Parse(vertexData[2]) * k;
vertices.Add(new XYZ(x, y, z));
}
// Чтение треугольников (граней)
for (int i = 3 + vertexCount; i < 3 + vertexCount + faceCount; i++)
{
string[] faceData = strs[i].Split(" ")[1].Split(' ');
if (faceData.Length == 3) // Треугольная грань
{
int v1 = int.Parse(faceData[0]);
int v2 = int.Parse(faceData[1]);
int v3 = int.Parse(faceData[2]);
triangles.Add([v1, v3, v2]);
}
}
return vertices.Count >= 3;
}
And:
public static bool SolidFromMesh(List<XYZ> vertices, List<int[]> triangles, out Solid solid)
{
solid = null;
try
{
// 2. Инициализация BRepBuilder для создания замкнутого объёма
BRepBuilder brepBuilder = new(BRepType.OpenShell);
brepBuilder.SetAllowShortEdges();
brepBuilder.AllowRemovalOfProblematicFaces();
// 3. Проход по треугольникам и создание граней
foreach (int[] triangle in triangles)
{
XYZ v1 = vertices[triangle[0]];
XYZ v2 = vertices[triangle[1]];
XYZ v3 = vertices[triangle[2]];
if (v1.DistanceTo(v2) < 0.5 || v2.DistanceTo(v3) < 0.5 || v3.DistanceTo(v1) < 0.5)
{
continue; // Пропустить такие треугольники
}
// Создание плоскости для треугольной грани
Plane trianglePlane = Plane.CreateByThreePoints(v1, v2, v3);
// Добавление грани в BRepBuilder
BRepBuilderGeometryId faceId = brepBuilder.AddFace(
BRepBuilderSurfaceGeometry.Create(trianglePlane, null), false);
// Создание рёбер треугольника
BRepBuilderEdgeGeometry edge1 = BRepBuilderEdgeGeometry.Create(v1, v2);
BRepBuilderEdgeGeometry edge2 = BRepBuilderEdgeGeometry.Create(v2, v3);
BRepBuilderEdgeGeometry edge3 = BRepBuilderEdgeGeometry.Create(v3, v1);
// Добавление рёбер в BRepBuilder
BRepBuilderGeometryId edgeId1 = brepBuilder.AddEdge(edge1);
BRepBuilderGeometryId edgeId2 = brepBuilder.AddEdge(edge2);
BRepBuilderGeometryId edgeId3 = brepBuilder.AddEdge(edge3);
if (!brepBuilder.IsValidEdgeId(edgeId1) ||
!brepBuilder.IsValidEdgeId(edgeId2) ||
!brepBuilder.IsValidEdgeId(edgeId3))
{
}
// Создание цикла для грани
BRepBuilderGeometryId loopId = brepBuilder.AddLoop(faceId);
var coEdgeId1 = brepBuilder.AddCoEdge(loopId, edgeId1, false);
var coEdgeId2 = brepBuilder.AddCoEdge(loopId, edgeId2, false);
var coEdgeId3 = brepBuilder.AddCoEdge(loopId, edgeId3, false);
if (!brepBuilder.IsValidEdgeId(coEdgeId1) ||
!brepBuilder.IsValidEdgeId(coEdgeId2) ||
!brepBuilder.IsValidEdgeId(coEdgeId3))
{
}
if (!brepBuilder.IsValidLoopId(loopId))
{
}
if (!brepBuilder.IsValidFaceId(faceId))
{
}
brepBuilder.FinishLoop(loopId);
brepBuilder.FinishFace(faceId);
}
// 4. Завершение создания BRep
brepBuilder.Finish();
if (brepBuilder.IsResultAvailable())
{
solid = brepBuilder.GetResult();
return true;
}
}
catch (Exception e)
{
TaskDialog.Show("SolidHelper.SolidFromMesh()", e.Message);
}
return false;
}
But I came across the fact that with any combinations and variants of the
public static bool SolidFromMesh(List<XYZ> vertices, List<int[]> triangles, out Solid solid)
method when checking in
brepBuilder.IsResultAvailable()
at the end will be true only in the case of
BRepBuilder brepBuilder = new(BRepType.OpenShell)
In the case of
BRepBuilder brepBuilder = new(BRepType.Solid)
brepBuilder.IsResultAvailable()
- always returns false
I decided that the data was too complex for Revit, and then I created an off-file containing a regular cube:
OFF
8 12 0
-50 -50 -50
50 -50 -50
50 50 -50
-50 50 -50
-50 -50 50
50 -50 50
50 50 50
-50 50 50
3 2 1 0
3 3 2 0
3 4 5 6
3 4 6 7
3 0 4 7
3 0 7 3
3 6 5 1
3 2 6 1
3 0 1 5
3 0 5 4
3 6 2 3
3 7 6 3
In this case, I received the correct filled Solid (despite the instruction to create OpenShell).

Based on this, I believe that the main problem lies in the verification:
if (v1.DistanceTo(v2) < 0.5 || v2.DistanceTo(v3) < 0.5 || v3.DistanceTo(v1) < 0.5)
{
continue; // Пропустить такие треугольники
}
With such a check, of course, instead of Solid, we will get an Open Shell, as can be seen in the screenshot below:

If you make a check of at least 0.1
if (v1.DistanceTo(v2) < 0.1 || v2.DistanceTo(v3) < 0.1 || v3.DistanceTo(v1) < 0.1)
{
continue; // Пропустить такие треугольники
}
brepBuilder.IsResultAvailable()
- always returns false
Dear @jeremy_tammik can I ask you or someone from the development team to clarify in more detail what the introductory conditions are for the grid source data? Minimum edge lengths, minimum face area, etc? Because at the moment it seems to me that such an accurate geometry in Revit simply cannot be built from such constraints.
Thanks!