Face.GetNormal() Bug

I believe that I have found a bug in Face.GetNormal().

Backstory:
I want to be able to get the normal at any point on a brep. Brep.ClosestPoint() has an overload that will return a normal. Sadly for me, this out “normal” param will instead sometimes return a tangent. Because of this I have created the following function for getting the normal of a brep at a given point:

        private static Vector3d GetNormalAtPoint(Brep brep, Point3d point)
        {
            double closestDist = double.PositiveInfinity;
            Vector3d closestNormal = Vector3d.Unset;
            foreach (var face in brep.Faces)
            {
                face.ClosestPoint(point, out double u, out double v);
                Point3d closest = face.PointAt(u, v);
                double dist = (point - closest).Length;
                if (dist < closestDist)
                {
                    closestDist = dist;
                    closestNormal = face.NormalAt(u, v);
                }
            }
            return closestNormal;
        }

Sadly this seems to fail in rare cases. If the point where a normal is requested is on an edge where faces meet with low tolerance, the normal that is returned by Face.GetNormal is non-sense. The normal returned does not match either neighboring face.

To replicate the bug, I have attached a C# command and a 3d model. The 3d model contains a brep and a point. if you run the command, it will prompt you to select a brep, then a point, then it will output a line that represents the normal at that point. As you can see, this normal does not match the normal of either neighboring surface.

BuggyNormal.cs (2.1 KB)
BuggyNormal.3dm (1.2 MB)

Hi @Nick_Drian,

Check the BrepFace.OrientationIsReversed property and, if true, reverse the direction of closestNormal.

– Dale

Even if the normal were reversed, it would not be the correct normal for either of the neighboring faces.

close to the point, there is a very narrow 0.027 mm wide stripe of a surface…
the untrimmed version of this surface is in above screenshot (red)


check this issue again, after you fixed this issue.

1 Like

Thanks Tom! I didn’t even see that surface because some problematic trims made it invisible.

happy to help.

use
Brep.ClosestPoint
(the first, complex overload)
check the ComponentIndex - this will help to interpret the result.

kind regards -tom

Hi @Nick_Drian,

I’ve modified your code. See if this helps.

TestNick.cs (3.2 KB)

And another “fun” example.

TestBrepFaceNormal.cs (1.9 KB)

– Dale

1 Like

Yes, your code seems significantly more robust. It does however make me wonder if/why face.ClosestPoint would ever return a point where PointFaceRelation == Exterior.

And thanks for the fun example. It’s cool to see how one can extend Getters and override OnDynamicDraw :slight_smile:

A BrepFace is a topological object, and is a proxy for its underlying surface geometry. This when you use BrepFace.ClosesfPoint, you are evaluating the underlying untrimmed surface.

— Dale

I though that to find the closest point on the underlying surface, you would use Brep.Surfaces[idx].ClosestPoint. It feels like Brep.Faces[idx].ClosestPoint should return the closest point on the surface within the trim boundary. :thinking: Because after all a brep face is really a trimmed surface, and TrimmedSurface.ClosestPoint() seems like it should acknowledge the trims.

Just speaking from intuition here.

What you can do is use DuplicateFace method to make the face a single face brep and then Brep.ClosestPoint will give you the 3dpoint that’s within the trim boundary.

That 3dpoint can be used then to find the u,v coordinates of the surface and then the normal vector.