I am trying to animate a particle and a vector that is attached to its center as a random motion is applied to the particle. The particle behaves as intended, but the vector always has a offset from the particle. I tried to set the random seed of each scene, but it didn't work.
from manim import *
import numpy as np
class Particles(ThreeDScene):
def construct(self):
self.camera.background_color = WHITE
coordinate = np.array([ (0,0,0) ])
angle = 90
radius = 1.25
#Polar coordinates
def polar2cart(theta_degrees, rho):
theta = theta_degrees*(np.pi/180)
x = rho * np.cos(theta)
y = rho * np.sin(theta)
return(x, y, 0)
start_arrow = np.array(coordinate) - np.array( [polar2cart(angle,radius )] )
end_arrow = np.array(coordinate) + np.array( [polar2cart(angle,radius )] )
arrow = Arrow( start= start_arrow, end = end_arrow, color = '#ff0000')
dot = Dot(point=coordinate, radius=0.2, color = '#000000')
#Display
self.add(arrow, dot)
##############################################################################################################################
#Random motion
valor = ValueTracker(0)
dot.add_updater(lambda m: m.move_to( m.get_center() + (np.random.normal(0,0.05),np.random.normal(0,0.05),np.random.normal(0,0.05)) ) )
arrow.add_updater(lambda m: m.move_to( dot.get_center() + (np.random.normal(0,0.05),np.random.normal(0,0.05),np.random.normal(0,0.05)) ) )
self.play(valor.animate.set_value(10),rate_func=smooth, run_time=5)
self.wait()
Particle and vector
How can I make that the vector and the particle remain fixed in all frames?
Here is some things that you need to know:
The order in which updaters attached to mobjects are executed is the order in which their mobjects have been added to the scene. In your code, the updater attached to arrow runs before the updater attached to dot (which will make the arrow always lag behind).
Even if you fix the seed, subsequent calls to np.random.norm will not yield the same numbers (fortunately, otherwise your dot would move in a straight line out of the scene). If you want to fix the arrow to the center of the dot, then there is no need to add the random offset to that as well.
Here are possible solutions:
Add the objects in the other way around, self.add(dot, arrow), and remove the random offset from the updater attached to the arrow, arrow.add_updater(lambda m: m.move_to(dot.get_center())). If the order of mobjects on the screen is important to you, you can also run dot.set_z_index(1).
Alternatively, you could also simply create a group of your two objects and attach an updater to that; particle_group = VGroup(arrow, dot) followed by particle_group.add_updater(lambda m: m.shift(...)).
I'd personally go for the group, but it really depends on what else you intend to do in this scene.
Related
I am working on a project related to charge distribution on the sphere and I decided to simulate the problem using vpython and Coulomb's law. I ran into an issue when I created a sphere because I am trying to evenly place out like 1000 points (charges) on the sphere and I can't seem to succeed, I have tried several ways but can't seem to make the points be on the sphere.
I defined an arbitrary value SOYDNR as a number to divide the diameter of the sphere into smaller segments. This would allow me to create a smaller rings of charges and fill out the surface of the spahre with charges. Then I make a list with 4 values that represent different parts of the radius to create the charge rings on the surface. Then I run into a problem and I am not sure how to deal with it. I have tried calculating the radius at those specific heights but yet I couldn't implement it. This is how it looks visually:![Sphere with charges on the surface].(https://i.stack.imgur.com/3N4x6.png) If anyone has any suggestions, I would be greatful, thanks!
SOYDNR = 10 #can be changed
SOYD = 2*radi/SOYDNR # strips of Y direction; initial height + SOYD until = 2*radi
theta = 0
dtheta1 = 2*pi/NCOS
y_list = np.arange(height - radi + SOYD, height, SOYD).tolist()
print(y_list)
for i in y_list:
while Nr<NCOS and theta<2*pi:
position = radi*vector(cos(theta),i*height/radi,sin(theta))
points_on_sphere = points_on_sphere + [sphere(pos=position, radius=radi/50, color=vector(1, 0, 0))]
Nr = Nr + 1
theta = theta + dtheta1
Nr = 0
theta = 0
I found a great way to do it, it creates a bunch of spheres in the area that is described by an if statement this is the code I am using for my simulation that creates the sphere with points on it.
def SOSE (radi, number_of_charges, height):
Charged_Sphere = sphere(pos=vector(0,height,0), radius=radi, color=vector(3.5, 3.5, 3.5), opacity=(0.2))
points_on_sphere = []
NCOS = number_of_charges
theta = 0
dtheta = 2*pi/NCOS
dr = radi/60
direcVector = vector(0, height, 0)
while theta<2*pi:
posvec1 = radi*vector(1-radi*random(),1-radi*random()/radi,1-radi*random())
posvec2 = radi*vector(1-radi*random(),-1+radi*random()/radi,1-radi*random())
if mag(posvec1)<radi and mag(posvec1)>(radi-dr):
posvec1 = posvec1+direcVector
points_on_sphere=points_on_sphere+[sphere(pos=posvec1,radius=radi/60,color=vector(1, 0, 0))]
theta=theta + dtheta
if mag(posvec2)<radi and mag(posvec2)>(radi-dr):
posvec2 = posvec2+direcVector
points_on_sphere=points_on_sphere+[sphere(pos=posvec2,radius=radi/60,color=vector(1, 0, 0))]
theta=theta + dtheta
This code can be edited to add more points and I have two if statements because I want to change the height at which the sphere is present, and if I have just one statement I only see half of the sphere. :)
I am writing a program in Python using graphics.py library. I want to draw two circles, and then in loop move one of them around another one. I know I have to use sin and cos function, but I have no idea what is a mathematical formula for that.
That's my code:
from graphics import *
from math import sin, cos, pi
from time import sleep
win = GraphWin('Program', 500, 500)
win.setBackground('white')
c = Circle(Point(250, 250), 50)
c.draw(win)
c1 = Circle(Point(250, 175), 25)
c1.draw(win)
while True:
c1.move() #there I have to use some formula for moving circle c1 around circle c
sleep(1)
win.getMouse()
win.close()
A bit of mathematics have to be used. Based on your example, you want the circles to be adjacent to each other.
Because of that, distance between their centres will always be r1+r2. This is a length of our vector. We need to split that vector into x axis and y axis parts. This is where sine and cosine functions come in, value it of it at a given angle will mean how far along that axis we have to move our center.
After calculating new position all we have to do is subtract that from current position to see how far we need to move our other circle.
from graphics import *
from math import sin, cos, pi
from time import sleep
win = GraphWin('Program', 500, 500)
win.setBackground('white')
c_origin_x = 250
c_origin_y = 250
c_radius = 50
c = Circle(Point(c_origin_x, c_origin_y), c_radius)
c.draw(win)
c1_oldpos_x = c_origin_x # doesn't actually matter, it gets moved immediately
c1_oldpos_y = c_origin_y
c1_radius = 25
c1 = Circle(Point(c1_oldpos_x, c1_oldpos_y), c1_radius)
c1.draw(win)
angle = 0 # initial angle
while True:
c1_newpos_x = sin(angle) * (c_radius + c1_radius) + c_origin_x
c1_newpos_y = cos(angle) * (c_radius + c1_radius) + c_origin_y
c1.move(c1_newpos_x - c1_oldpos_x, c1_newpos_y - c1_oldpos_y)
sleep(1)
angle += 0.1 * pi # this is what will make a position different in each iteration
c1_oldpos_x = c1_newpos_x
c1_oldpos_y = c1_newpos_y
win.getMouse()
win.close()
I'm trying to move the mouse in a bezier curve motion in Pyautogui to simulate more of a human movement as seen here:
There is some tweening / easing functions within pyautogui but none of which represent a bezier curve type movement. I created a small script to calculate the random places it will hit before ultimately hitting its destination.
Default "Robot" linear path:
Unfortunately, which each destination the mouse temporarily stops.
import pyautogui
import time
import random
print "Randomized Mouse Started."
destx = 444;
desty = 631;
x, y = pyautogui.position() # Current Position
moves = random.randint(2,4)
pixelsx = destx-x
pixelsy = desty-y
if moves >= 4:
moves = random.randint(2,4)
avgpixelsx = pixelsx/moves
avgpixelsy = pixelsy/moves
print "Pixels to be moved X: ", pixelsx," Y: ",pixelsy, "Number of mouse movements: ", moves, "Avg Move X: ", avgpixelsx, " Y: ", avgpixelsy
while moves > 0:
offsetx = (avgpixelsx+random.randint(-8, random.randint(5,10)));
offsety = (avgpixelsy+random.randint(-8, random.randint(5,10)));
print x + offsetx, y + offsety, moves
pyautogui.moveTo(x + offsetx, y + offsety, duration=0.2)
moves = moves-1
avgpixelsx = pixelsx / moves
avgpixelsy = pixelsy / moves
Info:
Windows 10
Python 2.7
Willing to use other libraries, Python version if necessary
I've seen this post: python random mouse movements
but can't figure out how to define a "start and stop" position. The answer is pretty close to what I'm looking for.
Any ideas on how to accomplish this?
Using scipy, numpy and anything that can simply move mouse cursor:
import pyautogui
import random
import numpy as np
import time
from scipy import interpolate
import math
def point_dist(x1,y1,x2,y2):
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
cp = random.randint(3, 5) # Number of control points. Must be at least 2.
x1, y1 = pyautogui.position() # Starting position
# Distribute control points between start and destination evenly.
x = np.linspace(x1, x2, num=cp, dtype='int')
y = np.linspace(y1, y2, num=cp, dtype='int')
# Randomise inner points a bit (+-RND at most).
RND = 10
xr = [random.randint(-RND, RND) for k in range(cp)]
yr = [random.randint(-RND, RND) for k in range(cp)]
xr[0] = yr[0] = xr[-1] = yr[-1] = 0
x += xr
y += yr
# Approximate using Bezier spline.
degree = 3 if cp > 3 else cp - 1 # Degree of b-spline. 3 is recommended.
# Must be less than number of control points.
tck, u = interpolate.splprep([x, y], k=degree)
# Move upto a certain number of points
u = np.linspace(0, 1, num=2+int(point_dist(x1,y1,x2,y2)/50.0))
points = interpolate.splev(u, tck)
# Move mouse.
duration = 0.1
timeout = duration / len(points[0])
point_list=zip(*(i.astype(int) for i in points))
for point in point_list:
pyautogui.moveTo(*point)
time.sleep(timeout)
And you can remove any built-in delay in pyautogui by setting:
# Any duration less than this is rounded to 0.0 to instantly move the mouse.
pyautogui.MINIMUM_DURATION = 0 # Default: 0.1
# Minimal number of seconds to sleep between mouse moves.
pyautogui.MINIMUM_SLEEP = 0 # Default: 0.05
# The number of seconds to pause after EVERY public function call.
pyautogui.PAUSE = 0 # Default: 0.1
P.S.: Example above doesn't require any of those settings as it doesnt use public moveTo method.
For a simple solution, you can try using numpy with the bezier library:
import pyautogui
import bezier
import numpy as np
# Disable pyautogui pauses (from DJV's answer)
pyautogui.MINIMUM_DURATION = 0
pyautogui.MINIMUM_SLEEP = 0
pyautogui.PAUSE = 0
# We'll wait 5 seconds to prepare the starting position
start_delay = 5
print("Drawing curve from mouse in {} seconds.".format(start_delay))
pyautogui.sleep(start_delay)
# For this example we'll use four control points, including start and end coordinates
start = pyautogui.position()
end = start[0]+600, start[1]+200
# Two intermediate control points that may be adjusted to modify the curve.
control1 = start[0]+125, start[1]+100
control2 = start[0]+375, start[1]+50
# Format points to use with bezier
control_points = np.array([start, control1, control2, end])
points = np.array([control_points[:,0], control_points[:,1]]) # Split x and y coordinates
# You can set the degree of the curve here, should be less than # of control points
degree = 3
# Create the bezier curve
curve = bezier.Curve(points, degree)
# You can also create it with using Curve.from_nodes(), which sets degree to len(control_points)-1
# curve = bezier.Curve.from_nodes(points)
curve_steps = 50 # How many points the curve should be split into. Each is a separate pyautogui.moveTo() execution
delay = 1/curve_steps # Time between movements. 1/curve_steps = 1 second for entire curve
# Move the mouse
for i in range(1, curve_steps+1):
# The evaluate method takes a float from [0.0, 1.0] and returns the coordinates at that point in the curve
# Another way of thinking about it is that i/steps gets the coordinates at (100*i/steps) percent into the curve
x, y = curve.evaluate(i/curve_steps)
pyautogui.moveTo(x, y) # Move to point in curve
pyautogui.sleep(delay) # Wait delay
I came up with this trying to write something to draw SVG Paths with the mouse. Running the above code will make your mouse move along the same path as below. The red dots are positioned at each of the control points that define the curve.
Note that you'll have to add pyautogui.mouseDown() before and pyautogui.mouseUp() after the loop at the end of the script if you want to click and drag like I did here in GIMP:
You can check out the bezier docs here: https://bezier.readthedocs.io/en/stable/index.html
you just need know is the move_mouse((300,300))will let you mouse arrive (300,300),then never change.look at the implement,it just call the WIN32 api mouse_event.read something about it,you will find there are no "start and stop" position.i don't know how to draw bezier curve.
while True:
pos = (random.randrange(*x_bound),random.randrange(*y_bound))
move_mouse(pos)
time.sleep(1.0/steps_per_second)
look,that is the secret of animation.all you need do is write a pos = draw_bezier_curve(t)
I'm attempting to animate the Earth rotating using Mayavi mlab. I've succeeded in the past by just rotating the camera around a BuiltinSurface representation of the Earth, but this becomes inconvenient when I need to plot many other objects (spacecraft, stars, etc) in the frame as well. The code below seems to "almost" work: on my Windows 10 machine, it runs for 8 iterations and then the animation freezes. How can I fix this code, or is there a better way to animate a BuiltinSurface in general?
import numpy as np
from mayavi import mlab
from mayavi.sources.builtin_surface import BuiltinSurface
from mayavi.modules.surface import Surface
from mayavi.filters.transform_data import TransformData
def rotMat3D(axis, angle, tol=1e-12):
"""Return the rotation matrix for 3D rotation by angle `angle` degrees about an
arbitrary axis `axis`.
"""
t = np.radians(angle)
x, y, z = axis
R = (np.cos(t))*np.eye(3) +\
(1-np.cos(t))*np.matrix(((x**2,x*y,x*z),(x*y,y**2,y*z),(z*x,z*y,z**2))) + \
np.sin(t)*np.matrix(((0,-z,y),(z,0,-x),(-y,x,0)))
R[np.abs(R)<tol]=0.0
return R
#mlab.show
#mlab.animate(delay=200)
def anim():
fig = mlab.figure()
engine = mlab.get_engine()
# Add a cylinder builtin source
cylinder_src = BuiltinSurface()
engine.add_source(cylinder_src)
cylinder_src.source = 'earth'
# Add transformation filter to rotate cylinder about an axis
transform_data_filter = TransformData()
engine.add_filter(transform_data_filter, cylinder_src)
Rt = np.eye(4)
Rt[0:3,0:3] = rotMat3D((0,0,1), 0) # in homogeneous coordinates
Rtl = list(Rt.flatten()) # transform the rotation matrix into a list
transform_data_filter.transform.matrix.__setstate__({'elements': Rtl})
transform_data_filter.widget.set_transform(transform_data_filter.transform)
transform_data_filter.filter.update()
transform_data_filter.widget.enabled = False # disable the rotation control further.
# Add surface module to the cylinder source
cyl_surface = Surface()
engine.add_filter(cyl_surface, transform_data_filter)
#add color property
#cyl_surface.actor.property.color = (1.0, 0.0, 0.0)
ind=1
while ind<90:
print ind
Rt[0:3,0:3] = rotMat3D((0,0,1), ind) # in homogeneous coordinates
Rtl = list(Rt.flatten()) # transform the rotation matrix into a list
transform_data_filter.transform.matrix.__setstate__({'elements': Rtl})
transform_data_filter.widget.set_transform(transform_data_filter.transform)
transform_data_filter.filter.update()
transform_data_filter.widget.enabled = False # disable the rotation control further.
# Add surface module to the cylinder source
cyl_surface = Surface()
engine.add_filter(cyl_surface, transform_data_filter)
# add color property
#cyl_surface.actor.property.color = (1.0, 0.0, 0.0)
yield
ind+=1
anim()
I haven't been able to figure out a way to use Mayavi to make this happen. However, Vpython appears to be much better suited to accomplish this task. I've posted an example section of code below to make a revolving Earth, along with a few other features.
from visual import *
def destroy():
for obj in scene.objects:
obj.visible = False
del obj
R = 6378. # radius of sphere
angle=0.
scene.range = 10000.
SunDirection=vector(.77,.77,0)
# scene.fov = 0.5
scene.center = (0,0,0)
scene.forward = (-1,0,-1)
scene.up = (0,0,1)
scene.lights=[distant_light(direction=SunDirection, color=color.gray(0.8)),
distant_light(direction=-SunDirection, color=color.gray(0.3))]
x=0
y=0
while True:
rate(10)
angle=angle+1.*pi/180.
destroy()
s = sphere(pos=(x,y,0), radius=R, material=materials.BlueMarble)
s.rotate(angle=90.*pi/180.,axis=(1,0,0)) # Always include this to rotate Earth into correct ECI x y z frame
s.rotate(angle=90.*pi/180.,axis=(0,0,1)) # Always include this to rotate Earth into correct ECI x y z frame
s.rotate(angle=angle, axis=(0,0,1)) # This rotation causes Earth to spin on its axis
xaxis = arrow(pos=(0,0,0), axis=vector(1,0,0)*7000, shaftwidth=100, color=color.red)
yaxis = arrow(pos=(0,0,0), axis=vector(0,1,0)*7000, shaftwidth=100, color=color.green)
zaxis = arrow(pos=(0,0,0), axis=vector(0,0,1)*7000, shaftwidth=100, color=color.blue)
ST = cone(pos=(0,8000,0),axis=(0,700,0),radius=700*tan(10*pi/180),color=color.blue,opacity=1)
I'm running quite a complex code so I won't bother with details as I've had it working before but now im getting this error.
Particle is a 3D tuple filled with 0 or 255, and I am using the scipy centre of mass function and then trying to turn the value into its closest integer (as I'm dealing with arrays). The error is found with on the last line... can anyone explain why this might be??
2nd line fills Particle
3rd line deletes any surrounding particles with a different label (This is in a for loop for all labels)
Particle = []
Particle = big_labelled_stack[x_start+20:x_stop+20,y_start+20:y_stop+20,z_start+20:z_stop+20]
Particle = np.where(Particle == i ,255,0)
CoM = scipy.ndimage.measurements.center_of_mass(Particle)
CoM = [ (int(round(x)) for x in CoM ]
Thanks in advance. If you need more code just ask but I dont think it will help you and its very messy.
################## MORE CODE
border = 30
[labelled_stack,no_of_label] = label(labelled,structure_array,output_type)
# RE-LABEL particles now no. of seeds has been reduced! LAST LABELLING
#Increase size of stack by increasing borders and equal them to 0; to allow us to cut out particles into cube shape which else might lye outside the border
h,w,l = labelled.shape
big_labelled_stack = np.zeros(shape=(h+60,w+60,l+60),dtype=np.uint32)
# Creates an empty border around labelled_stack full of zeros of size border
if (no_of_label > 0): #Small sample may return no particles.. so this stage not neccesary
info = np.zeros(shape=(no_of_label,19)) #Creates array to store coordinates of particles
for i in np.arange(1,no_of_label,1):
coordinates = find_objects(labelled_stack == i)[0] #Find coordinates of label i.
x_start = int(coordinates[0].start)
x_stop = int(coordinates[0].stop)
y_start = int(coordinates[1].start)
y_stop = int(coordinates[1].stop)
z_start = int(coordinates[2].start)
z_stop = int(coordinates[2].stop)
dx = (x_stop - x_start)
dy = (y_stop - y_start)
dz = (z_stop - z_start)
Particle = np.zeros(shape=(dy,dx,dz),dtype = np.uint16)
Particle = big_labelled_stack[x_start+30:x_start+dx+30,y_start+30:y_start+dy+30,z_start+30:z_start+dz+30]
Particle = np.where(Particle == i ,255,0)
big_labelled_stack[border:h+border,border:w+border,border:l+border] = labelled_stack
big_labelled_stack = np.where(big_labelled_stack == i , 255,0)
CoM_big_stack = scipy.ndimage.measurements.center_of_mass(big_labelled_stack)
C = np.asarray(CoM_big_stack) - border
if dx > dy:
b = dx
else: #Finds the largest of delta_x,y,z and saves as b, so that we create 'Cubic_Particle' of size 2bx2bx2b (cubic box)
b = dy
if dz > b:
b = dz
CoM = scipy.ndimage.measurements.center_of_mass(Particle)
CoM = [ (int(round(x))) for x in CoM ]
Cubic_Particle = np.zeros(shape=(2*b,2*b,2*b))
Cubic_Particle[(b-CoM[0]):(b+dx-CoM[0]),(b-CoM[1]):(b+dy-CoM[1]),(b-CoM[2]):(b+dz-CoM[2])] = Particle
volume = Cubic_Particle.size # Gives volume of the box in voxels
info[i-1,:] = [C[0],C[1],C[2],i,C[0]-b,C[1]-b,C[2]-b,C[0]+b,C[1]+b,C[2]+b,volume,0,0,0,0,0,0,0,0] # Fills an array with label.No., size of box, and co-ords
else:
print('No particles found, try increasing the sample size')
info = []
Ok, so I have a stack full of labelled particles, there are two things I am trying to do, first find the centre of masses of each particle with respect ot the labelled_stack which is what CoM_big_labelled_stack (and C) does. and stores the co-ords in a list (tuple) called info. I am also trying to create a cubic box around the particle, with its centre of mass as the centre (which is relating to the CoM variable), so first I use the find objects function in scipy to find a particle, i then use these coordinates to create a non-cubic box around the particle, and find its centre of mass.I then find the longest dimension of the box and call it b, creating a cubic box of size 2b and filling it with particle in the right position.
Sorry this code is a mess, I am very new to Python