Is this what you’re looking for? It works pretty much like Shiffman’s script does.
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.
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.
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.
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.