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)
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)
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)
-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()