Making a series of circles on a curve with grasshopper and python

Hi guys,

i started using python in grasshopper not long ago. Right now I am trying to create a series of chain linked circles with increasing radius on a rail curve. And the centre of each circle is determined by the intersection of previous circle and the rail curve. I was hoping to create something like the evaluate length component in grasshopper which will create a set of points on a curve. And i was hoping my will work with a set of given values as distance between points. I have made the grasshopper definition of it as shown in the files attached. Meanwhile when i tried to recreate it in python but i can’t get it to work. can someone help me with this?test evaluate distance.gh (23.9 KB)

cheers much appreciated

test evaluate distance.3dm (25.7 KB)

Hi @dbtorchris,

Welcome to the forums! :slight_smile:

Here’s how to do the whole thing in Python:

import Rhino.Geometry as rg


div_points = []
div_points.append(Curve.PointAtStart)

cirs = []
prev_div_len = 0

for i in range(CircleCount):
    t = InitialLength * (PercentageAdded * 0.01)
    div_len = InitialLength + i * t
    
    total_div_len = prev_div_len + div_len
    prev_div_len = total_div_len
    
    div_pt = Curve.PointAtLength(total_div_len)
    div_points.append(div_pt)
    
    cir = rg.Circle(div_pt, div_len)
    cirs.append(cir)
 
# Outputs
Points = div_points
Circles = cirs

The division points are evaluated by curve length, instead of by circle intersections.
This is much more precise, since circle intersections produce different results - not faithful to the desired distance between division points - in places, where the curvature of the curve is too strong. Here, the distance between two division points will be bigger than the desired division length, when using circle intersections.

I hope this helps:
test_evaluate_distance_02.gh (24.4 KB)

Hi @diff-arch
Thank you for the help! that is great ! works like a charm and I am aware of the precision with evaluate curve by length. Although i am wondering if I need to make a chain of circles where the edges meets the centre point, then is there a way to achieve it with python?
cheers

You’re welcome!

Sure, everything is possible in scripting. You just have to come up with a way to achieve the goal.
I’m not sure, if this is the only, or even the best way, but here’s what I’ve come up with:

import Rhino.Geometry as rg


def average_points(points):
    """Solves the arithmetic average for a collection of points.
    
    Args:
      points: list of three-dimensional points.
    
    Returns:
      The average point.
    """
    sums = [0] * len(points[0])
    for pt in points:
        for i in range(len(pt)):
            sums[i] += pt[i]
    avgs = [s / len(points) for s in sums]
    return rg.Point3d(avgs[0], avgs[1], avgs[2])


# --- FIND THE DESIRED NUMBER OF DIVISION POINTS
div_points = []
div_points.append(Curve.PointAtStart)

prev_div_len = 0

for i in range(CircleCount):
    # Calculate the division length of the current curve segement
    t = InitialLength * (PercentageAdded * 0.01)
    div_len = InitialLength + i * t
    # Calculate the total division length from the start of the curve
    total_div_len = prev_div_len + div_len
    # Prevent the search for division points beyond the curve length
    if total_div_len <= Curve.GetLength():
        # Find the current division point
        div_pt = Curve.PointAtLength(total_div_len)
        div_points.append(div_pt)
        # Update the previous total division length
        prev_div_len = total_div_len


# --- CREATE THE CHAINED CIRCLES
center_pts = []
cirs = []

for i in range(len(div_points)):
    if i == 0: # first division point / start point
        continue # skip this iteration
    elif (i > 0) and (i + 1 < len(div_points)): # other division point / inbetween points
        # Calculate the circle radius, which is half the distance between the previous and next division point
        radius = div_points[i - 1].DistanceTo(div_points[i + 1]) / 2
        # Get the circle center point by averaging the previous and next division point
        center_pt = average_points([div_points[i - 1], div_points[i + 1]])
    else: # last division point / end point
        # Calculate the circle radius, which is the distance between the current and previous division point
        radius = div_points[i - 1].DistanceTo(div_points[i])
        # Get the circle center point, which is the current division point
        center_pt = div_points[i]
    center_pts.append(center_pt)
    # Create the chain circles
    cir = rg.Circle(center_pt, radius)
    cirs.append(cir)
    


# --- OUTPUTS
Points = div_points
Circles = cirs
Centers = center_pts

Each circle now passes through its neighbouring circles centers. However, you might have noticed that its very own center point has to move a little (cf. image above).
In order, to make each circle intersect its neighbouring circles center points and keep the desired diameter (curve division length), this probably has to happen. I’m not sure, though.
Unfortunately, depending on the curvature of the curve, some circle center points aren’t placed on the curve anymore. You could refit the curve through the new center points, though.

Furthermore, there are some algorithms that could solve this more efficiently. You could look into circle relaxation or even circle packing. These algorithms can be implemented in Python, as well as in vanilla Grasshopper with Kangaroo.

Even flying?
import antigravity :rocket:

2 Likes

Thanks! That is pretty cool!
I am pretty new in python but I am certainly really interested! I guess I still have a lot to learn.

1 Like

You’re welcome! Feel free to ask, if you have any questions about the script.

1 Like

Rhino Compute, Rhino Inside etc are adding new possibilities faster than you can learn them! Python and programming more generally open up a whole new world of options in Rhino and across an ever growing range of fields (web development, data science, deep learning, big data, …). You will allways have a lot to learn :smiley:

2 Likes

Cheers! The thing that I struggled the most with is the def definition. I watched some videos on recursive script, which allows you to add geometry to grow exponentially. It reminded me of how you can make a cluster component in grasshopper and link them up. Although i just find it difficult to visualise this process in python. I guess I just need to keep on practicing before get the hang of it.

Do you struggle with the concept of functions/methods or specifically with my average_points() function?

Are you interested in exponential growth for visualisation, let’s say to animate the script, or rather to re-evaluate data at each frame/iteration and thus change behaviour?

Sorry for the confusion, I mean in general, I struggle with the concept of functions/methods. in addition, since it’s all based on syntax then it is hard for me to visualise the logic in my head each step of the way.

maybe attached is a more simple alternative?circles_on_crv.gh (8.8 KB)

1 Like

I did the Codecademy python course https://www.codecademy.com/learn/learn-python when starting out 3 years ago - I found it very useful for getting the basics. There are lots of free resources of this type around and it really helps to do this kind of structured learning in parallel with practical coding tasks.

1 Like

In programming, the compiler usually reads the code from top to bottom and every line of code is executed in that order. The compiler is the mechanise which makes the translation from a high-level programming language (e.g. C++, Python) to a low level programming language (e.g. assembly, machine code) that for instance the processor in your computer understands.

Example 1:

x = 10
y = 2.5
z = 7

s1 = x + y
s2 = x + z
s3 = y + z

print s1
print s2
print s3

Functional programming, allows you to structure, refractor you code in more flexible ways. They basically work like mathematical functions.
The code inside functions is only read, when the function is called. You mostly call functions, by calling them by name.

Functions can have arguments, that you pass in when calling the function. Arguments are variables that you provide and that are probably used inside the function.
Most functions return some type of data, although there are many other ways to use functions.
Repetitive tasks that would have to be written over and over again, can for instance simply be put into a function and called when needed.

Example 2:

def add_numbers(number1, number2):
    added = number1 + number2
    return added

x = 10
y = 2.5
z = 7

s1 = add_numbers(x, y)
s2 = add_numbers(x, z)
s3 = add_numbers(y, z)

print s1
print s2
print s3

Example 3:

def add_numbers(number1, number2):
    added = number1 + number2
    return added

numbers = [10, 2.5, 7]
incremented = []

for num in numbers:
    s = add_numbers(num, 2)
	incremented.append(s)

print incremented

Of course, the topic is much more complex, but this should give you a simple overview.

2 Likes

Cheers! Thanks for the explanation. I will look into it :slight_smile:

Thanks! That is also brilliant !

Hey, is there a way to improve any of these scripts?
I needed a way such that the circles, when I take index 0,2,4 etc of the circles list, theyre tangential to each other. Right now, its not…