DomainSurface U,V Vs. Actual Sizes (Creating a grid of points on surface)

Hello,

I’ve been writing a python script to create a grid of points on a flat square surface, using the surface domain command and very similar to the one in the help.

def DivideSurface(): 
    srf = rs.GetObject("Pick surface to divide",rs.filter.surface)
    spacing = rs.GetInteger("Spacing:")
    offset = rs.GetInteger("Offset:")
    u = rs.SurfaceDomain(srf,0)
    v = rs.SurfaceDomain(srf,1)
    
    # Calculate the number of points we want to divide our surface into
    udiv = int(int(max(u))/spacing)
    vdiv = int(int(max(v))/spacing)
     
    # Turn off redraw
    rs.EnableRedraw(False)
    # Divide our surface up based on number of points required 
    for i in range(0, udiv+1, 1):
        for j in range(0, vdiv+1, 1):
            pt = (i/udiv,j/vdiv,0)
            srfP = rs.SurfaceParameter(srf,pt)
            newpt = rs.EvaluateSurface(srf,srfP[0],srfP[1])
            matrix = rs.XformTranslation((0,0,offset))
            newpt = rs.PointTransform(newpt, matrix)
            pts.append(rs.AddPoint(newpt))
    rs.EnableRedraw(True)
    return pts

Now the problem is that this works but the points can be created outside the surface. I’ve spent a couple of days now searching and fiddling around with the code but I’m none the wiser as to why the u,v lengths are different to the length and width of the actual surface. I’m clearly missing something fundamental about how this works but I can’t seem to crack it. I’ve tried adding tests for pointsinsurface, which does work but then it messes up the amount of points I need.

I’ve come to the conclusion if I could somehow get the accurate u,v values for the surface then it would achieve the grid as I want it but I don’t seem to be able to do so.

If you look at the below screenshot, I want the grid of points to all be neatly contained and centred within the surface. Any help would be greatly appreciated.

It’s because the points are being created on the UV of the underlying untrimmed surface without reference to the trim border. Untrim your surface to see…

If your surface is always rectangular and UV’s are parallel to the edges, you can shrink your trimmed surface to the edge - otherwise, I’m not sure how to guarantee an even distribution within the borders, as trims can be arbitrary. UV’s my also not be distributed “evenly” on untrimmed surfaces, depending on their parametrization.

–Mitch

Hi Tokyo, as you have noticed you can evaluate a NURBS surface anywhere, even outside the min/max - u/v domain.

I’ve modified your script so that it approximately fits to the surface domain. If it has to be more precise then you’ll have to calculate the upper bounds explicitly. I can explain later, for now this script should help:

import rhinoscriptsyntax as rs

def DivideSurface(): 
    srf = rs.GetObject("Pick surface to divide",rs.filter.surface)
    spacing = rs.GetInteger("Spacing:")
    offset = rs.GetInteger("Offset:")
    u = rs.SurfaceDomain(srf,0)
    v = rs.SurfaceDomain(srf,1)

    # Calculate the number of points we want to divide our surface into
    udiv = (u[1]-u[0])/spacing
    vdiv = (v[1]-v[0])/spacing

    # Turn off redraw
    rs.EnableRedraw(False)
    # Divide our surface up based on number of points required 
    pts = []
    for i in range(spacing+1):
        for j in range(spacing+1):
            up = u[0]+(udiv*i)
            vp = v[0]+(vdiv*j)
            newpt = rs.EvaluateSurface(srf,up,vp)
            matrix = rs.XformTranslation((0,0,offset))
            newpt = rs.PointTransform(newpt, matrix)
            pts.append(rs.AddPoint(newpt))
    rs.EnableRedraw(True)
    return pts
    
DivideSurface()

均匀点.py (759 Bytes)
Hi Tokyo
Check the attached files,

Many thanks for your responses.

Jess, I tried your code but it just seems to change the way may code works and still leaves the points outside. I should explain the spacing variable is the distance required between points, not how many divisions across the surface. So it allows me to create an approximate grid of points with a 500x500 spacing for example.

I need to get the gh plugin for rhino to try your code 6, which I’ll have to do later. Looking at the code, does the creation of the brep surface and edge give the accurate representation of the untrimmed surface?

I’ll look further into the untrimmed surface and trying to explicitly declare the upper boundaries, and post back.

OK, but then this will not work if the surface is angled or freeform. If you are are just dealing with planes, then you could get the surface plane and create the point grid on that plane. With IsPointOnSurface you can check if the point is on the surface.

The gh component DivideSurface divides the surface like my script does. But I think it also has methods to create point grids.

Hi Tokyo,

If your surface is rectangular the way it looks in your first reply, then Mitch already explained you the issue: you need to shrink it. That is exactly what grasshopper’s Divide Surface function from 603419608 post does too.

Here is an edited version of Jess upper script:

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def DivideSurface(): 
    brepId = rs.GetObject("Pick surface to divide",rs.filter.surface)
    brep = rs.coercebrep(brepId)
    brep.Faces.ShrinkFaces()
    srf = brep.Faces[0]
    spacing = rs.GetInteger("Spacing: ", 10)
    offset = rs.GetInteger("Offset: ", 10)
    u = rs.SurfaceDomain(brepId,0)
    v = rs.SurfaceDomain(brepId,1)
    
    # Calculate the number of points we want to divide our surface into
    udiv = (u[1]-u[0])/spacing
    vdiv = (v[1]-v[0])/spacing
    
    # Turn off redraw
    rs.EnableRedraw(False)
    # Divide our surface up based on number of points required 
    pts = []
    for i in range(spacing+1):
        for j in range(spacing+1):
            up = u[0]+(udiv*i)
            vp = v[0]+(vdiv*j)
            newpt = rs.EvaluateSurface(srf,up,vp)
            matrix = rs.XformTranslation((0,0,offset))
            newpt = rs.PointTransform(newpt, matrix)
            pts.append(rs.AddPoint(newpt))
    rs.EnableRedraw(True)

DivideSurface()

Thanks all for your replies, the shrinkfaces works nicely and that spurred me on to manage to get what I needed.

I’ve updated the script quite a bit to achieve what I want below. It now strips out points and works on rotation. The code I’m afraid isn’t very pretty but it does achieve what I wanted, I’m hoping the clean code will come with practice. I’ve left all the debug/learning bits in so it may of be some use to someone else who is just picking up this all up.

If anyone has any better ideas of how to achieve the same results especially with all the angle calculations I would gladly hear them. This took a while to work out and it feels like there must be a neater solution.

import rhinoscriptsyntax as rs
import math

def DivideSurface(GroupName): 
	brepId = rs.GetObject("Pick surface to divide",rs.filter.surface)
	if not brepId: return
	rs.SelectObject(brepId)
	rs.Command("_Reparameterize A _Enter")
	rs.Command("_SelNone")
	brep = rs.coercebrep(brepId)
	brep.Faces.ShrinkFaces()
	srf = brep.Faces[0]
	#udiv = rs.GetInteger("Number of Divisions in U",10)
	#vdiv = rs.GetInteger("Number of Divisions in V",10)
	spacing = 250
	offset = 750
	spacing = rs.GetInteger("Spacing:",500)
	offset = rs.GetInteger("Offset:",650)
	u = rs.SurfaceDomain(srf,0) # Get length/width of surface
	v = rs.SurfaceDomain(srf,1)	# Get length/width of surface
	
	# Calculate the number of points we want to divide our surface into
	udiv = int(round(abs(max(u)/spacing))) + 1# Make sure its positive abs(), coords can be -ve
	vdiv = int(round(abs(max(v)/spacing))) + 1
	# Calculate spacing between points
	spaceu = abs((u[1]-u[0])/(udiv+1))
	spacev = abs((v[1]-v[0])/(vdiv+1))
	# Calculate length of grid
	gridu = udiv * spaceu
	gridv = vdiv * spacev

	# First get boundary points to find which way the surface is
	pt0 = rs.EvaluateSurface(srf,u[0], v[0])
	pt1 = rs.EvaluateSurface(srf,u[0], v[1])
	pt2 = rs.EvaluateSurface(srf,u[1], v[1])
	pt3 = rs.EvaluateSurface(srf,u[1], v[0])
	ptc = rs.EvaluateSurface(srf,u[1]/2, v[1]/2)
	ptu = rs.EvaluateSurface(srf,u[1]/2, v[1]+500)
	ptv = rs.EvaluateSurface(srf,u[1]+500, v[1]/2)
	rs.AddLine(pt0,pt1)
	rs.AddLine(pt1,pt2)
	rs.AddLine(pt2,pt3)
	rs.AddLine(pt3,pt0)
	rs.AddPoint(pt0)
	rs.AddPoint(pt1)
	rs.AddPoint(pt2)
	rs.AddPoint(pt3)
	rs.AddPoint(ptc)
	rs.AddText("0",pt0,height=250)
	rs.AddText("1",pt1,height=250)
	rs.AddText("2",pt2,height=250)
	rs.AddText("3",pt3,height=250)
	rs.AddText("C",ptc,height=250)
	rs.AddText("U",ptu,height=250)
	rs.AddText("V",ptv,height=250)
	# Get Angle to determine if we need to rotate offset
	rAngle1 = rs.Angle(pt0,pt1) # u 
	rAngle2 = rs.Angle(pt0,pt3) # v
	rAngle3 = rs.Angle(pt0,pt2)	# h
	print("Angle 1: " + str(rAngle1[0]) + "Angle 2: " + str(rAngle2[0]) + " Angle 3: " + str(rAngle3[0]))
	# Check whether the object is rotated too far on axis to get
	rAngle = rAngle2[0]
	dAngle = math.radians(rAngle)
	uoffset = (((u[1]-u[0])/2) - (gridu/2))
	voffset = (((v[1]-v[0])/2) - (gridv/2))
	print("Uoffset: " + str(uoffset) + " Voffset: " + str(voffset) + " Angle: " + str(math.cos(dAngle)))
	print(rAngle)
	if rAngle > 1:
		# Calc hypo of offset
		h = math.sqrt(pow(uoffset*2,2) + pow(voffset*2,2))
		newang = rAngle + (90 - math.degrees(math.atan(voffset/uoffset)))
		# Generate offsets
		uoffset = abs((h * math.sin(math.radians(newang)))/2)
		voffset = abs((h * math.cos(math.radians(newang)))/2)
		print("rAngle: " + str(rAngle) + " Tan Angle: " + str(math.degrees(math.atan(uoffset/voffset))))
		print(" Hypotenuse: " + str(h) + " Angle: " + str(newang))
	# Pt0 should be bottom left hand one
	# Check u condition
	if abs(pt0[0]) > abs(pt2[0]):
		print("Flipping u")
		uoffset = -uoffset
	# Check v condition
	if abs(pt0[1]) < abs(pt2[1]):
		print("Flipping v")
		voffset = -voffset

	# Debug Print
	print("Point 0: " + str(pt0[0]) + " , " + str(pt0[1]))
	print("Point 1: " + str(pt1[0]) + " , " + str(pt1[1]))
	print("Point 2: " + str(pt2[0]) + " , " + str(pt2[1]))
	print("Point 3: " + str(pt3[0]) + " , " + str(pt3[1]))
	print("Angle Between Them: " + str(rAngle) + " Deg: " + str(dAngle))
	print("U Length: " + str(u[1]) + " Divisions: " + str(udiv) + " Spacing: " +  str(spaceu) + " Grid Size: " + str(gridu) + " Uoff: " + str(uoffset*2))
	print("V Length: " + str(v[1]) + " Divisions: " + str(vdiv) + " Spacing: " +  str(spacev) + " Grid Size: " + str(gridv) + " Voff: " + str(voffset*2))
	# Create points list
	pts = []
	
	# Turn off redraw - otherwise routine is very slow
	rs.EnableRedraw(False)
	n = 0
	
	# Divide our surface up based on number of points required 
	# First loop through points on row then each column

	# Function
	for i in range(0, udiv, 1):
		for j in range(0, vdiv, 1):
			# Keep track of total points
			n = n + 1
			# Work out point coordinates
			pt = (i/(udiv+1),j/(vdiv+1),0)

			# Converts a normalized surface parameter to a surface parameter; one within the surface's domain.
			srfP = rs.SurfaceParameter(srf,pt)

			# Evaluates a surface at a U,V parameter.
			newpt = rs.EvaluateSurface(srf,srfP[0],srfP[1])	

			# Creates a translation transformation matrix.
			# Move it on the surface only so we can test whether the point is on the surface
			matrix = rs.XformTranslation((uoffset,voffset,0))

			# Transforms a 3-D point.
			newpt = rs.PointTransform(newpt, matrix)
		
			# Test whether this point is on the surface
			test = rs.IsPointOnSurface(srf,newpt)
			if test:
				# Create a matrix to move AFFL
				matrix = rs.XformTranslation((uoffset,voffset,750))
				# Transforms a 3-D point.
				newpt = rs.PointTransform(newpt, matrix)
				# Add the new point to our list
				pts.append(rs.AddPoint(newpt))
	print("Points: ", str(len(pts)), " Not on surface: ", str(n-len(pts)))
	# Turn on redraw
	rs.EnableRedraw(True)
	# Check if GroupName is not empty, if it isn't automatically rename our group
	if GroupName:
		grpname = rs.AddGroup(GroupName)
	else:
		strgrpname = rs.StringBox("Name of calculation:")
		grpname = rs.AddGroup(strgrpname)
	print("Group added: " + str(grpname))
	rs.AddObjectsToGroup(pts, grpname)
	return pts, grpname
    
DivideSurface("")

Screenshot of it working below: