Sorting Objects according to the distance between them

Hello everyone,

I’m trying to sort some objects according to the distance between them (ADBT). I am working on a definition and there is a problem that I face. Whenever I put some planar closed curves into a grasshopper curve component they come in their random order. Of course if I select them in the order that I want, the list appears in the order I want to have but this is too much work when you work with too many curves. I want to create a workflow to put them in line ADBT.

NeoArchaic has this beautiful component for sorting points ADBT here. It works fine for points but when sorting the points are not the goal but just a tool to sort other objects, I don’t know how the use the sorted points information to sort other objects accordingly (if it’s possible).

The method that I thought about this to;

  1. When putting the objects into the GH component, first select the object that I want to it to be the first item in the list and then rectangle-drag select all the others (This makes sure that the starting point is the one that I want the sequence to start from)
  2. Find the center points of objects (area centroids if curves or surfaces or volume centroids if polysurfaces, breps or meshes)
  3. Create the copy of the centroids list.
  4. Starting a for loop to get the distances between the first point and all the other points.
  5. Do this for all points until all points are used.
  6. Merge the distances list with the object list in tuple groups as this [(distance[0], obj[0]), (distance[1], obj[1]), …]
  7. Sort this new list to get the tuples sorted.
  8. Exctract the 2nd items in the tuples to form a new list, thus getting the objects sorted.

I love the GH Sort List component and it works awesome. I tried to do want I want to achieve here using that as well. The thing that I couldn’t get right was the necessity to loop. I have to extract the evaluated points from the list and re-evaluate (a shift list and cull index would do it, but again since IDK how many times I’ll have to do it, I have to use some kind of loop action…)

I know it is an old subject but I couldn’t seem to find what I was looking for so I decided to post here.

This is the ghpython code (where x gets the objects (in this case some curves) and y gets the centroids) by which I tried to realize my ideas:

import rhinoscriptsyntax as rs
import Rhino as rh

crvs = x
pts = y

pts_dup = list(pts)

distances = []

for i in range(1, len(pts_dup)-1):
    first = rs.coerce3dpoint(pts_dup[:i])
    check = rs.coerce3dpoint(pts_dup[1:len(pts_dup)])
    gaps = rs.Distance(first, check)
    gaps.sort()
    distances.append(gaps[0])
    pts_dup.pop()
    

As the interpreter gave an error on the line where it reads;
“gaps = rs.Distance(first, check)”
I stopped writing the code because I couldn’t solve the error.

"Runtime error (ValueErrorException): Could not convert None to a Point3d

Traceback:
line 655, in coerce3dpoint, “C:\Users\Erdem\AppData\Roaming\McNeel\Rhinoceros\6.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript\utility.py”
line 365, in Distance, “C:\Users\Erdem\AppData\Roaming\McNeel\Rhinoceros\6.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript\utility.py”
line 24, in script"

I’m open to new ideas, maybe I am completely off from what I’m trying to achieve. I’m looking forward to some answers… Thanks for reading such a long post… :slight_smile:

The reason for your error is because you try to convert a list of points to a point, hence the None as result.

I would think that you need something like:


import rhinoscriptsyntax as rs

pts=y[:] #y typehint point3d
pt = x #x typehint point3d
a=[]
for i in range(0,len(pts)):
    dists = rs.Distance(pt, pts)
    newpts = [x for _,x in sorted(zip(dists,pts))]
    line = rs.AddLine(pt, newpts[0])
    a.append(line)
    pt = newpts[0]
    newpts.pop(0)
    pts = newpts[:]

1 Like

If you are seeking to sort your data by the example you gave then its a totally different algorithm. I can easily do a C# script that does the same as the link you provided. As well as include the other features you are after ( not only points).

Thank you for the response. This really looks a lot more neat.

Can I ask
1.what the “x for _” exactly mean in the line that goes

newpts = [x for _,x in sorted(zip(dists,pts))]
  1. If the newpts is [(distance[0], point[0]), …], then newpts[0] would return the distances and not points. So rs.AddLine wouldn’t function, am I right?

  2. Why do we make lines in the first place?

I understood the rest.

Thank you so much.

Hi Nicholas. Thank you very much for the offer. Though that would serve my purpose, I’d like to learn how to do it in the first place. Could you write it in VB? I’m trying to learn it as well along side Python (I have no knowledge of C#).

The lines you don’t need. I just added those to visualize the effect
_ is like a placeholder variable that you discard since you only need the new list of points.

I believe since I did not use i at all I could just have written:

for _ in range(0,len(pts))

edit: here is a better explanation:

1 Like

Sorry, I don’t know VB, I am a C# dude

1 Like

This is a Python feature, called list comprehension, which is a fast, inline, concise way to create lists via iteration.
The longer code that does the same would look something like this:

newpts = [] # empty list

for _, x in sorted(zip(dists, pts)): # loop the zipped and sorted values
    newpts.append(x) # append x, a value from pts, to newpts

zip() creates a new list of tuples, where each tuple includes a pair of items, one from the first and one from the second list:

list_1 = [3, 1, 2]
list_2 = ['a', 'b', 'c']

list_3 = zip(list_1, list_2)

print list_3
>>> [(3, 'a'), (1, 'b'), (2, 'c')]

sorted() simply sorts the values of an existing list, by default in ascending order. In the above list comprehension, it sorts the tuples by their first value; the distance, and thus sorts also the points.

print sorted(list_1)
>>> [1, 2, 3]

No, you only save the variable x to the newpts list. _,x in sorted(zip(dists,pts)) means that _ refers to a distance and x to a point in (dists, pts).
newpts should thus be a list of points!
If newpts were a list of tuples (i.e. [(dist, pt), (dist, pt), ...]), newpts[0]would also give you the first tuple from the list, not a distance or distances. In this case, newpts[0][0] would give you a distance value and newpts[0][1] a point.
To unpack all the distance values from such a list of tuples, you’d need another list comprehension:

dists = [dist for dist, pt in newpts]
print dists
>>> [dist, dist, dist, ...]
5 Likes

This was a torch that lighted the way p1r4t3b0y and helped me understand the code that Gijs have written. When I have the time I’ll work on it. I think I can handle from now on after all these explanations. Thanks a lot…

1 Like

:smiley: haha. Yes…

Hello again. I worked on the issue a bit and came until this point.

import rhinoscriptsyntax as rs
import Rhino as rh

crvs = rs.GetObjects("Curves to sort according to their distances", 4)

def get_centers(crvs):
    if not crvs: return
    centroids =[]
    for curve in crvs:
        center = rs.CurveAreaCentroid(curve)
        centroids.append(center[0])
    return centroids

pts = get_centers(crvs)
pts_dup = pts[:]
pts_and_dist = []

for pt in pts_dup:
    if len(pts_dup) < 2: break
    distance = rs.Distance(pts_dup[0], pts_dup[1:])
    #print distance
    #print len(pts_dup)
    wrapped = zip(distance, pts_dup[1:])
    #print wrapped
    wrapped.sort()
    #print wrapped
    pts_and_dist.append(wrapped[0])
    #print pts_and_dist
    pts_dup.pop(0)
    #print pts_dup
    #print len(pts_dup)

#print pts_and_dist

pts_and_dist.insert(0, (0,pts[0]))
sorted_pts = [x for (_,x) in pts_and_dist]

#print pts_and_dist
#print len(pts_and_dist)
#print sorted_pts

for i in range(len(sorted_pts)):
    rs.AddTextDot(str(i), sorted_pts[i])

This seemed to work at start but it doesn’t. Because in the first loop, I’m picking the points from the old unsorted list. What I should do is to pick the closest point to the last evaluated point… How do I do that?SPADBT.3dm (1.1 MB)

Hi there,

How about this?

import rhinoscriptsyntax as rs
import Rhino as rh

crvs = rs.GetObjects("Curves to sort according to their distances", 4)

def get_centers(crvs):
    if not crvs: return
    centroids =[]
    for curve in crvs:
        center = rs.CurveAreaCentroid(curve)
        centroids.append(center[0])
    return centroids

pts = get_centers(crvs)
sorted_pts = [pts.pop(0)]


while pts:
    pts.sort(key=lambda x: rs.Distance(sorted_pts[-1],x),reverse=True) # closest pt at the end
    sorted_pts.append(pts.pop()) # It is quicker to pop() from the end than from the beginning
    

for i, pt in enumerate(sorted_pts):
    rs.AddTextDot(str(i), pt)

And to sort out the object layer bit :slight_smile:

for i, pt in enumerate(sorted_pts):
    crv = rs.PointClosestObject(pt, crvs)[0]
    crv_layer = rs.ObjectLayer(crv)
    dot = rs.AddTextDot(str(i), pt)
    rs.ObjectLayer(dot, crv_layer)
1 Like

This is clever!!! Thanks for this and also for the past post.

I managed to it via expanding my first idea and I got the curves sorted. Now I want to sort crvs, srfs, polysrf and meshes via the same code and I’ve come with this idea but I receive an error.

“line 20, in get_centers, “””

What does it mean??

Here’s my GH file…unnamed.gh (1.0 MB)

What is the rest of the error message? Which line is line 20?

I only have an expired evaluation version of Rhino 6 at the moment so I can’t open your GrassHopper file… only rhino + python

There is nothing else that states another thing, I guess. (Except the detail of the main .py files that this error depends on)

I cannot post the code now but I tweaked the get_centers function so that it can say if the input objects are points, curves, surfaces, polysurfaces or meshes and then find their center points accordingly.

The way I did this is via

rs.ObjectType(obj)

method. And this error comes from the first line that includes this method.

When I comment out this first line then the same error occurs on the second line that includes the same method. There are 5 lines like this in the code (one for each object type).

An important note: The code works when I run it from within the EditPythonScript command’s dialog window. So, the code should be ok. I guess a setting in GHPython component or the GH itself needs a change. What do you think?

Hmm - do you need to set list access on the x input to the python component?

Already done it and this produces the error.

Item option causes the loopCount to be zero (“loopCount has no len” error)

ahh you are putting geometry into the component and rhinoscriptsyntax needs guids, not geometry objects

Set type hint to ghdoc object (rhinoscriptsyntax) works on Rhino 5