Odd phenom with plane.ClosestPoint or DistanceTo

In the file below, there are 6 planes, which started out as copies (no history). Then each one has had its top edge moved in the Y direction a tiny amount as indicated by the dots - to simulate a “just off vertical situation”. The idea was to check at what tolerance the script would detect a non-vertical condition.

Basically what it does is get the surface plane, move its origin to W0, make a point a certain height (roughly the size of the object) along the World Z axis and check the distance from this point to the closest point on the surface plane. Obviously a completely vertical surface will have essentially 0 distance, as the plane is tilted by moving the top edge there should be an increasing distance between the two points, getting greater the more the tilt.

Moving the check point higher along the world Z axis should result in a proportionally larger distance being measured. The script prints out this distance. I compare this to the file tolerance and if it is less than that, I select the object as being ‘vertical within tolerance’.

What I am finding is that the script (just a prototype for something else) is working OK for all except the second object - the one that has had the upper edge moved by only 0.001 (file tolerance). I would expect a distance to be printed out for that one that is smaller than the file tolerance, maybe around 0.0009. Instead it prints 0.0:

Now if I increase the ‘size’ variable by a factor of 10, all the values should be multiplied by 10 proportionally.

Which they do, all except the second one which remains 0.0.

I can increase the size as much as I want - to 1000, 10,000 or more - but the second one stubbornly stays at 0.0. Interestingly if I start decreasing the size value from 100, I start getting more of the planes selected, as one would expect - at 50 I get the third one lighting up, at 30 the fourth one, at 20 the fifth one, and at 10 all of them. So it is working in that direction. So what am I not understanding here?

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def TestPlaneWVertical(o_plane,size,tol):
    w0=Rhino.Geometry.Point3d(0,0,0)
    chk_pt=Rhino.Geometry.Point3d(0,0,size)
    o_plane.Origin=w0
    print(abs(o_plane.DistanceTo(chk_pt))) #debug
    if abs(o_plane.DistanceTo(chk_pt))<tol: return True
    return False

size=100.0 #start value, can change
tol=sc.doc.ModelAbsoluteTolerance

obj_ids=rs.ObjectsByType(8)
breps=[rs.coercebrep(obj_id) for obj_id in obj_ids]
for i,brep in enumerate(breps):
    face=brep.Faces[0]
    rc,obj_plane=face.TryGetPlane()
    if rc:
        if TestPlaneWVertical(obj_plane,size,tol):
            rs.SelectObject(obj_ids[i])

TestSrfs.3dm (2.9 MB)

EDIT:
OK, I have a possible suspect here -
Looks like moving the edge by only 0.001 (the file tolerance) results in the edge being pulled out of tolerance instead of the underlying surface itself actually getting changed. What shows this:

image

and I see this if I put a point at 0,0,100 and I zoom way in:

If I look at the next one which has had the edge moved 0.002, I see this:

image

So it looks like this might actually be a bug in the way moving a surface edge happens when the distances are very small. Not good IMO.

Edit 2:
More or less confirmed, RebuildEdges on that surface makes the edge go back to where it was before nudging.

Its not surprising to me that moving edges out of the plane does not always work reliably.

One might argue that the TryGetPlane() function not paying attention to edges is a bug.

Well, I have been doing it for a long time (with planar-based objects) and haven’t noticed any issues until now. First time I recall trying with such a small movement though.

Also, the Y+0.001 surface is a PlaneSurface, while the other 4 deformed ones are NurbsSurfaces.

The threshold appears to be 2X the model tolerance; moving the top edge of the surface at the origin by 0.002 in Y retains the PlaneSurface but with a maximum edge tolerance of 0.002.

I buggified this anyway to have it looked at…

RH-87196 Edge move at file tolerance does not change underlying surface

They should actually all remain Plane surfaces as that’s what they are.

@jim,

Surface.TryGetPlane is a surface operation, and it has nothing to do with Breps.

@Helvetosaur,

Surface.TryGetPlane is a virtual function and, thus, does different thing depending on the surface you are calling it on.

Two of the six surfaces are PlaneSurfaces. For these, TryGetPlane just returns the plane surface’s plane.

The other four surfaces are degree=1 NurbsSurfaces. To compute the plane of a NURBS surface, some surface evaluation and vector comparison is performed. Here is the source to ON_NurbsSurface::IsPlanar if you want more information.

Hope this helps.

– Dale

Yes, but they should all be Plane surfaces. Or, if moving any edge converts a Plane surface to a NURBS surface, then the one with the edge moved by 0.001 should also be a NURBS surface. But in fact it is still a plane surface because moving the edge by that amount has not converted it to NURBS with the new edge loop, it just moved the edge away from the surface and left it as a Plane surface.

The issue here is not getting the plane, the issue is that the edge on that surface is now off the surface and that should not happen.

@Helvetosaur - how are you moving the edges? How can I reproduce?

– Dale

Sub-object select the top edge, type a value at the Gumball arrow and Enter.

@Joshua_Kennedy - can you have a look at this?

I don’t think that edge is out tolerance. I think it’s at the very edge of but still within what is decided allowable by the document setting. This is the root of why the underlying surface isn’t being changed when you move that edge by the document tolerance. The surface fitter takes a look and says everything is within acceptable limits.

Yes, That is the problem that I am arguing is a bug. Mitch’s script called brep.Faces[0].TryGetPlane()

TryGetPlane is not the only example of the bug. A significant number of the BrepFace methods have nothing to do with Breps they are just pointers to BrepFace.UnderlyingSurface()

Some lazy Rhino Developer just copy and pasted a bunch of Surface methods into the BrepFace methods.
If users want an alias for BrepFace.UnderlyingSurface() they could make one themselves.

Over the years there have been numerous examples posted to this forum of users being tripped up by assuming the BrepFace methods actually applied to BrepFaces. Some do and some don’t.

Well, I don’t think this is a good thing. The original is an untrimmed plane surface and any kind of edge editing that keeps it a rectangular plane and untrimmed should result in a plane surface with 0 edge tolerance. Maybe this sort of thing needs to be special cased.

One could imagine that this same surface could have been achieved by rotating the surface very slightly at the X axis and stretching it by a tiny amount in the other direction. If you do that, the surface does remain a plane surface with 0 edge tolerance. Or simply make a planar surface from the edges - same thing.

Note also that the other four surfaces in the example file end up as NURBS surfaces after the edge is moved more than the file tolerance. Again, they all can be easily represented as a simple plane surface.