Nature of code - force, attracto

I want to implement the following:

2.6: Gravitational Attraction - The Nature of Code


333 !
by rhino, grasshopper, python

also I Referenced:

but it didn’t work well…
it have to go round, but it comes out straight.

Here is my script

Please advise…

coding train, python.gh (5.7 KB) mover.3dm (23.5 KB)

asd|452x424

Hi @bjbj7878,

Is this what you’re looking for? It works pretty much like Shiffman’s script does.

2020-04-21 09-22-09.2020-04-21 09_26_27

It also recomputes the GHPython component itself, much like the Update() function works in Processing, instead of saving the frame-by-frame data in a list, which can be quite expensive.

import Rhino.Geometry as rg
import Grasshopper as gh
import scriptcontext as sc


WIDTH = 100 # Global volume width
DEPTH = 100 # Global volume depth
HEIGHT = 100 # Gobal volume height
G = 1 # Global gravitational constant


class Mover():
    def __init__(self, position):
        global WIDTH, DEPTH, HEIGHT
        self.position = position
        self.velocity = rg.Vector3d.Zero
        self.acceleration = rg.Vector3d.Zero
        self.mass = 1
        self.max_speed = 10
        
    def apply_force(self, force):
        #Newton's 2nd Law with mass!
        self.acceleration += force / self.mass

    def update(self):
        #update location, velocity
        self.velocity += self.acceleration
        self.position += self.velocity
        print "Acceleration: ", self.acceleration
        self.acceleration *= 0.0

    def check_edges(self):
        bounce = -1
        if self.position.X >= WIDTH or self.position.X <= 0:
            self.velocity.X = self.velocity.X * bounce
        if self.position.Y >= DEPTH or self.position.Y <= 0:
            self.velocity.Y = self.position.Y * bounce
        if self.position.Z >= HEIGHT or self.position.Z <= 0:
            self.velocity.Z = self.velocity.Z * bounce
            
    def display(self):
        return self.position


class Attractor():
    def __init__(self):
        global WIDTH, DEPTH, HEIGHT, G
        self.position = rg.Vector3d(WIDTH / 2, DEPTH / 2, HEIGHT / 2)
        self.mass = 20 # Mass, tied to size
        
    def attract(self, mover):
        # Calculate the direction of force vector
        force = self.position - mover.position
        # Constrain the magnitude of the force vector to eliminate 
        # "extreme" results for very close or very far objects
        force = vector_constrain(force, 5.0, 25.0)
        dist = force.Length # constrained distance/magnitude
        # Normalize the force vector (distance doesn't matter here, we just
        # want this vector for direction)
        force.Unitize()
        # Calculate the gravitational force magnitude 
        strength = (G * self.mass * mover.mass) / dist**2
        # Get the force vector (magnitude x direction)
        force *= strength
        return force
        
    def display(self):
        return rg.Sphere(rg.Point3d(self.position), self.mass / 2)


################################################################################


def vector_constrain(vec, min_mag=0, max_mag=20):
    """Returns a vector whose magnitude is constrained 
        inbetween a minimum and maximum value."""
    if (vec.Length <= max_mag and vec.Length >= min_mag):
        return vec
    if (vec.Length > max):
        mag = max_mag
    if (vec.Length < min):
        mag = min_mag
    vec.Unitize()
    vec *= mag
    return vec


def update_component():
    """Updates this GH component, similar to using a Grasshopper timer."""
    def call_back(e):
        """Defines a callback action."""
        ghenv.Component.ExpireSolution(False)
    # Get the Grasshopper document
    ghDoc = ghenv.Component.OnPingDocument() 
    # Schedule this component to expire
    ghDoc.ScheduleSolution(1, gh.Kernel.GH_Document.GH_ScheduleDelegate(call_back))


################################################################################


if Reset or "count" not in globals():
    sc.sticky.pop("Simulation", None)
    count = 0
    print count
    ghenv.Component.Message = "Reset"

if Run:
    # Setup the simulation
    if not "Simulation" in sc.sticky.keys():
        # Initialise a new mover and attractor
        mv = Mover(Location)
        a = Attractor()
    else:
        # Get the existing mover and attractor
        mv = sc.sticky["Simulation"]["Mover"]
        a = sc.sticky["Simulation"]["Attractor"]
    
    # Update/draw the simulation
    f = a.attract(mv)
    mv.apply_force(f)
    mv.update()
    count += 1
    
    # Store the current iteration/frame in the sticky dictionary
    sc.sticky["Simulation"] = {"Mover": mv, "Attractor": a}
    
    if count < MaxCount:
        update_component()
        ghenv.Component.Message = "Running..."
    else:
        ghenv.Component.Message = "Converged"
    
    print count
    
    # Display/outputs
    MPos = mv.display()
    AGeo = a.display()

else:
    # Pause the simulation
    if "Simulation" in sc.sticky.keys():
        # Get the existing mover and attractor, and pause
        mv = sc.sticky["Simulation"]["Mover"]
        a = sc.sticky["Simulation"]["Attractor"]
        
        ghenv.Component.Message = "Paused"
        
        print count
        
        # Display/outputs
        MPos = mv.display()
        AGeo = a.display()

I’ve changed the script to RhinoCommon, instead of rhinoscriptsyntax, which is better and closer to Processing, which should make translations easier.

coding train python 2.gh (7.8 KB)

2 Likes

wow thank you so much!

  1. when it is rg.vector, Using ‘+’ makes calculations really easy This part is really amazing

class Mover():
def init(self, position):
global WIDTH, DEPTH, HEIGHT
self.position = position
self.velocity = rg.Vector3d.Zero
self.acceleration = rg.Vector3d.Zero
self.mass = 1
self.max_speed = 10

def apply_force(self, force):
    #Newton's 2nd Law with mass!
    self.acceleration += force / self.mass

def update(self):
    #update location, velocity
    self.velocity += self.acceleration
    self.position += self.velocity
    print "Acceleration: ", self.acceleration
    self.acceleration *= 0.0

you so amazing…

self.velocity = rg.Vector3d.Zero
self.acceleration = rg.Vector3d.Zero
" self.velocity += self.acceleration"

how could it possible??
i want to use this but i didn’t use it before so…
i dont understand very well but
It seems to be very helpful in solving the problem.

  1. Because of my lack of skill, I haven’t fully understood the many skills you’ve done.lol
    by any youtube or something, I want to know where to learn.

def update_component():
“”“Updates this GH component, similar to using a Grasshopper timer.”“”
def call_back(e):
“”“Defines a callback action.”“”
ghenv.Component.ExpireSolution(False)
# Get the Grasshopper document
ghDoc = ghenv.Component.OnPingDocument()
# Schedule this component to expire
ghDoc.ScheduleSolution(1, gh.Kernel.GH_Document.GH_ScheduleDelegate(call_back))

####################################
if Reset or “count” not in globals():
sc.sticky.pop(“Simulation”, None)
count = 0
print count
ghenv.Component.Message = “Reset”

i think i dont know about ‘ghenv’, ‘sc.sticky’ … and so on
I really want to learn how to use it, but I am not sure how to do it.

Thank you again and I will study more by referring. Thank you very much.

rg.Vector3d.Zero simply creates a zero vector. It’s analogues to creating it “manually” with rg.Vector3d(0.0, 0.0, 0.0).

self.velocity += self.acceleration is simply a short way of doing self.velocity = self.velocity + self.acceleration!

I’ve never seen this being mentioned in any YouTube tutorial/video. I’ve learned here from @AndersDeleuran.
The update_component() function is responsible for the recomputation of the GHPython component. It’s basically what enables the animation! Each time the component expires/recomputes corresponds to an animation frame, much like the Update() function in Processing.

globals() is a function from Python that returns the global symbol table. I use it here to check whether count is defined as a global variable.
The sticky dictionary is a dictionary provided by Rhino that exists beyond your current session/document. You can save data in it that would otherwise expire and be lost, for instance when you’re GHPython component refreshes/recomputes. It works just like standard Python dictionaries.

sc.sticky.pop(“Simulation”, None) means that we delete the values/data, corresponding to the key “Simulation” from the sticky dictionary, since we want to reset everything. None simply means that nothing should be returned by pop().

sc.sticky["Simulation"] = {"Mover": mv, "Attractor": a} means that we store the dictionary {"Mover": mv, "Attractor": a} as a value under the key “Simulation” inside the sticky dictionary.

mv = sc.sticky["Simulation"]["Mover"] means that we get the value (i.e. Mover object) that is stored under the key “Mover” from the dictionary that is stored under the key “Simulation” in the sticky dictionary.

ghenv.Component.Message simply displays a string/text under the component.

2 Likes

Thanks a lot once again. Thank you for explaining one by one. I will continue to study ‘nature of code’ in the future, and I will use the content that I have answered and implement it further. It was my first time asking a question like this, thank you very much for your kind answer.

1 Like