I get "Failure" result on a very simple BRepBuilder example, when I try to build a (4-sided triangle pyramid) Tetrahedron. I know about TessellatedShapeBuilder, but it fails for a quite complex object here, so I hope to bypass this with the BREP.
public class Test{
public Test(){}
// Keep track of already created edges and their orientation
struct BrepEdge {
public BRepBuilderGeometryId id;
public XYZ p1, p2;
}
List<BrepEdge> brep_edges = new List<BrepEdge>();
// add edge to face loop
void AddEdgeToBREP( BRepBuilder brep, BRepBuilderGeometryId loop, XYZ a, XYZ b) {
foreach(var be in brep_edges) {
// ab is p1-p2
if(be.p1.DistanceTo(a) < 1e-7 && be.p2.DistanceTo(b) < 1e-7) { brep.AddCoEdge(loop, be.id, false);return; }
// ab is p2-p1 (reversed edge)
if(be.p1.DistanceTo(b) < 1e-7 && be.p2.DistanceTo(a) < 1e-7) { brep.AddCoEdge(loop, be.id, true); return; }
}
// must create a new edge
BRepBuilderGeometryId edge = brep.AddEdge(BRepBuilderEdgeGeometry.Create(a, b));
brep.AddCoEdge(loop, edge, false);
var bed = new BrepEdge();
bed.p1 = a;
bed.p2 = b;
bed.id = edge;
brep_edges.Add(bed);
}
// add triangle face to solid
private void AddTriangleToBREP( BRepBuilder brep, XYZ a, XYZ b, XYZ c ) {
Plane plane = Plane.CreateByThreePoints(a, b, c);
BRepBuilderGeometryId face = brep.AddFace(BRepBuilderSurfaceGeometry.Create(plane, null), true);
var loop = brep.AddLoop(face);
AddEdgeToBREP(brep, loop, a, b);
AddEdgeToBREP(brep, loop, b, c);
AddEdgeToBREP(brep, loop, c, a);
brep.FinishLoop(loop);
brep.FinishFace(face);
}
///////////////////////////////////////////////////////////////////////////
public void run() {
BRepBuilder brep = new BRepBuilder(BRepType.Solid);
var points = new List<XYZ>(4);
points.Add(new XYZ(0, 0, 0)); // 0 origin
points.Add(new XYZ(1, 0, 0)); // 1 right
points.Add(new XYZ(0, 1, 0)); // 2 back
points.Add(new XYZ(0, 0, 1)); // 3 top
AddTriangleToBREP(brep, points[2], points[1], points[0]); // bottom face
AddTriangleToBREP(brep, points[0], points[1], points[3]); // front face
AddTriangleToBREP(brep, points[1], points[2], points[3]); // diagonal face
AddTriangleToBREP(brep, points[2], points[0], points[3]); // left face
var outcome = brep.Finish(); // <<<<< Failure
// throws: "This BRepBuilder object hasn't completed building data or was unsuccessful building it.
// Built Geometry is unavailable. In order to access the built Geometry,
// Finish() must be called first. That will set the state to completed."
var res = brep.GetResult();
}
}
Solved! Go to Solution.
Solved by jeremytammik. Go to Solution.
I cannot say why your code does not work.
Here is an example that does work:
https://thebuildingcoder.typepad.com/blog/2018/02/directshape-from-brepbuilder-and-boolean.html
However:
Please note that BRepBuilder
wasn’t really meant for 'manually' constructing geometry. Its interface is very cumbersome for that purpose. It was meant for translating existing geometry into Revit, with rather thorough validation of the input geometry.
I got another workaround using TesselatedShapeBuilder, but it's strange that I can't get the simplest example working with the BREP commands. Also, the error message is not very helpful.
Hi @jeremytammik , it has been a while since the question was posted, I am wondering if there is any good news or further details on the topic?
I experienced the same error when try to create a simple pyramid shape as a prototype for recreating geometry from linked IFC file. I would like to use some external algorithm the geometry and convert the external data to a mesh and rebuilt the new shape. The API documentation does not provide much of information about why it failed.
Would you have any suggestion for my problem? Thank you very much.
No news, just the same advice as above: try using the TesselatedShapeBuilder instead of the BRepBuilder.
It takes a bit of organisation but it does work.
It isn't ideal for manual creation since (1) you need to arrange the edges so they are compatible and (2) the edges need to sit on the surface.
I organised the above original into the below edge orders and directions and it worked fine. Each co-edge should be reversed on one face and not reversed on the other. Outer loops for a face should be anticlockwise with respect to the face normal (pointing outwards for a solid). So the edge is indicated as reversed on the face if it can't satisfy that e.g. the edge of two adjoining faces needs to be reversed on one of them.
Private Function Obj_230606a(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData,
ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) As Result
Dim UIApp As UIApplication = commandData.Application
Dim UIDoc As UIDocument = commandData.Application.ActiveUIDocument
If UIDoc Is Nothing Then Return Result.Cancelled Else
Dim IntDoc As Document = UIDoc.Document
'Only needs 6 edges
'Set on triangle if an edge of that triangle is reversed or not
'Each edge has two faces the edge should be reversed on one face and not on the other
'Outer loops should be anticlockwise with respect to normal
Dim A As New TriangleSide(1, 0)
Dim B As New TriangleSide(0, 2)
Dim C As New TriangleSide(2, 1)
Dim D As New TriangleSide(1, 3)
Dim E As New TriangleSide(3, 0)
Dim F As New TriangleSide(3, 2)
Dim T_ABC As New Triangle(A, B, C, New Boolean(2) {False, False, False})
Dim T_ADE As New Triangle(A, D, E, New Boolean(2) {True, False, False})
Dim T_BEF As New Triangle(B, E, F, New Boolean(2) {True, True, False})
Dim T_CFD As New Triangle(C, F, D, New Boolean(2) {True, True, True})
Dim Triangles As Triangle() = New Triangle(3) {T_ABC, T_ADE, T_BEF, T_CFD}
Dim Edges As TriangleSide() = New TriangleSide(5) {A, B, C, D, E, F}
'The coords
Dim X As Double = 1
Dim Y As Double = 1
Dim Z As Double = 1
Dim Points As XYZ() = New XYZ(3) {New XYZ(0, 0, 0),
New XYZ(X, 0, 0),
New XYZ(0, Y, 0),
New XYZ(0, 0, Z)}
Dim BrepB As New BRepBuilder(BRepType.Solid)
'Faces
For i = 0 To Triangles.Length - 1
Dim T As Triangle = Triangles(i)
Dim P As Plane = T.GetPlane(Points)
Triangles(i).FaceId = BrepB.AddFace(BRepBuilderSurfaceGeometry.Create(P, Nothing), False)
Next
'Edges
For i = 0 To Edges.Length - 1
Dim ed As TriangleSide = Edges(i)
Edges(i).EdgeId = BrepB.AddEdge(BRepBuilderEdgeGeometry.Create(ed.GetXYZ(0, Points), ed.GetXYZ(1, Points)))
Next
'Face loops
For i = 0 To Triangles.Length - 1
Dim T As Triangle = Triangles(i)
Triangles(i).LoopId = BrepB.AddLoop(T.FaceId)
Next
'co-edges
For i = 0 To Triangles.Length - 1
Dim T As Triangle = Triangles(i)
For ia = 0 To 2
Dim ed As TriangleSide = T(ia)
BrepB.AddCoEdge(T.LoopId, ed.EdgeId, T.Sides_Reversed(ia))
Next
BrepB.FinishLoop(T.LoopId)
BrepB.FinishFace(T.FaceId)
Next
Dim Outcome As BRepBuilderOutcome = BrepB.Finish
Using Tx As New Transaction(IntDoc, "DS")
If Tx.Start = TransactionStatus.Started Then
Dim DS As DirectShape = DirectShape.CreateElement(IntDoc, New ElementId(BuiltInCategory.OST_GenericModel))
Dim S As Solid = BrepB.GetResult()
DS.SetShape(New GeometryObject() {S}.ToList)
Tx.Commit()
End If
End Using
Return Result.Succeeded
End Function
Public Class TriangleSide
Public Property EdgeId As BRepBuilderGeometryId
Public ReadOnly Property N1 As Integer
Public ReadOnly Property N2 As Integer
Public Function GetXYZ(Idx As Integer, Points As XYZ()) As XYZ
If Idx = 0 Then
Return Points(N1)
ElseIf Idx = 1 Then
Return Points(N2)
Else
Throw New ArgumentOutOfRangeException
End If
End Function
Public Sub New(Node1 As Integer, Node2 As Integer)
N1 = Node1
N2 = Node2
End Sub
End Class
Public Class Triangle
Public Property LoopId As BRepBuilderGeometryId
Public Property FaceId As BRepBuilderGeometryId
Public ReadOnly Property Sides_Reversed As Boolean()
Public ReadOnly Property S1 As TriangleSide
Public ReadOnly Property S2 As TriangleSide
Public ReadOnly Property S3 As TriangleSide
Public Function GetPlane(Points As XYZ()) As Plane
'The edge ends could be reversed for a triangles so use mid points to ensure points are unique
'and no colinear
Dim Mid1 As XYZ = (Points(S1.N1) + Points(S1.N2)) / 2
Dim Mid2 As XYZ = (Points(S2.N1) + Points(S2.N2)) / 2
Dim Mid3 As XYZ = (Points(S3.N1) + Points(S3.N2)) / 2
Return Plane.CreateByThreePoints(Mid1, Mid2, Mid3)
End Function
Default Public ReadOnly Property Side(Idx As Integer) As TriangleSide
Get
If Idx = 0 Then
Return S1
ElseIf Idx = 1 Then
Return S2
ElseIf Idx = 2 Then
Return S3
Else
Throw New ArgumentOutOfRangeException
End If
End Get
End Property
Public Sub New(Side1 As TriangleSide, Side2 As TriangleSide, Side3 As TriangleSide, Reversed As Boolean())
S1 = Side1
S2 = Side2
S3 = Side3
If Reversed.Length <> 3 Then
Throw New ArgumentOutOfRangeException
End If
Sides_Reversed = Reversed
End Sub
End Class
It took me a while to figure out how the edges and faces should be added before the detail explanation and sample code reaches me. Thank you very much for your effort.
That's a really cool image, did you create it or you found in some Revit API presentation?
Is kinda missing a Four-sided dice in that image 😀
Another thing that is good to mention, is if your BRepType is Void, all the face normals must point into the void.
Thanks @ricaun I drew it with AutoCAD after a couple of attempts at what I wanted to get across using pencil, paper and eraser (more eraser than pencil and paper).
I'll have to check how important it is to set the boundaries of the surfaces. I'm sure I noticed previously and the other day that just specifying null may create a performance issue in Revit after the direct shape exists.
The best thing about the BRepBuilder in my view is that you can create single face solids. So all those API functions specific to the face class can be used on such.
@RPTHOMAS108 wrote:I'll have to check how important it is to set the boundaries of the surfaces. I'm sure I noticed previously and the other day that just specifying null may create a performance issue in Revit after the direct shape exists.
In my code, the boundaries of the surfaces are null as well, never have a problem but the most complex thing I did was create a copy of a Solid to change the Material. Works, but reordering and reversing each edge is painful.
@RPTHOMAS108 wrote:The best thing about the BRepBuilder in my view is that you can create single face solids. So all those API functions specific to the face class can be used on such.
For a simple face is possible to use TessellatedShapeBuilder, you can create a Solid or Mesh is fails.
In this D4 (tetrahedron) is much simpler to use TessellatedFace.
var shapeBuilder = TessellatedShapeCreatorUtils.Create(builder =>
{
var points = new List<XYZ>(4);
points.Add(new XYZ(0, 0, 0)); // 0 origin
points.Add(new XYZ(1, 0, 0)); // 1 right
points.Add(new XYZ(0, 1, 0)); // 2 back
points.Add(new XYZ(0, 0, 1)); // 3 top
var materialId = ElementId.InvalidElementId;
builder.AddFace(new TessellatedFace(new[] { points[2], points[1], points[0] }, materialId)); // bottom face
builder.AddFace(new TessellatedFace(new[] { points[0], points[1], points[3] }, materialId)); // front face
builder.AddFace(new TessellatedFace(new[] { points[1], points[2], points[3] }, materialId)); // diagonal face
builder.AddFace(new TessellatedFace(new[] { points[2], points[0], points[3] }, materialId)); // left face
});
Here is the full code: https://gist.github.com/ricaun/35baa2ed9f33de3487e46e4217b5e8bd with the TessellatedShapeCreatorUtils util.
public static class TessellatedShapeCreatorUtils
{
public static TessellatedShapeBuilderResult Create(Action<TessellatedShapeBuilder> actionBuilder)
{
TessellatedShapeBuilder builder = new TessellatedShapeBuilder();
builder.Target = TessellatedShapeBuilderTarget.AnyGeometry;
builder.Fallback = TessellatedShapeBuilderFallback.Mesh;
builder.OpenConnectedFaceSet(true);
actionBuilder?.Invoke(builder);
builder.CloseConnectedFaceSet();
builder.Build();
TessellatedShapeBuilderResult result = builder.GetBuildResult();
return result;
}
}
If you are working with a Revit surface BRepBuilder is the way to go.
Edited: Add tetrahedron in the D4 text.
Editing this thread for a blog post, I wonder what you mean by, "In this D4 is much simpler to use TessellatedFace".
Cheers
Jeremy
I mean a tetrahedron, a shape with 4 flat faces, a dice with 4 faces, or a D4 if you are familiar with dice and RPG (role-playing game). 😀
Edited: Add (role-playing game)
Thank you for the explanation. Now, I don't know what you mean by RPG 🙂
I checked it out and assume role-playing game?
Still, I edited and published this very illuminating discussion, Richard's organisation explanation and your sample code on the blog:
https://thebuildingcoder.typepad.com/blog/2023/06/brepbuilder-and-toposurface-interior.html#2
Thank you very much for the great advice!
Can't find what you're looking for? Ask the community or share your knowledge.