I have written the below program which is aimed at simulating a weight balancing experiment using knife edge. however, the weight of the box is not affecting the segment as shown when simulated. I am currently new on learning how to code with pymunk. kindly help me out on this
import pyglet
import pymunk
import chipmunk
from pymunk import Vec2d
from pymunk.pyglet_util import DrawOptions
window = pyglet.window.Window(800,600,"Knife Edge Mass Balancing Simulation",resizable=False)# W,H
options=DrawOptions()
space=pymunk.Space()
space.gravity= 0,-100
mass = 1
radius=30
ground_moment = pymunk.moment_for_segment(800,(0,0),(800,0),2)
ground_body = pymunk.Body(body_type=pymunk.Body.STATIC)
ground_shape = pymunk.Segment(ground_body,(0,0),(800,0),20)
ground_body.position=0,100
ground_body.elasticity=0.1
ground_body.friction=0.1
poly_shape=pymunk.Poly(None,((0,0),(100,0),(50,150)))
poly_moment = pymunk.moment_for_poly(500,poly_shape.get_vertices())
poly_body=pymunk.Body(body_type=pymunk.Body.STATIC)
poly_shape.body=poly_body
poly_body.position = 350,100
poly_body.elasticity=0.1
segment_moment = pymunk.moment_for_segment(mass,(0,0),(300,0),2)
segment_body = pymunk.Body(mass,segment_moment)
segment_shape = pymunk.Segment(segment_body,(0,0),(400,0),5)
segment_body.position= 210,250
segment_shape.elasticity = 0.1
segment_shape.friction = 0.1
size = 20
box_mass = 0.0
moment = pymunk.moment_for_box(box_mass, (size, size))
box_body = pymunk.Body(box_mass, moment)
box_body.position = Vec2d(300, 265.5)
box_shape = pymunk.Poly.create_box(box_body, (size, size))
box_shape.friction = 0.8
box_shape.elasticity = 0.1
space.add(ground_body, ground_shape,poly_body,poly_shape,segment_body, segment_shape, box_body, box_shape)
#window.event
def on_draw():
window.clear()
space.debug_draw(options)
def update(dt):
space.step(dt)
if __name__=="__main__":
pyglet.clock.schedule_interval(update,1.0/60)
pyglet.app.run()
The problem is that the the segment shape has its weight in one end, and not in the center. This happens because weight in Pymunk is collected at the position of the Body of the shape(s).
Try to change the segment code to something like this:
segment_moment = pymunk.moment_for_segment(mass,(-150,0),(150,0),2)
segment_body = pymunk.Body(mass,segment_moment)
segment_shape = pymunk.Segment(segment_body,(-200,0),(200,0),5)
segment_body.position= 400,250
segment_shape.elasticity = 0.1
segment_shape.friction = 0.1
Related
I also posted in the pyqtgraph forum here.
My overall goal is to have several clickable regions overlaid on an image, and if the plot boundary of any region is clicked I get a signal with the ID of that region. Something like this:
If I use only one PlotDataItem with nan-separated curves then each boundary sends the same signal. However, using a separate PlotDataItem for each boundary makes the application extremely sluggish.
I ended up subclassing ScatterPlotItem and rewriting the pointsAt function, which does what I want. The problem now is I can't figure out the appropriate way to change the ScatterPlotItem's boundingRect. Am I on the right approach? Is there a better way of doing this?
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
class CustScatter(pg.ScatterPlotItem):
def pointsAt(self, pos: QtCore.QPointF):
"""
The default implementation only checks a square around each spot. However, this is not
precise enough for my needs. It also triggers when clicking *inside* the spot boundary,
which I don't want.
"""
pts = []
for spot in self.points(): # type: pg.SpotItem
symb = QtGui.QPainterPath(spot.symbol())
symb.translate(spot.pos())
stroker = QtGui.QPainterPathStroker()
mousePath = stroker.createStroke(symb)
# Only trigger when clicking a boundary, not the inside of the shape
if mousePath.contains(pos):
pts.append(spot)
return pts[::-1]
"""Make some sample data"""
tri = np.array([[0,2.3,0,1,4,5,0], [0,4,4,8,8,3,0]]).T
tris = []
xyLocs = []
datas = []
for ii in np.arange(0, 16, 5):
curTri = tri + ii
tris.append(curTri)
xyLocs.append(curTri.min(0))
datas.append(ii)
def ptsClicked(item, pts):
print(f'ID {pts[0].data()} Clicked!')
"""Logic for making spot shapes from a list of (x,y) vertices"""
def makeSymbol(verts: np.ndarray):
outSymbol = QtGui.QPainterPath()
symPath = pg.arrayToQPath(*verts.T)
outSymbol.addPath(symPath)
# From pyqtgraph.examples for plotting text
br = outSymbol.boundingRect()
tr = QtGui.QTransform()
tr.translate(-br.x(), -br.y())
outSymbol = tr.map(outSymbol)
return outSymbol
app = pg.mkQApp()
pg.setConfigOption('background', 'w')
symbs = []
for xyLoc, tri in zip(xyLocs, tris):
symbs.append(makeSymbol(tri))
"""Create the scatterplot"""
xyLocs = np.vstack(xyLocs)
tri2 = pg.PlotDataItem()
scat = CustScatter(*xyLocs.T, symbol=symbs, data=datas, connect='finite',
pxMode=False, brush=None, pen=pg.mkPen(width=5), size=1)
scat.sigClicked.connect(ptsClicked)
# Now each 'point' is one of the triangles, hopefully
"""Construct GUI window"""
w = pg.PlotWindow()
w.plotItem.addItem(scat)
plt: pg.PlotItem = w.plotItem
plt.showGrid(True, True, 1)
w.show()
app.exec()
Solved! It turns out unless you specify otherwise, the boundingRect of each symbol in the dataset is assumed to be 1 and that the spot size is the limiting factor. After overriding measureSpotSizes as well my solution works:
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
class CustScatter(pg.ScatterPlotItem):
def pointsAt(self, pos: QtCore.QPointF):
"""
The default implementation only checks a square around each spot. However, this is not
precise enough for my needs. It also triggers when clicking *inside* the spot boundary,
which I don't want.
"""
pts = []
for spot in self.points(): # type: pg.SpotItem
symb = QtGui.QPainterPath(spot.symbol())
symb.translate(spot.pos())
stroker = QtGui.QPainterPathStroker()
mousePath = stroker.createStroke(symb)
# Only trigger when clicking a boundary, not the inside of the shape
if mousePath.contains(pos):
pts.append(spot)
return pts[::-1]
def measureSpotSizes(self, dataSet):
"""
Override the method so that it takes symbol size into account
"""
for rec in dataSet:
## keep track of the maximum spot size and pixel size
symbol, size, pen, brush = self.getSpotOpts(rec)
br = symbol.boundingRect()
size = max(br.width(), br.height())*2
width = 0
pxWidth = 0
if self.opts['pxMode']:
pxWidth = size + pen.widthF()
else:
width = size
if pen.isCosmetic():
pxWidth += pen.widthF()
else:
width += pen.widthF()
self._maxSpotWidth = max(self._maxSpotWidth, width)
self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
self.bounds = [None, None]
"""Make some sample data"""
tri = np.array([[0,2.3,0,1,4,5,0], [0,4,4,8,8,3,0]]).T
tris = []
xyLocs = []
datas = []
for ii in np.arange(0, 16, 5):
curTri = tri + ii
tris.append(curTri)
xyLocs.append(curTri.min(0))
datas.append(ii)
def ptsClicked(item, pts):
print(f'ID {pts[0].data()} Clicked!')
"""Logic for making spot shapes from a list of (x,y) vertices"""
def makeSymbol(verts: np.ndarray):
plotVerts = verts - verts.min(0, keepdims=True)
symPath = pg.arrayToQPath(*plotVerts.T)
return symPath
app = pg.mkQApp()
pg.setConfigOption('background', 'd')
symbs = []
for xyLoc, tri in zip(xyLocs, tris):
symbs.append(makeSymbol(tri))
"""Create the scatterplot"""
xyLocs = np.vstack(xyLocs)
tri2 = pg.PlotDataItem()
scat = CustScatter(*xyLocs.T, symbol=symbs, data=datas, connect='finite',
pxMode=False, brush=None, pen=pg.mkPen(width=5), size=1)
scat.sigClicked.connect(ptsClicked)
# Now each 'point' is one of the triangles, hopefully
"""Construct GUI window"""
w = pg.PlotWindow()
w.plotItem.addItem(scat)
plt: pg.PlotItem = w.plotItem
plt.showGrid(True, True, 1)
w.show()
app.exec()
I am creating a program which opens a world map in a window using Zelle's graphics.py. It has one function which draws dots on the map, and another function which undraws those dots after they are on the screen for 1 second (which are stored in a list after being drawn). I want these functions to work concurrently, but when the addDots() function is called in a thread it won't draw the dot in the window, it just stalls. Here is the module which I run:
import thread
import threading
import time
import random
import sys
sys.path.append('..')
from Display import map
import tester
import datetime
dots = list(())
def deleteDots():
while 1==1:
tF = datetime.datetime.now()
a = 0
for i in range(len(dots)):
tD = tF - dots[i-a][2]
tD = int(str(tD)[5:7])
if tD >= 1:
map.deletePoint(dots[i-a][0],dots[i-a][1])
dots.pop(i-a)
a = a+1
def addDots():
oldResponseCount = tester.getResponseCount()
oldResponseCount = int(str(oldResponseCount))
while 1==1:
print(oldResponseCount)
newResponseCount = tester.getResponseCount()
newResponseCount = int(str(newResponseCount))
print(newResponseCount)
if(newResponseCount != oldResponseCount):
difference = newResponseCount - oldResponseCount
for i in range(difference):
lat = random.randint(-90,90)
long = random.randint(-180,180)
map.drawPoint(lat,long)
tI = datetime.datetime.now()
dots.append([lat,long,tI])
oldResponseCount = newResponseCount
if __name__ == '__main__':
threading.Thread(target=addDots).start()
threading.Thread(target=deleteDots).start()
And here is the map module which draws the map on a graphics window and contains the functions to plot and delete a point:
from graphics import *
import math
import images
size = 0.6
Circles = list(())
win = GraphWin("My Window", 1920*size, 1080*size)
win.setBackground('blue')
images.test(size)
myImage = Image(Point(960*size,540*size), "../Display/temp.gif")
myImage.draw(win)
import time
def drawPoint(lat,long):
x = int(long*5.3+960)*size
y = int(lat*(-5.92)+540)*size
pt = Point(x,y)
cir = Circle(pt,5)
cir.setFill(color_rgb(255,0,0))
Circles.append([cir,x,y])
cir.draw(win)
def deletePoint(lat,long):
x = int(long*5.3+960)*size
y = int(lat*(-5.92)+540)*size
for c in Circles:
if c[1]==x and c[2]==y:
c[0].undraw()
How should I go about doing this?
There are a couple of issues that have to be addressed. First, any graphics.py commands that invoke tkinter (i.e. commands that cause something to be drawn/undrawn) must be issued by the primary (main) thread. So we need the secondary threads to communicate drawing requests to the primary thread.
Second, you have both your secondary threads modifying the Circles and dots lists -- you need to syncronize (lock) access to these lists so that only one thread at a time can modify or iterate them.
Below is my rework of your code as an example. I've eliminated map and tester routines as I'm just putting dots up on a window with one thread and deleting them after they are a second old from another thread:
from threading import Thread, Lock
from queue import Queue # use for thread-safe communications
from random import randint
import time
from graphics import *
def drawPoint(lat, long):
x = int(long * 5.3 + 960)
y = int(lat * -5.92 + 540)
point = Point(x, y)
circle = Circle(point, 5)
circle.setFill(color_rgb(255, 0, 0))
circles_lock.acquire()
circles.append(circle)
circles_lock.release()
actions.put((circle.draw, win))
def deletePoint(lat, long):
global circles
x = int(long * 5.3 + 960)
y = int(lat * -5.92 + 540)
keep_circles = []
circles_lock.acquire()
for circle in circles:
center = circle.getCenter()
if center.getX() == x and center.getY() == y:
actions.put((circle.undraw,))
else:
keep_circles.append(circle)
circles = keep_circles
circles_lock.release()
def deleteDots():
global dots
while True:
keep_dots = []
dots_lock.acquire()
now = time.time()
for dot in dots:
lat, long, then = dot
if now - then >= 1.0:
deletePoint(lat, long)
else:
keep_dots.append(dot)
dots = keep_dots
dots_lock.release()
time.sleep(0.5)
def addDots():
while True:
lat = randint(-90, 90)
long = randint(-180, 180)
drawPoint(lat, long)
dots_lock.acquire()
dots.append((lat, long, time.time()))
dots_lock.release()
time.sleep(0.25)
win = GraphWin("My Window", 1920, 1080)
circles = []
circles_lock = Lock()
dots = []
dots_lock = Lock()
actions = Queue()
Thread(target=addDots, daemon=True).start()
Thread(target=deleteDots, daemon=True).start()
while True:
if not actions.empty():
action, *arguments = actions.get()
action(*arguments)
time.sleep(0.125)
When I run this code I get an error "Error: Property 'pos' must be a vector." Do I have to write another vector somewhere? Because I wrote vector at
grav_force = vector(0,-object.mass*grav_field,0)
This is my whole code
GlowScript 2.7 VPython
from visual import *
display(width = 1300, height = 1000)
projectile = sphere(pos = (-5,0,0),
radius = 0.1,
color = color.red,
make_trail = True)
projectile.speed = 3.2 # Initial speed.
projectile.angle = 75*3.141459/180 # Initial angle, from the +x-axis.
projectile.velocity = vector(projectile.speed*cos(projectile.angle),
projectile.speed*sin(projectile.angle),
0)
projectile.mass = 1.0
grav_field = 1.0
dt = 0.01
time = 0
while (projectile.pos.y >=0):
rate(100)
# Calculate the force.
grav_force = vector(0,-projectile.mass*grav_field,0)
force = grav_force
# Update velocity.
projectile.velocity = projectile.velocity + force/projectile.mass * dt
# Update position.
projectile.pos = projectile.pos + projectile.velocity * dt
# Update time.
time = time + dt
Change
projectile = sphere(pos = (-5,0,0), radius = 0.1,color = color.red, make_trail = True)
to
projectile = sphere(pos = vector(-5,0,0), radius = 0.1, color = color.red, make_trail = True)
See documentation
http://www.glowscript.org/docs/VPythonDocs/sphere.html
also from documentation
How GlowScript VPython and VPython 7 differ from Classic VPython 6
· Vectors must be represented as vector(x,y,z) or vec(x,y,z), not as (x,y,z).
I am fairly new to Processing but I have managed to make a good amount of a GUI in the Python Mode. I wanted to graph some data on a white box. I don't want to use background(0) because that'll make the entire window white. Using a rectangular in the draw() function also did not help as the rectangular kept on refreshing the graph. I am trying to simulate the hold on function as in MATLAB
Here's my pseudo code:
class plotEverything:
def __init__
def plotAxis
def plotGraph
def clearGraph
def setup():
size (800,600)
p1 = plotEverything()
background(0)
def draw():
rect (100,100,200,200)
fill(255)
p1.drawAxis()
p1.plotGraph()
Is there any way I can make that rectangle fixed in the background?
EDIT Added graph class | Ignore indents(Assume they are all properly indented) --
class graphData:
def __init__(self, originX, originY, xUpper, yUpper):
self.originX = originX
self.originY = originY
self.xUpper = xUpper
self.yUpper = yUpper
self.pointX1 = originX
self.pointX2 = xUpper
self.pointY1 = originY
self.pointY2 = yUpper
self.scaleFactorX = 10.0/(xUpper - originX) #Assuming data is between is 0 and 10
self.scaleFactorY = 10.0/(originY - yUpper) #Assuming data is between is 0 and 1
def drawAxis(self):
stroke(255)
strokeWeight(1.5)
line(self.originX, self.originY, self.originX, self.yUpper) #y axis
line(self.originX, self.originY, self.xUpper, self.originY) #x axis
def plotStaticData(self,data2Plot): #X-axis static
ab = zip(data2Plot,data2Plot[1:],data2Plot[2:],data2Plot[3:])[::2]
if ab:
(X1,Y1,X2,Y2) = ab[-1]
print (X1,Y1,X2,Y2)
self.pointX1 = self.originX + ceil((float(X1) - 0.0)/self.scaleFactorX)
self.pointX2 = self.originX + ceil((float(X2) - 0.0)/self.scaleFactorX)
self.pointY1 = self.originY - ceil((float(Y1) - 0.0)/self.scaleFactorY)
self.pointY2 = self.originY - ceil((float(Y2) - 0.0)/self.scaleFactorY)
stroke(255)
strokeWeight(2.0)
line(self.pointX1,self.pointY1,self.pointX2,self.pointY2)
def clearPlot(self):
background(0)
self.drawAxis()
I've tried searching but none of the other questions seem to be like mine. I'm more or less experimenting with perspective projection and rotation in python, and have run into a snag. I'm sure my projection equations are accurate, as well as my rotation equations; however, when I run it, the rotation starts normal, but begins to swirl inwards until the vector is in the same position as the Z axis (the axis I am rotating over).
''' Imports '''
from tkinter import Tk, Canvas, TclError
from threading import Thread
from math import cos, sin, radians, ceil
from time import sleep
''' Points class '''
class pPoint:
def __init__(self, fPoint, wWC, wHC):
self.X = 0
self.Y = 0
self.Z = 0
self.xP = 0
self.yP = 0
self.fPoint = fPoint
self.wWC = wWC
self.wHC = wHC
def pProject(self):
self.xP = (self.fPoint * (self.X + self.wWC)) / (self.fPoint + self.Z)
self.yP = (self.fPoint * (self.Y + self.wHC)) / (self.fPoint + self.Z)
''' Main class '''
class Main:
def __init__(self):
''' Declarations '''
self.wWidth = 640
self.wHeight = 480
self.fPoint = 256
''' Generated declarations '''
self.wWC = self.wWidth / 2
self.wHC = self.wHeight / 2
''' Misc declarations '''
self.gWin = Tk()
self.vPoint = pPoint(self.fPoint, self.wWC, self.wHC)
self.vPoint.X = 50
self.vPoint.Y = 60
self.vPoint.Z = -25
self.vPoint.pProject()
self.ang = 0
def initWindow(self):
self.gWin.minsize(self.wWidth, self.wHeight)
self.gWin.maxsize(self.wWidth, self.wHeight)
''' Create canvas '''
self.gCan = Canvas(self.gWin, width = self.wWidth, height = self.wHeight, background = "black")
self.gCan.pack()
def setAxis(self):
''' Create axis points '''
self.pXax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pXbx = pPoint(self.fPoint, self.wWC, self.wHC)
self.pYax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pYbx = pPoint(self.fPoint, self.wWC, self.wHC)
self.pZax = pPoint(self.fPoint, self.wWC, self.wHC)
self.pZbx = pPoint(self.fPoint, self.wWC, self.wHC)
''' Set axis points '''
self.pXax.X = -(self.wWC)
self.pXax.Y = 0
self.pXax.Z = 1
self.pXbx.X = self.wWC
self.pXbx.Y = 0
self.pXbx.Z = 1
self.pYax.X = 0
self.pYax.Y = -(self.wHC)
self.pYax.Z = 1
self.pYbx.X = 0
self.pYbx.Y = self.wHC
self.pYbx.Z = 1
self.pZax.X = 0
self.pZax.Y = 0
self.pZax.Z = -(self.fPoint) / 2
self.pZbx.X = 0
self.pZbx.Y = 0
self.pZbx.Z = (self.fPoint * self.wWC) - self.fPoint
def projAxis(self):
''' Project the axis '''
self.pXax.pProject()
self.pXbx.pProject()
self.pYax.pProject()
self.pYbx.pProject()
self.pZax.pProject()
self.pZbx.pProject()
def drawAxis(self):
''' Draw the axis '''
self.gCan.create_line(self.pXax.xP, self.pXax.yP, self.pXbx.xP, self.pXbx.yP, fill = "white")
self.gCan.create_line(self.pYax.xP, self.pYax.yP, self.pYbx.xP, self.pYbx.yP, fill = "white")
self.gCan.create_line(self.pZax.xP, self.pZax.yP, self.pZbx.xP, self.pZbx.yP, fill = "white")
def prePaint(self):
self.vA = self.gCan.create_line(self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP, fill = "red")
def paintCanvas(self):
try:
while True:
self.ang += 1
if self.ang >= 361:
self.ang = 0
self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))) - (self.vPoint.Y * sin(radians(self.ang)))
self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))) + (self.vPoint.Y * cos(radians(self.ang)))
self.vPoint.pProject()
self.gCan.coords(self.vA, self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP)
self.gWin.update_idletasks()
self.gWin.update()
sleep(0.1)
except TclError:
pass
mMain = Main()
mMain.initWindow()
mMain.setAxis()
mMain.projAxis()
mMain.drawAxis()
mMain.prePaint()
mMain.paintCanvas()
Thank you for any input :)
EDIT: Sorry, I just realized I forgot to put my question. I just want to know why it is gravitating inward, and not just rotating "normally"?
This section is wrong:
self.ang += 1
if self.ang >= 361:
self.ang = 0
self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))
- self.vPoint.Y * sin(radians(self.ang)))
self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))
+ self.vPoint.Y * cos(radians(self.ang)))
self.vPoint.pProject()
For two reasons:
self.ang will take integers in the open range [0 - 360], which means the angle 360 (== 0) is repeated.
In each iteration, you rotate the point from the previous iteration by the angle. As a result, your first frame is at 1 degree, your second at 1+2 = 3, the third at 1 + 2 + 3... You should either be:
rotating the point from the previous iteration by a constant angle each time (1°). This suffers from the problem mentioned in my comment
rotating the initial point by the current angle of rotation each time
Not actualy related to your problem, but I strongly suggest you to use Numpy to perform geometric transformations, specially if it involves 3D points.
Below, I post a sample snippet, I hope it helps:
import numpy
from math import radians, cos, sin
## suppose you have a Nx3 cloudpoint (it might even be a single row of x,y,z coordinates)
cloudpoint = give_me_a_cloudpoint()
## this will be a rotation around Y azis:
yrot = radians(some_angle_in_degrees)
## let's create a rotation matrix using a numpy array
yrotmatrix = numpy.array([[cos(yrot), 0, -sin(yrot)],
[0, 1, 0],
[sin(yrot), 0, cos(yrot)]], dtype=float)
## apply the rotation via dot multiplication
rotatedcloud = numpy.dot(yrotmatrix, pointcloud.T).T # .T means transposition