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.
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
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.
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. Because after all a brep face is really a trimmed surface, and TrimmedSurface.ClosestPoint() seems like it should acknowledge the trims.
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.