Python Error..Help! Not sure why

Sorry I am new to python and learning from this exact video and not yielding the same results. (See in GH file on the top left corner)

Can anyone give me a pointer?

Thanks in advance! Python for Recursive Aggregation.gh (24.8 KB)

Hi @tamsamantha06,

Yes. At it again, eh? :slight_smile:

Sanchez’s method for getting the face boundary vertices was a little over-engineered, so I simplified it. He got the end points for each line segment of a surface boundary, which produces quite a lot of duplicate points and this is where the main issue comes from. Each line segment shares a vertex with the one before and after it, meaning that for closed surface boundaries you get duplicates for each vertex.
Also getting the surface center point is superfluous for this example!

rs.OrientObject() tries to construct a plane from the points that are provided. I believe it at least needs three distinct points to find the x- and y-axes of a plane. If you select point_list[0], point_list[1], and point_list[2] as the source points, and point_list[1] and point_list[2] are duplicates of each other, the plane construction fails, because you only provide two distinct points!

Furthermore, you have to pay attention that the indices of the selected vertices are not beyond the available vertex count. For tri faces only indices 0, 1, 2 are available and for quads 0, 1, 2, 3!
Negative indices can be used in Python to get items in reverse. For example, -1 gets you the last item from a list.

2020-12-05 11-50-51.2020-12-05 11_53_18

Here’s the revised code:

import rhinoscriptsyntax as rs


def get_vertices(srf):
    border = rs.DuplicateSurfaceBorder(srf)
    vertices = rs.PolylineVertices(border)
    vertices.pop(-1)
    return vertices


def aggregate(obj, point_list, count):
    source = [point_list[0], point_list[1], point_list[2]]
    target1 = [point_list[-1], point_list[-2], point_list[-3]]
    target2 = [point_list[0], point_list[-1], point_list[-2]]
    print source
    
    if(count % 3 == 0):
        new_obj = rs.OrientObject(obj, source, target1, 1)
    else:
        new_obj = rs.OrientObject(obj, source, target2, 1)
    
    return new_obj
    

def recursive_aggregation(obj, gens, obj_list=[], count=0):
    if(gens == 0):
        return obj_list
    
    all_srf = rs.ExplodePolysurfaces(obj)
    
    point_set1 = get_vertices(all_srf[2])
    point_set2 = get_vertices(all_srf[3])
    point_set3 = get_vertices(all_srf[0])

    if (count % 2 == 0):
        new_obj = aggregate(obj, point_set1, count)
    else:
        new_obj = aggregate(obj, point_set2, count)

    copy = rs.CopyObject(new_obj)
    obj_list.append(copy)

    return recursive_aggregation(new_obj, gens-1, obj_list, count+1)


a = recursive_aggregation(brep, iterations)

For real recursion, you also need to return function() within function(). Sanchez did only a shallow recursion at best.

Generally speaking, unlike C, C#, Java, etc., Python is not written using UsingDuckTyping (also called CamelCase), except class names. Variables, functions, methods, etc. are named_in_lower_case_(with_underscores).
Also don’t put spaces/whitespace between function descriptions and their parentheses!! That is super cringe.

Here is the file:

Python for Recursive Aggregation 2.gh (30.9 KB)

2 Likes

Just to avoid confusion, duck typing isn’t related to case, and is actually quite central to Python :slight_smile:

1 Like

Haha yes back at it! I really appreciate your pointers always :smiley:

In CPython yes. In IronPython I wouldn’t do that. If your underlying framework and your favorite libraries are using CamelCase, i would stick to that. The most important rule is consistency. And nothing is more odd than switching naming conventions all over the place!

2 Likes

Oh, my bad! Thanks for clearing that up. Although I did mention that class names were duck typed! :wink:

Duck Typing has nothing to do with naming conventions. Its the ability to call a class method/function without to care what the object actually is. Therefore the only constraint is that the naming has to be very precise!

A class should be called Duck (or duck or DuckBird or Duck_Bird, it doesn’t matter), if it does things like a duck. The name just means we suspect that a duck can fly, and therefore likely has a fly (or Fly) method. We don’t need to do a type-check/type-cast nor we need to implement an interface or abstraction (like you would do in C# ) to just invoke this fly method. If its there, it executes, if not it raises an exception.

If duck typing is an advantage is very subjective and depends on the complexity. For scripting purposes I would say its rather an advantage. For an medium-to-large sized app, its definitly not. But this is my oppinion.

1 Like

“If it walks like a duck and it quacks like a duck, then it must be a duck”

Let’s do some duck typing in Python to make sure we understand it ! Let’s add the ability to iterate over a line object in Rhino without inheriting from an iterable. Normally if we try to iterate over a LineCurve we get this error:

Message: iteration over non-sequence of type LineCurve

But if we add the __getitem__ method to it then we can iterate ! This could be useful, for instance to create an object representing a route from point A to point B :

from Rhino.Geometry import LineCurve, Point3d

class Route(LineCurve):
    """A route from point A to point B"""
    def __getitem__(self, position):
        if position == 0:
            return self.PointAtStart
        elif position == 1:
            return self.PointAtEnd
        else:
            raise StopIteration

start = Point3d(0,0,0)
end = Point3d(10,0,0)
my_route = Route(start, end)

for point in my_route:
    print point

This works !!! Giving the following output :

0,0,0
10,0,0

Note: don’t forget the StopIteration or you will end up, as I did, in an infinite loop :open_mouth:

1 Like

I think comparing languages makes it clear

# Python
do_something(duck):  # if argument passed in is of type duck it executes, if not it raises
   duck.fly()
// C#
DoSomething(object duck) // argument can be anything
{
   duck.Fly(); // does not compile. We cannot do it like this
}

// -> instead
DoSomething(Duck duck)  // We restrict the type to only work with Duck objects
{ 
    duck.Fly(); // works
}

DoSomething(IFlyable flyable)  // Or we make it more dynamic by using an interface.
{ 
    flyable.Fly(); // works
}
// -> or to keep it dynamic
DoSomething(object duck)
{
    if (duck is Duck)
    {
         ((Duck)duck).Fly(); // we cast and than run the Fly method
    }
}
1 Like