Here’s a Python script that should work for most surfaces.
Keep in mind that the found extreme points are always just an approximation.
import Rhino.Geometry as rg
import Grasshopper as gh
import scriptcontext as sc
def divide_surface(srf, udiv, vdiv, nested=False):
"""Divides a surface into a grid of uv-points.
srf (Rhino.Geometry.Surface): Surface to divide
udiv (int): Number of divisions in surface u-direction
vdiv (int): Number of divisions in surface v-direction
nested (bool): Optionally True to return a nested list [column][row],
by default False to return a flat list.
The division points."""
num_rows = udiv + 1
num_cols = udiv + 1
udom = rg.Interval(0, udiv)
vdom = rg.Interval(0, vdiv)
div_pts = 
for i in xrange(num_rows):
for j in xrange(num_cols):
div_pts.append(rg.Surface.PointAt(srf, i, j))
return [div_pts[i:i+num_rows] for i in xrange(0, len(div_pts), num_rows)]
def find_extremes_z(srf, udiv=10, vdiv=10, step=10, max_div=250, __pts=, __vals=):
"""Recursively finds the approximated extremes - highest and lowest point in z - on a surface.
srf (Rhino.Geometry.Surface): Surface to evaluate the z-extremes for
udiv (int): Optional number of start divisions in surface u-direction, by default 15
vdiv (int): Optional number of start divisions in surface v-direction, by default 15
step (int): Optional step value that increments udiv and vdiv each recursion level, by default 15
max_div (int): Optional maximum number of divisions to in uv-direction of the surface
The approximated lowest  and hightest point  on the surface."""
if udiv >= max_div or vdiv >= max_div:
sample_pts = divide_surface(srf, udiv, vdiv)
sample_pts.sort(key=lambda pt: pt.Z)
extremes = [sample_pts, sample_pts[-1]]
# print "U:", udiv, "V:", vdiv, "-> Extremes:", [pt.Z for pt in __pts]
if len(__pts) == 0:
__vals = [ for _ in xrange(len(extremes))]
return find_extremes_z(srf, udiv+step, vdiv+step, step, max_div, extremes, __vals)
for i in xrange(len(extremes)):
difference = abs(extremes[i].Z) - abs(__pts[i].Z)
if difference > 0.0:
__pts[i] = extremes[i]
if len(__vals) == 10:
dsum = sum([sum(lt) for lt in __vals])
if dsum < sc.doc.ModelAbsoluteTolerance:
return find_extremes_z(srf, udiv+step, vdiv+step, step, max_div, __pts, __vals)
if __name__ == "__main__":
if not D:
D = 250
E = find_extremes_z(S, max_div=D)
"Input parameter S failed to collect data"
The surface gets recursively more and more divided, until for at least 10 steps there was no change for both extremes found.
At each step all the division points are sorted and the extreme points compared to those found at the previous step.
This doesn’t exclude that for a later, even finer division, there would have been an even better result! This and the fact that the surface can infinitely be subdivided, makes the resulting extremes always an approximation.
max of surface 4.gh (199.6 KB)