Python recursion simple error

Hi,
I am confused as to why this code is not working. I believe it has to do with the fact that Rhino draws circles with radii rather than diameter. I am simply trying a simple recursion where circles are scaled by half the diameter and moved by half of the diameter. Attached is an image I made in processing.

import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

a = []

x = 2
y = 0
p1 = rs.coerce3dpoint([x,y,0]) 


def recursiveCircle(p1, r, num): #input variables
        r = 3
        print r/2
        p2 = rs.AddPoint(x + r, 0, 0)
        a.append(p2)
        cir1 = rs.AddCircle(p1, r)
        a.append(cir1)  

        if (num > 1):
             #counter
            
            #recursive function ; needs to be diameter
            recursiveCircle(p1 + rg.Point3d(x + r*.5, y, 0), r*.5, num-1) 
            
 
      
recursiveCircle(p1, r, 2) #input parameters

I am sure that I am missing something silly. I just don’t totally understand the hierarchy of the recursive functions.

  1. declare the function with the variables
  2. call the function outside the function with parameters
  3. pass the outside parameters through the insider recursive function.

Thank you for the help!
Erik

Then main thing wrong in the above is that you have the definition of r as an inital value inside the function. It should be outside, otherwise there is no initial value and it errors out. If you do that, your script will run as written.

However, I get the feeling you are looking to get all the circles tangent at the right quadrant, which the posted script doesn’t do correctly. It’s in how you define your new center point. I prefer to define it explicitly before passing it into the recursion.

Edit - forgot to look at the first image. Modified the script to match the image, also included two other options.

import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

a = []
x = 2
y = 0
p1 = rs.coerce3dpoint([x,y,0]) 
r = 3 #this needs to be outside the recursion, it's an initial value

def recursiveCircle(p1, r, num): #input variables
        print r/2
        p2 = rs.AddPoint(x + r, 0, 0)
        a.append(p2)
        cir1 = rs.AddCircle(p1, r)
        a.append(cir1)  

        if (num > 1):
            #counter
            #recursive function ; needs to be diameter
            p1=rg.Point3d(p1.X+r,p1.Y,p1.Z)
            #p1=rg.Point3d(p1.X+r*0.5,p1.Y,p1.Z) #tangent inside
            #p1=rg.Point3d(p1.X+r*1.5,p1.Y,p1.Z) #tangent outside
            recursiveCircle(p1, r*.5, num-1)
            
recursiveCircle(p1, r, 10) #input parameters

HTH, --Mitch

Perfect. Thanks Mitch.
Works perfectly.

Quick question: 1. the final call of ‘recursiveCircle’ contains the same variables as the declaration of the function, is this common practice?

I ask because in GH Python when I add a slider to ‘num’ I now how the declaration function, the recursive function, as well as the called function containing the same variable.
Thanks again

  1. is there an better way to declare p1? I know I can use ‘addPoint’, but is there a way to do it without an rs method?
    something like p1 = ([x,y,0])? (this does not work obviously)

not that i’m answering any of your questions but… :wink:

…here’s another approach using python for the loop then grasshopper for the center points and circles:

recCircle_2.gh (6.2 KB)


a = []
b = []

while (num > 0):
	a.append(x)
	b.append(rad)
	x = x + rad
	rad = rad / 2
	num = num - 1

Thank you Jeff,

I agree with the combined approach and think it is much more powerful as well as faster; nevertheless, in this instance, I have been practicing Recursion. In order to create the below show complex shape, it can be quicker to just code the options rather than stacking GH components. Of course, this only works if the code is solid :slight_smile: I am not the greatest coder and have been trying to justify when to use code as opposed to relying on GH (which I normally do).

In almost every instance, I would definitely agree with you.

I added the negative direction to your code. I wonder if there is a way to achieve it without the extra complexity I added.

Thanks,
Erik


hey Erik…
so i’ve been messing around with this on&off since you posted… here are the two solutions i’ve come up with:

1- just python / rhinoscriptsyntax:

import rhinoscriptsyntax as rs
rad = 25
num = 3
x = 0
y = 0

pt = rs.AddPoint(x, y, 0)
ptlist = [pt]
rs.AddCircle(pt, rad)

def quads(rad, num, ptlist):
    temp = ptlist
    ptlist = []

    if num > 0:
        for i in range(len(temp)):

            pt1 = rs.CopyObject(temp[i], (rad, 0, 0))
            pt2 = rs.CopyObject(temp[i], (0, rad, 0))
            pt3 = rs.CopyObject(temp[i], (-rad, 0, 0))
            pt4 = rs.CopyObject(temp[i], (0, -rad, 0))

            ptlist.extend([pt1, pt2, pt3, pt4])

        rad = rad / 2
        num = num - 1
        for i in range(len(ptlist)):
            rs.AddCircle(ptlist[i], rad)
        quads(rad, num, ptlist)

quads(rad, num, ptlist)


.

.


then a grasshopper / python method which doesn’t use any rhinoscriptsyntax… it makes three lists… X position, Y position, and Radius then feeds these lists to grasshopper Point and Circle components:

RecCircle.gh (2.3 KB)

rlist = [rad]
pts = [(0, 0)]
xlist = [0]
ylist = [0]

def quads(rad, num, pts):
    temp = pts
    pts = []

    if num > 0:
        for i in range(len(temp)):

            pt1 = (temp[i][0] + rad, temp[i][1])
            pt2 = (temp[i][0], temp[i][1] + rad)
            pt3 = (temp[i][0] - rad, temp[i][1])
            pt4 = (temp[i][0], temp[i][1] - rad)
            pts.extend([pt1, pt2, pt3, pt4])

            xlist.extend([pt1[0], pt2[0], pt3[0], pt4[0]])
            ylist.extend([pt1[1], pt2[1], pt3[1], pt4[1]])
            rlist.extend([rad / 2] * 4)

        rad = rad / 2
        num = num - 1
        quads(rad, num, pts)

quads(rad, num, pts)


hope there’s something in the above to help you along.

…otherwise, it was deceptively challenging for me and i learned a thing or two from the exercise so thanks for posting it :wink:

Hi Erik,

In answer to your question, I do this all the time (using the same names for variables inside and outside of functions), and it appears not to go against convention:

However,one should be aware that the two "r"s refer to two variables which have a different scope.
(Which was your initial problem). In other words, the outside “r” will still have the original value after you run the recursion. As long as one is aware of variable scope, there’s no problem, but to be super clear you might want to use different names.

Cheers,
Thomas

Unfortunately Python hasn’t got something like Golang’s ‘gofmt’, but there is also an official style guide for Python: PEP8. And it also has a section about naming conventions.

heh, in Atom.app (which is what mac users are doing python/rhinoscript with), there’s a package you can install called autopep8 :

…it formats all your code to the guidelines of pep8… it’s pretty sweet and i use it often.
(fwiw)

Yeah, Atom is a nice piece of software…

Thanks Jeff and ParamDesSing,

I appreciate the feedback. The naming conventions are very helpful.
Nice work Jeff, I like the two approaches… very clean.

I have a new recursive question since we are on a pseudo-fractal thread:

How can I delete previous generations as the recursion increases?
I have a GH hack, but it doesn’t work after 5 generations. It should I would think…

import rhinoscriptsyntax as rs
import Rhino.Geometry as rg

a = []
num = 6

def vicsek(CRVGUID,num):
   
    crvGUID = CRVGUID
    
    if (num > 1):
        num -= 1
        #explodeLines
        lines = rs.ExplodeCurves(crvGUID)
        #evaluate paramaters
        parameter = rs.CurveParameter(lines[0],(1.0/3.0))
        parameterA = rs.CurveParameter(lines[0],(0))
        length = rs.CurveLength(lines[0])
        #addPTs
        p0 = rs.EvaluateCurve(lines[0], parameterA)
        p1 = rs.EvaluateCurve(lines[0], parameter)
        p2 = rg.Point3d(p1.X,p1.Y+length/3,0)
        #createRects
        rectComp1 = rs.AddRectangle(rg.Plane(p0,rg.Vector3d(0,0,1)),length/3,length/3)
        rectComp2 = rs.CopyObject(rectComp1, rg.Vector3d(length*2/3,0,0))
    
        transCtr2 = rs.VectorSubtract(p2,p0)
        rectCompCtr = rs.CopyObject(rectComp1, transCtr2)

        rectComp3 = rs.CopyObject(rectComp1, rg.Vector3d(0,length*2/3,0))
        rectComp4 = rs.CopyObject(rectComp2, rg.Vector3d(0,length*2/3,0))
        
        
        a.extend([rectComp1,rectComp2,rectCompCtr,rectComp3,rectComp4])
        
        
        #recursion
        vicsek(rectComp1,num)
        vicsek(rectComp2,num)
        vicsek(rectCompCtr,num)
        vicsek(rectComp3,num)
        vicsek(rectComp4,num)
        
        

def Main():
    
    crvGUID = rs.AddRectangle(rs.WorldXYPlane(),20,20)
    vicsek(crvGUID,num)  
    
Main()