3といっても新しいことはしていないのだけど。
前回記事のPythonプログラムは、PySideというGUIモジュールを使っているのだけど、考えてみれば、解析の様子を見せるだけなので、標準GUIの方が良いよなぁっと思って、Tkinterに書き換えました。・・・それだけ、です。
PureなPython(2.7)で動くプログラムになりました。
# -*- coding: utf-8 -*-
import sys
import math
import random
import time
import Tkinter
G = 9.80665 # 重力加速度
rho = 10 # 粒子間密度
elem_num = 0 #粒子数
class Element(object):
def __init__(self):
global elem_num
self.n = elem_num #要素No.
elem_num += 1
self.r = 0 #半径
self.x = 0 #X座標
self.y = 0 #Y座標
self.a = 0 #角度
self.dx = 0 #X方向増加量
self.dy = 0 #Y方向増加量
self.da = 0 #角度増加量
self.vx = 0 #X方向速度
self.vy = 0 #Y方向速度
self.va = 0 #角速度
self.fy = 0
self.fx = 0
self.fm = 0
self.en = [] #弾性力(直方向)
self.es = [] #弾性力(せん断方向)
class Particle(Element):
def __init__(self,x,y,vx=0,vy=0):
super(Particle,self).__init__()
self.type = 1
self.x = x #X座標
self.y = y #Y座標
self.vx = vx #X方向速度
self.vy = vy #Y方向速度
self.r = 5 #半径
self.m = 4.0/3.0*math.pi*rho*self.r**3 # 質量
self.Ir = math.pi*rho*self.r**4/2.0 #慣性モーメント
def config(self):
self.en = [0 for i in range(elem_num)]
self.es = [0 for i in range(elem_num)]
def nextStep(self,dt):
#位置更新(オイラー差分)
ax = self.fx/self.m
ay = self.fy/self.m
aa = self.fm/self.Ir
self.vx += ax*dt
self.vy += ay*dt
self.va += aa*dt
self.dx = self.vx*dt
self.dy = self.vy*dt
self.da = self.va*dt
self.x += self.dx
self.y += self.dy
self.a += self.da
class Line(Element):
def __init__(self,x1,y1,x2,y2):
super(Line,self).__init__()
self.type = 2
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
class Interface():
def set(self,elem1,elem2):
if elem1.type == 1 and elem2.type == 1:
#粒子間
self.kn = 10**6 #弾性係数(法線方向)
self.etan = 10000 #粘性係数(法線方向)
self.ks = 10*6 #弾性係数(せん断方向)
self.etas = 10000 #粘性係数(せん断方向)
self.frc = 10*6 #摩擦係数
elif elem1.type == 1 and elem2.type == 2:
#粒子-Line間
self.kn = 10**7
self.etan = 0
self.ks = 10*6
self.etas = 0
self.frc = 10*6
class DEM():
def __init__(self):
self.step = 0
self.dt = 0.001
self.pars = []
self.lines = []
self.beams = []
self.interface = Interface()
# set particle random
for i in range(10):
p = Particle(random.randint(20,180),random.randint(20,180),
random.randint(-40,40),random.randint(-40,40))
self.pars.append(p)
# set line
l1 = Line(5,5, 295,5)
l2 = Line(5,195,5,5)
l3 = Line(295,195,295,5)
l4 = Line(5,195,295,195)
l5 = Line(10,150,180,140)
l6 = Line(30,30,160,50)
self.lines.append(l1)
self.lines.append(l2)
self.lines.append(l3)
self.lines.append(l4)
self.lines.append(l5)
self.lines.append(l6)
for p in self.pars:
p.config()
return
def calcForce(self):
#2粒子間の接触判定
combs = [(p1, p2) for p1 in self.pars for p2 in self.pars]
for p1,p2 in combs:
if p1.n == p2.n:
continue
lx = p1.x - p2.x
ly = p1.y - p2.y
ld = math.sqrt(lx**2+ly**2)
if (p1.r+p2.r)>ld:
cos_a = lx/ld
sin_a = ly/ld
self.force2par(p1,p2,cos_a,sin_a)
else:
p1.en[p2.n] = 0.0
p1.es[p2.n] = 0.0
#粒子と線の接触判定
combs = [(l, p) for l in self.lines for p in self.pars]
for l,p in combs:
hit = False
th0 = math.atan2(l.y2-l.y1,l.x2-l.x1)
th1 = math.atan2(p.y-l.y1,p.x-l.x1)
a = math.sqrt((p.x-l.x1)**2+(p.y-l.y1)**2)
d = abs(a*math.sin(th1-th0))
if d < p.r:
b = math.sqrt((p.x-l.x2)**2+(p.y-l.y2)**2)
s = math.sqrt((l.x2-l.x1)**2+(l.y2-l.y1)**2)
if a < s and b < s:
s1 = math.sqrt(a**2-d**2)
x = l.x1 + s1*math.cos(th0)
y = l.y1 + s1*math.sin(th0)
hit = True
elif a < b and a < p.r:
x = l.x1
y = l.y1
hit = True
elif b < p.r:
x = l.x2
y = l.y2
hit = True
if hit:
lx = p.x - x
ly = p.y - y
ld = math.sqrt(lx**2+ly**2)
cos_a = lx/ld
sin_a = ly/ld
self.force2par(p,l,cos_a,sin_a)
else:
p.en[l.n] = 0.0
p.es[l.n] = 0.0
#外力
for p in self.pars:
p.fy += -G*p.m #重力
for b in self.beams:
b.fy += -G*b.m
def force2par(self,p1,p2,cos_a,sin_a):
#相対的変位増分
un = +(p1.dx-p2.dx)*cos_a+(p1.dy-p2.dy)*sin_a
us = -(p1.dx-p2.dx)*sin_a+(p1.dy-p2.dy)*cos_a+(p1.r*p1.da+p2.r*p2.da)
#相対的速度増分
vn = +(p1.vx-p2.vx)*cos_a+(p1.vy-p2.vy)*sin_a
vs = -(p1.vx-p2.vx)*sin_a+(p1.vy-p2.vy)*cos_a+(p1.r*p1.va+p2.r*p2.va)
self.interface.set(p1,p2)
#合力(局所座標系)
p1.en[p2.n] += self.interface.kn*un
p1.es[p2.n] += self.interface.ks*us
hn = p1.en[p2.n] + self.interface.etan*vn
hs = p1.es[p2.n] + self.interface.etas*vs
if hn <= 0.0: #法線力がなければ、せん断力は0 hs = 0.0 elif abs(hs) >= self.interface.frc*hn:
#摩擦力以上のせん断力は働かない
hs = self.interface.frc*abs(hn)*hs/abs(hs)
#全体合力(全体座標系)
p1.fx += -hn*cos_a + hs*sin_a
p1.fy += -hn*sin_a - hs*cos_a
p1.fm -= p1.r*hs
def clacStep(self):
for p in self.pars:
p.fx = 0
p.fy = 0
p.fm = 0
self.calcForce()
for p in self.pars:
p.nextStep(self.dt)
self.step += 1
class MainWindow(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.dem = DEM()
self.canvas = Tkinter.Canvas(self, bg="white")
self.canvas.pack(fill=Tkinter.BOTH,expand=True)
self.geometry('300x200')
h = 200
for l in self.dem.lines:
self.canvas.create_line(l.x1,h-l.y1,l.x2,h-l.y2,width=1)
self.redraw()
self.st = time.time()
print('init')
def redraw(self):
self.canvas.delete('elem')
h = 200
for p in self.dem.pars:
x1 = round(p.x-p.r)
y1 = round(h-p.y-p.r)
x2 = round(p.x+p.r)
y2 = round(h-p.y+p.r)
self.canvas.create_oval(x1,y1,x2,y2,tags='elem')
x1 = round(p.x)
y1 = round(h-p.y)
x2 = round(p.x+p.r*math.cos(p.a))
y2 = round(h-p.y+p.r*math.sin(p.a))
self.canvas.create_line(x1,y1,x2,y2,tags='elem')
self.update_idletasks()
self.after(100,self.redraw)
def calcloop(self):
self.dem.clacStep()
if self.dem.step % 1000 == 0:
print(self.dem.step)
#print('time:{0}'.format(time.time()-self.st))
self.st = time.time()
self.after(0,self.calcloop)
def main():
w = MainWindow()
w.after(500,w.calcloop)
w.after(500,w.redraw)
w.mainloop()
if __name__ == '__main__':
main()
※円形要素はランダムな位置に作成されるので、たまに位置がカブってエラーになる時がある
Tkinterにすると一つ困るのが、画像の出力。描いたものを直接PNG等に出来ないので、お手軽に動画にできない。うーむ。
追記:お手軽な解決方法がありました。スクリーンショットを撮るっていう。ピュアPythonではなくなりますが。
from PIL import ImageGrab
class Window(Tkinter.Tk):
def saveImage(self):
filepath = 'c://Temp/dem/capture%05d.png' % self.dem.step
img = ImageGrab.grab()
s,x,y = self.geometry().split('+')
w,h = s.split('x')
w,h,x,y = map(int,[w,h,x,y])
x += 8
y += 30
img = img.crop((x,y,x+w,y+h))
img.save(filepath)
