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)
Related
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 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.
I am currently working on a project about Bezier curves and their properties. I am struggling to understand a concept. I can't seem to understand why you need a scale for the tangent line in a Bezier curve.
This is my code that does the animation.
from tkinter import Tk, Canvas
from graphics_template import *
import math, time
vp_width, vp_height = 1024, 768
w_xmin, w_ymin, w_xmax = -3, -3, 10
w_ymax = w_ymin + (w_xmax - w_xmin)/vp_width * vp_height
B2 = [[0.0, 0.0], # point 0
[7.0, 0.0], # point 1
[1.0, 4.0]] # point 2
V_pos = [] # position
V_vec = [] # velocity vector
animation_done = False
DELTA_TDRAW = 0.02 # 50 fps
def eval_Bezier2(P, t):
# P(t) = (1-t)^2P[0] + 2t(1-t) P[1] + t^2P[2]
res = [0.0, 0.0]
for xy in range(2):
res[xy] = (1-t)*(1-t)*P[0][xy] + 2*t*(1-t)*P[1][xy] + t*t*P[2][xy]
return res
def eval_dBezier2(P, t):
# P'(t) = -2(1-t)P[0] + 2(1-t)P[1]-2tP[1] + 2tP[2]
res = [0.0, 0.0]
for xy in range(2):
res[xy] = -2*(1-t)*P[0][xy] + 2*(1-t)*P[1][xy]-2*t*P[1][xy] + 2*t*P[2][xy]
return res
def draw_Bezier (P, nsteps):
xi = P[0][0]
yi = P[0][1]
t_delta = 1/nsteps
t = t_delta
for ti in range(nsteps):
p = eval_Bezier2(P, t)
draw_line(canvas, xi, yi, p[0], p[1], rgb_col(255, 0 ,0))
draw_small_square(canvas, xi, yi, rgb_col(255, 255, 0))
xi = p[0]
yi = p[1]
t += t_delta
for i in range(len(P)):
draw_small_square(canvas, P[i][0], P[i][1], rgb_col(0, 255, 0))
def do_animation (t):
global animation_done
v_factor = 5 # reparameterization
u = t/v_factor
if (t > v_factor): #animation stops at t = v_factor
animation_done = True
else:
current_pos = eval_Bezier2(B2, u);
V_pos[0] = current_pos[0]
V_pos[1] = current_pos[1]
current_vel = eval_dBezier2(B2, u);
V_vec[0] = current_vel[0]
V_vec[1] = current_vel[1]
def draw_scene ():
draw_grid(canvas)
draw_axis(canvas)
draw_Bezier(B2, 20)
draw_dot(canvas, V_pos[0], V_pos[1], rgb_col(0,255,0))
draw_line(canvas, V_pos[0], V_pos[1], (V_pos[0] + V_vec[0]), (V_pos[1] + V_vec[1]), rgb_col(0,255,0))
def init_scene ():
#no data inits needed
V_pos.append(0.0)
V_pos.append(0.0)
V_vec.append(0.0)
V_vec.append(0.0)
do_animation(0.0)
draw_scene()
window = Tk()
canvas = Canvas(window, width=vp_width, height=vp_height, bg=rgb_col(0,0,0))
canvas.pack()
init_graphics (vp_width, vp_height, w_xmin, w_ymin, w_xmax)
# time.perf_counter() -> float. Return the value (in fractional seconds)
# of a performance counter, i.e. a clock with the highest available resolution
# to measure a short duration. It does include time elapsed during sleep and
# is system-wide. The reference point of the returned value is undefined,
# so that only the difference between the results of consecutive calls is valid.
init_time = time.perf_counter()
prev_draw_time = 0
init_scene ()
while (not animation_done):
draw_dt = time.perf_counter() - init_time - prev_draw_time
if (draw_dt > DELTA_TDRAW): # 50 fps
prev_draw_time += DELTA_TDRAW
do_animation(prev_draw_time)
canvas.delete("all")
draw_scene()
canvas.update()
As you can see, the formulas work and are correct. As you also can see in the do_animation() function, there is a v_factor that makes the t smaller. This is because the animation goes at 50 frames per second. Every t that comes in is 0.02, this is divided by 5 to make the point move accross the curve for 5 seconds before reaching u = 1.
As you may notice, I divided the velocity vector by the v_factor, I don't understand this concept. I know my v_factor is a scale, but I don't understand why I need it. Doesn't the derivative of the bezier curve just output the correct velocity at every point in the curve? I only know that when I remove the v_factor from there, my velocity vector would become too big to fit my screen. As I said, I know it's a scale, but why do I need it? Why don't I need it for the V_pos vector? I tried to understand this concept from this stackoverflow post: How to calculate tangent for cubic bezier curve?, but unfortunately to no succes.
The velocity at any point B(t) is the derivative vector B'(t) so you got that part right, but the derivative (remember not to call it the tangent: tangents are true lines without a start or end point) at a point tells you the velocity "over one unit of time". And inconveniently, an entire Bezier curve only covers a single unit of time (with the t parameter running from 0 to 1)...
So, if we space our t values using some smaller-than-one value, like your curve does by using 20 points (so a t interval of 1/20), we need to scale all those derivatives by 1/(point count) too.
If we look at the unscaled derivatives, they are unwieldy and huge:
But if we scale them by 1/20 so that they represent the velocity vector for the time interval we're actually using, things look really good:
And we see an error between "where the true next point is" and "where the previous point plus its velocity vector says it'll be" based on curvature: the stronger the curvature, the further away from the "true point" a "previous point + velocity over time interval" will be, which we can mitigate by using more points.
If we use only 8 steps in our curve, with the derivatives scaled by 1/8, things don't look all that great:
If we bump that up to 15, with 1/15 scaling, things start to look better:
And while your curve with 20 points looks alright, let's look at what 50 points with 1/50 scaling gets us:
That's preeeetty good.
Other answers are very good, but I would just like to point out that the formula for the scaled derivative is actually the reciprocal with the order of the derivative.
If time interval goes from 0 to 5, the actual velocity is v<sub>5</sub>(t) = v(t) / 5², and the actual acceleration is a<sub>5</sub>(t) = a(t) / 5³. Or more generally, if your interval runs from 0 to x, v(x, t) = v(t) / x² and a(x, t) = a(t) / x³.
So if your goal is to scale the velocity curve, instead of breaking the line into pieces, you could make the time interval longer. Note that for the regular interval of [0,1], you would divide it by 1 in both cases, and it would be the same! This makes some intuitive sense, as the faster you want to complete the curve the faster the dot needs to go.
I don't see you dividing the velocity vector by v_factor. I guess you took that part out?
Anyway, this problem is probably just some elementary calculus. You are evaluating your Bezier curve in terms of the parameter u, and when you calculate the derivatives, you are getting dx/du and dy/du.
It seems that you want to want to show the velocities w.r.t. time, so you need dx/dt and dy/dt. By the chain rule, dx/dt = dx/du * du/dt, and du/dt = 1/v_factor. That's why you need to divide the velocity vector.
I want to create a Brownian motion sim
My particle will start at the (0,0), the origin then I've created NumPy random arrays for the x and y direction for example, x = [-2,1,3] and y = [0,-2,1]. Since the particle starts at the origin (0th point) it will to the next point by -2 in the direction and 0 in the y (the 1st point), and then for the 2nd point, it will 1 unit to the right (+1 in the x) and -2 units to the left(-2 in the y).
My question is how would I make it so each point acts as the new origin kind of like adding vectors. I'm thinking I would need some sort of for loop, but not sure how I would set it up.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation as am
np.random.seed(69420)
N=1000 #Number of steps
# Have the particle at the origin in 2D (x,y) plane
# Each step will have a lenght of 1 unit. Lets call it meters
# Each step is one second
# And this simulation will run for 100 seconds
## Now how to make a particle randomly walk???
t=100 # time interval
dt=t/N # delta time from each step
dx = np.random.randint(-5,5,N,dtype=int)# This would be our detla x for each step
dy = np.random.randint(-5,5,N,dtype=int)
dx_0 = np.zeros(N, dtype=int)
dy_0 = np.zeros(N,dtype=int)
X = dx_0+dx
Y = dy_0+dy
print(X)
xdis = np.cumsum(X) #Total distance travled after N steps
ydis = np.cumsum(Y)
plt.plot(dx_0,dy_0,'ko')
plt.plot(xdis,ydis,'b-')
plt.show()
This is what I have so far. Any help is appreciated.
You need to take N steps, starting from (0,0). Track your steps using a variable prev = [0,0], and make changes to it for each step. Simplified code:
prev = [0,0]
for i in range(N):
new_x = prev[0]+dx[i]
new_y = prev[1]+dy[i]
# do what you need with new_x and new_y
prev = [new_x, new_y]
Since it seems like you wish to graph the entire walk, just sum all these steps?
origin = np.array([0, 0])
x_series = np.cumsum(dx) + origin[0]
# (Similarly for y)
This is only for 1 random walker. If you have multiple random walkers - each of them will have a starting point and separate chain of dx-s.
Here is the module I'm using: http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf
I want to see whether a user's clicks are within a shape or not. I used the in operator, but I know that is incorrect. Below is a chunk of my code:
win = GraphWin("Click Speed", 700, 700)
theTarget = drawTarget(win, random.randrange(0,685), random.randrange(0,685))
while theTarget in win:
click = win.getMouse()
if click in theTarget:
print("Good job")
I left out the code that draws theTarget shape because it is length and unnecessary. It is a moving circle.
I'm using a while loop so it allows me to constantly get the user's clicks.
How do I go about checking whether or not a user's clicks are in the specified Target shape by using the getMouse() command?
I'm going to have to use this in the future for more abstract shapes (not simple circles).
Circle
For the simple case of a circle, you can determine whether the mouse is inside using the distance formula. For example:
# checks whether pt1 is in circ
def inCircle(pt1, circ):
# get the distance between pt1 and circ using the
# distance formula
dx = pt1.getX() - circ.getCenter().getX()
dy = pt1.getY() - circ.getCenter().getY()
dist = math.sqrt(dx*dx + dy*dy)
# check whether the distance is less than the radius
return dist <= circ.getRadius()
def main():
win = GraphWin("Click Speed", 700, 700)
# create a simple circle
circ = Circle(Point(350,350),50)
circ.setFill("red")
circ.draw(win)
while True:
mouse = win.getMouse()
if inCircle(mouse,circ):
print ("Good job")
main()
Oval
For the more advanced example of an ellipse we will need to use a formula found here. Here is the function implemting that:
def inOval(pt1, oval):
# get the radii
rx = abs(oval.getP1().getX() - oval.getP2().getX())/2
ry = abs(oval.getP1().getY() - oval.getP2().getY())/2
# get the center
h = oval.getCenter().getX()
k = oval.getCenter().getY()
# get the point
x = pt1.getX()
y = pt1.getY()
# use the formula
return (x-h)**2/rx**2 + (y-k)**2/ry**2 <= 1
Polygon
For a polygon of abitrary shape we need to reference this. I have converted that to a python equivalent for you. Check the link to see why it works because I am honestly not sure
def inPoly(pt1, poly):
points = poly.getPoints()
nvert = len(points) #the number of vertices in the polygon
#get x and y of pt1
x = pt1.getX()
y = pt1.getY()
# I don't know why this works
# See the link I provided for details
result = False
for i in range(nvert):
# note: points[-1] will give you the last element
# convenient!
j = i - 1
#get x and y of vertex at index i
vix = points[i].getX()
viy = points[i].getY()
#get x and y of vertex at index j
vjx = points[j].getX()
vjy = points[j].getY()
if (viy > y) != (vjy > y) and (x < (vjx - vix) * (y - viy) / (vjy - viy) + vix):
result = not result
return result