Example of using a Class in RhinoPython

Hi All,

I’ve been wanting to post an example of using classes in RhinoPython for a long time.
Inspired by the recent contamination simulations I found a good example of using a class.

At least I hope so, as understanding classes takes some time and maybe these examples are already too complex. Let me know if you have any questions or add comments to help others.

The first script is a simple random population “walking around”
contamination_simulation_01.py (4.3 KB)
image

Building on that first script I made a random person contaminated and let all walk around.
If someone comes too close to a contaminated person the contamination extends to them.
contamination_simulation_02.py (5.6 KB)
image

In the last version I added avoidance: everyone that is contaminated tries to avoid proximity to others by adjusting their course.
contamination_simulation_03.py (7.2 KB)
image

-Willem

Below the full code of the last version (contamination_simulation_03.py)

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino
import random

import System
# script by willemderks march 2020
# the script is an attempt to visualize the spread of a virus in a community
# based on the behaviour of the individuals in that community
# goal is to give an example of how classes can be used in RhinoPython



class Person(object):
    """
     this defines a class object that represents a person inside the simulation
     
     this object will have spatial properties
        a position  (.point)
        a direction (.vector)
        a speed     (.speed)
     
     this object will have virus related properties
        contaminated    True/False
        symptomatic     True/False
        immune          True/False
        
        
     there are additional properties that influence the behaviour
        carelessness  0.0 ~ 1.0
        
    """
    
    #these are some class global variables
    #changing these will affect all class instances
    
    
    color_dict = {}  #dictionary with colors for states
    color_dict['healty']       =  System.Drawing.Color.FromArgb(80,120,80)
    color_dict['contaminated'] =  System.Drawing.Color.FromArgb(140,50,50)
    color_dict['immune']       =  System.Drawing.Color.FromArgb(190,220,190)
    
    
    infection_distance = 10  #distance for contamination
    
    infection_duration = 150 #frames it takes to fight infection and become immune
    
        
    def __init__(self, point, vector, speed):
        #this method is called when creating the instance
        #the self variable represents the instance itself
        self.point  = point
        self.vector = vector
        self.speed  = speed
        
        self.state = 'healty'  #by default all persons are healty
        
        self.prepared_point = None
        self.color = self.color_dict[self.state]
        
        self.others = []
        
        self.contaminated_frames = 0
    
    
    
    
    
    def add_others(self, others):
        #add others exclusing self
        self.others = [other for other in others if not other == self]
    
    def distance_to(self,other):
        #calculate the distance to another person
        return self.point.DistanceTo(other.point)
    
    def future_position(self):
        return  self.point + (self.vector*self.speed)
    
    def do_contamination(self):
        #check if others in proximity and pass contamination
        if self.state == 'contaminated' :
            for other in self.others:
                if other.state == 'healty':
                    if self.distance_to(other) <= self.infection_distance:
                        other.state = 'contaminated'
    
    def adjust_course(self):
        #predict the position of others for the next frame
        #if they are contaminated and too close adjust direction
        
        if self.state != 'contaminated': return
        
        my_future_position = self.future_position()
        
        avoidance_vectors = []
        
        for other in self.others:
            if other.state == 'contaminated': continue
            #test if the next frame I'm too close
            other_future_position = other.future_position()
            other_future_distance = my_future_position.DistanceTo(other_future_position)
            if other_future_distance  <= self.infection_distance:
                #create vector away from future position
                away_vec = Rhino.Geometry.Vector3d(my_future_position-other_future_position)
                away_vec.Unitize()
                #set the length of the vector little over the safe distance
                away_vec = away_vec * 1.1 * (self.infection_distance*other_future_distance)
                avoidance_vectors.append(away_vec)
            
        if not avoidance_vectors : return
        
        #get the mean vector of all avoidances
        all_vec = Rhino.Geometry.Vector3d(0,0,0)
        for vec in avoidance_vectors: all_vec =  all_vec + vec
        mean_vec = all_vec/len(avoidance_vectors)
        
        mean_vec.Unitize()
        self.vector = mean_vec
    
    def prepare_frame(self):
        
        #if infected, contaminate all that are close
        self.do_contamination()
        
        if self.state == 'contaminated':
            self.contaminated_frames += 1
        if self.contaminated_frames > self.infection_duration:
            self.state = 'immune'
        
        
        self.adjust_course()
        
        self.prepared_point  = self.future_position()
        
        #simple bounce at limits when out of bounds
        self.vector = do_bounce_vector(self.prepared_point, self.vector)
        
    def set_frame(self):
        self.point = Rhino.Geometry.Point3d(self.prepared_point)
        self.color = self.color_dict[self.state]

# utilities
def get_random_point():
    x = -100 + random.random()*200
    y = -100 + random.random()*200
    return Rhino.Geometry.Point3d(x,y,0)
    
def get_random_vector():
    x = (-0.5+random.random())
    y = (-0.5+random.random())
    vector = Rhino.Geometry.Vector3d(x,y,0)
    vector.Unitize()
    return vector
    
def get_random_speed():
    base_speed = 2
    speed_factor = 5
    random_speed = random.random()
    return (base_speed + random_speed)/speed_factor

def do_bounce_vector(point,vector):
    
    if not -100 < point.X < 100 :
        vector.X = -vector.X
    if not -100 < point.Y < 100 :
        vector.Y = -vector.Y
        
    return vector




#runner
def do_run():
    
    rs.EnableRedraw(False)
    
    
    people = []
    total = 100
    for i in range(total):
        pt  = get_random_point()
        vec = get_random_vector()
        spd = get_random_speed()
        new_person = Person(pt,vec,spd)
        people.append(new_person)
        
    
    # we add the population to each individual
    # this allows for each individual to 'see' the others
    for person in people:
        person.add_others(people)
        
    
    # we now have 100 random people
    # we infect 1 of them
    people[0].state = 'contaminated'
    
    #lets make them move for 300 frames
    
    
    #a colorized pointcloud to visualize them
    point_cloud = Rhino.Geometry.PointCloud()
    _ = [point_cloud.Add(person.point,person.color) for person in people]
    cloud_id = sc.doc.Objects.AddPointCloud(point_cloud)
    
    frames = 1500
    for i in range(frames):
        #use a generator to let all people caluculate their next frame  
        _ = [person.prepare_frame() for person in people]
        
        #use a generator to set the prepared state as the new state
        _ = [person.set_frame() for person in people]
        
        #use a generator to fill the new pointcloud
        point_cloud = Rhino.Geometry.PointCloud()
        _ = [point_cloud.Add(person.point,person.color) for person in people]
        
        #replace the old pountcloud geometry with the new one
        sc.doc.Objects.Replace(cloud_id, point_cloud)
        
        #update the viewports
        rs.Redraw()
    
    
    rs.DeleteObject(cloud_id)
    
    rs.EnableRedraw(True)

if( __name__ == '__main__' ):
    
    do_run()
2 Likes

Thanks for sharing @Willem,

I have a problem stopping this though. Maybe you should add an escape button detection instead of waiting for the completion of the loop.

Indeed, because the last versions have more frames and are more computational intensive they tend to be slower and take annoyingly long.

Adjust the frames = 1500 at the bottom of the script to have less frames

-Willem

1 Like