I noticed that Rhino reports false minor deviation values that are nearly 10 times smaller than the actual huge deviation. These are highly misleading and give the user a false confidence about the quality and precision of the expected output surfaces.
Interesting. I have not noticed a discrepency that large but I also have not looked for it.
What does CrvDeviation report for the distance between the edges?
I no longer keep that scene, but I just created another surface and the difference between expected maximum deviation and real deviation of the output surface is quite large (almost 10 times worse).
I noticed this bug when I figured out that I can’t match surfaces that were supposed to be close enough within my file tolerance setting.
This file below contains two surfaces. The white surface is the original, whereas the cyan one is a rebuilt output using the following settings:
Rhino reports false deviation values.3dm (222.4 KB)
According to Rhino, the expected maximum deviation should be 0,0856711 mm, but the actual maximum deviation is 0,7700593 mm.
I tested @Rhino_Bulgaria example using Rhino Version 8 SR11 (8.11.24240.13481, 2024-08-27).
It appears that Rebuild of a surface uses a sparse set of locations to calculate the maximum deviation. In this examle those sparse locations are where the deviation is minimal.
In contrast Rebuild of a curve (for example the extracted edge of the white surface) reports the correct maximum deviation, presumably because Rebuild of a curve uses a much denser set of locations to calculate the maximum deviation.
PointDeviation between the two surfaces uses a much denser set of locations than Rebuild and provides the correct maximum deviation. There was a small but noticeable delay while PointDeviation calculated the deviation.
My guess is the use of sparse set of locations to calculate maximum deviation in Rebuild of a surface goes back to when the Rebuild command was introduced and CPU speed was a small fraction of what it is today. The choice of a sparse set may have been to minimize the time in calculate the “maximum deviation” during execution of Rebuild of a surface.
Unfortunately the “maximum deviation” shown during Rebuild of a surface can be very misleading and cause downstream problems. This should be fixed. In the meantime if knowing the actual maximimum deviation of a rebuilt surface is important then when rebuilding a surface the Delete input box should not be checked. After the surface is rebuilt use PointDeviation to check the deviation between the original surface and the rebuilt surface.
Hopefully the sample locations used for the calculation of the deviation could be raised to minimize the error. I don’t mind waiting 1 extra second if that will give a more accurate prediction for the output surface.
I wonder if this is the reason I have been sometimes struggling to sculpt surfaces properly. Especially when it comes to the matching process, I assume the CV changes should be very small relative to what I have just rebuilt. But then you see the surface go “crumble” in the viewport.
Sometimes I’ll keep going round and round rebuilding and matching for ages, wondering why the surface changes more drastically than the rebuild command suggests should happen.
I believe that this is the very reason for the unwanted high deviation that you noticed after rebuilding the surfaces. I had plenty of issues with rebuilt surfaces recently, this is why I ended up testing whether Rhino is reliable when it comes to calculating the expected deviation.
Hi @Rhino_Bulgaria, @davidcockey and @dale
This post caught my eye and I did some digging in our code and also found an old topic where @dale helped me calculate the deviation between 2 surfaces.
The example provided by Dale is this one:
My conclusion on why the reported deviation in Rebuild Surface is incorrect, is that only one surface is tested against the other but not also the other way around. In the example Dale provided, relevant samples are based on the surface knot count, however as the original and the rebuild surface are different, their samplepoint locations are also different. So relevant samplepoints of both surfaces need to be used to find all the relevant deviations
I altered the above script to test it on selected surfaces and noticed that depending on the picking order another deviation was found. The below version will test both surfaces against each other and the reported deviations are:
deviation AB : 0.0856710647197
deviation BA : 0.795439709366
These correspond with the deviation in the dialog and the one you found (albeit the actual deviation is larger as the location you found is close to the max deviation found.
As you can see the max deviation is at a samplepoint used by the script:

my script to test with:
test_srf_deviation.py (2.6 KB)
# coding=utf-8
from __future__ import division
import rhinoscriptsyntax as rs
import Rhino
def test_deviation(puv_samples , srf):
r_domain = []
for i in range(2):
r_domain.append(srf.Domain(i))
d_vecs = []
for po, un, vn in puv_samples:
uu = r_domain[0].ParameterAt(un)
vv = r_domain[1].ParameterAt(vn)
rc, ru, rv = srf.LocalClosestPoint(po, uu, vv)
if rc:
pr = srf.PointAt(ru, rv)
d_vecs.append(Rhino.Geometry.Vector3d(pr-po))
d_vecs.sort(key=lambda v : v.Length, reverse = True)
return d_vecs[0].Length
def helper_srfsrf_deviation(srf1, srf2):
def srf_samples(srf):
f_domain = []
for i in range(2):
f_domain.append(srf.Domain(i))
knot = []
for i in range(2):
k = srf.GetSpanVector(i)
knot.append(k)
puv = []
doc_pts = []
samples = 2
for j0 in range(knot[0].Count - 1):
u_interval = Rhino.Geometry.Interval(knot[0][j0], knot[0][j0 + 1])
for j1 in range(knot[1].Count - 1):
v_interval = Rhino.Geometry.Interval(knot[1][j1], knot[1][j1 + 1])
for i0 in range(samples + 1):
for i1 in range(samples + 1):
if i0 == 0 and j0 > 0: continue
if i1 == 0 and j1 > 0: continue
u = u_interval.ParameterAt(i0 / samples)
v = v_interval.ParameterAt(i1 / samples)
po = srf.PointAt(u, v)
doc_pts.append(rs.AddPoint(po))
nu = f_domain[0].NormalizedParameterAt(u)
nv = f_domain[1].NormalizedParameterAt(v)
puv.append([po,nu,nv])
return puv, doc_pts
puv1, doc_pts1 = srf_samples(srf1)
puv2, doc_pts2 = srf_samples(srf2)
dev1 = test_deviation(puv1 , srf2)
dev2 = test_deviation(puv2 , srf1)
print 'deviation AB : {}'.format(dev1)
print 'deviation BA : {}'.format(dev2)
return doc_pts1, doc_pts2
if __name__ == '__main__':
id1 = rs.GetObject('surface A', 8)
id2 = rs.GetObject('surface B', 8)
srf1 = rs.coercesurface(id1)
srf2 = rs.coercesurface(id2)
pts1, pts2 = helper_srfsrf_deviation(srf1, srf2)
rs.ObjectColor(pts1, rs.ObjectColor(id1))
rs.ObjectColor(pts2, rs.ObjectColor(id2))
rs.AddObjectToGroup(pts1, rs.AddGroup)
rs.AddObjectToGroup(pts2, rs.AddGroup)
PointDeviation provides a good result for the maximum deviation between the surfaces, independent of which surface is selected to provide points. The deviation estimate for surfaces in Rebuild needs to use the same methodology, including number of points tested, as PointDeviation.
