BRepBuilder fails on very simple example

GFric
Explorer
Explorer

BRepBuilder fails on very simple example

GFric
Explorer
Explorer

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();
    }
}
0 Likes
Reply
Accepted solutions (1)
1,967 Views
12 Replies
Replies (12)

jeremytammik
Autodesk
Autodesk
Accepted 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.

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes

GFric
Explorer
Explorer

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.

0 Likes

longt61
Advocate
Advocate

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.

jeremy_tammik
Autodesk
Autodesk

No news, just the same advice as above: try using the TesselatedShapeBuilder instead of the BRepBuilder.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

RPTHOMAS108
Mentor
Mentor

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.

 

230607_A.PNG

 

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

 

 

longt61
Advocate
Advocate

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.

ricaun
Advisor
Advisor

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.

 

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

RPTHOMAS108
Mentor
Mentor

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.

ricaun
Advisor
Advisor

@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.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

jeremy_tammik
Autodesk
Autodesk

Editing this thread for a blog post, I wonder what you mean by, "In this D4 is much simpler to use TessellatedFace".

  

Cheers

  

Jeremy

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes

ricaun
Advisor
Advisor

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)

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

0 Likes

jeremy_tammik
Autodesk
Autodesk

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!

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open