Python 3D动画:更新功能出错

问题描述 投票:0回答:1

我正在模拟3D洒水装置。在previous question中,我得到了2D动画效果(谢谢@William Miller)。我尝试使用相同的逻辑并将其实现为3D。在update函数中,出现以下错误:

'tuple'对象不可调用第129行-> self.scat = self.ax.scatter(x,y,z, 'b.')

编辑代码时,我看着this answer,这就是为什么我使用_offsets3d。我浏览了Matplotlib文档并试图弄清楚为什么我得到一个元组对象,但是没有成功。我还尝试在给定的轴上简单地使用scatter,但是内核变得无响应(即使连续5滴,8秒,50帧)。我不确定下一步该怎么做?

#Based on code by William Miller

import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
from math import sin, cos
from mpl_toolkits.mplot3d import Axes3D
import random

#Parameters
rho = 1.225
c = 0.5
v0 = 50
g = 9.81

#Timing
fps = 20
tmax = 1*10
nframes = tmax*fps
time = np.linspace(0,tmax, nframes)
dt = time[1]-time[0]

#Waterdroplets
ndrops = 100

#Positioning
maxs = [0.0, 0.0, 0.0]
rmax = [0.0008, 0.0065] #range of radii of water droplets in m
finx = []               #record landing positions of the droplets to be used for analysis
finy = []

#Droplet sizing
theta = np.radians(np.random.normal(37, 8, 80))
phi = np.radians(np.random.normal(3, 1.07, 80))
radii = np.random.normal(0.004, 0.001, 80)

class drop:

    def __init__(self,pos,vel,r):
        self.pos = pos
        self.vel = vel
        self.r = r

class sprinkler:

    def __init__(self):
        self.fig = plt.figure()
        self.ax = self.fig.add_subplot(111, projection = '3d')
        self.drops = [None]*ndrops    # creates empty list of length ndrops
        self.maxt = 0.0

        theta = np.radians(np.random.normal(37, 8, 100))
        phi = np.radians(np.random.normal(3, 1.07, 100))
        radii = np.random.normal(0.004, 0.001, 100)

        #Find the maximum flight time for each droplet 
        #and the maximum distance the droplets will travel

        for i in range(len(phi)):
            m = [drop([0.0, 0.0,0.1], [v0*cos(theta[i])*cos(phi[i]),
                                       v0*cos(theta[i])*sin(theta[i]),
                                       v0*sin(theta[i])],0.0008),
                 drop([0.0, 0.0,0.1], [v0*cos(theta[i])*cos(phi[i]),
                                       v0*cos(theta[i])*sin(theta[i]),
                                       v0*sin(theta[i])],0.0065)]
            for d in m:
                t = 0.0
                coef = -0.5*c*np.pi*d.r**2*rho
                mass = 4/3*np.pi*d.r**3*1000
                while d.pos[2] > 0:
                    a = np.power(d.vel, 2) * coef * np.sign(d.vel)/mass
                    a[2] -= g
                    d.pos += (d.vel + a * dt) * dt
                    d.vel += a * dt
                    t += dt
                    if d.pos[2] > maxs[2]:
                        maxs[2] = d.pos[2]                    
                    if d.pos[1] > maxs[1]:
                        maxs[1] = d.pos[1]
                    if d.pos[0] > maxs[0]:
                        maxs[0] = d.pos[0]
                    if d.pos[2] < 0.0:
                        if t > self.maxt:
                            self.maxt = t
                        break
        #print('Max time is:',maxt)
        #print('Max positions are:', maxs)


        #Create initial droplets
        for ii in range(ndrops):
            phiang = random.randint(0,len(phi)-1)
            thetang = random.randint(0,len(theta)-1)
            rad = random.randint(0,len(radii)-1)

            self.drops[ii] = drop([0.0, 0.0, 0.1],
                                 [v0*cos(theta[thetang])*cos(phi[phiang]),
                                  v0*cos(theta[thetang])*sin(phi[phiang]),
                                  v0*sin(theta[thetang])],
                                  radii[random.randint(0,len(radii)-1)])
        ani = animation.FuncAnimation(self.fig, self.update, init_func = self.setup,
                                          interval = 200, frames = nframes)
        ani.save('MySprinkler.mp4', fps = 20, extra_args=['-vcodec', 'libx264'])
        plt.show()

    def setup(self):
        self.scat = self.ax.scatter([d.pos[0] for d in self.drops],
                                    [d.pos[1] for d in self.drops],
                                    [d.pos[2] for d in self.drops], 'b.')

        self.ax.set_xlim(-1, 100)
        self.ax.set_ylim(-1, 100)
        self.ax.set_zlim(0, 50)
        self.ax.set_xlabel('X Distance')
        self.ax.set_ylabel('Y Distance')
        self.ax.set_zlabel('Height')

        return self.scat

    def update(self, frame):
        if time[frame] <(tmax-self.maxt*1.1):
            self.create(ndrops)
        self.step()
        for d in self.drops:
            x = d.pos[0]
            y = d.pos[1]
            z = d.pos[2]
            self.scat = self.scat._offsets3d(x,y,z, 'b.')

        return self.scat,

    def create(self, i):
        for l in range(i):
            phiang = random.randint(0,len(phi)-1)
            thetang = random.randint(0,len(theta)-1)
            rad = random.randint(0,len(radii)-1)
            self.drops.append(drop([0.0, 0.0, 0.0],
                                   [v0*cos(theta[thetang])*cos(phi[phiang]),
                                    v0*cos(theta[thetang])*sin(phi[phiang]),
                                    v0*sin(theta[thetang])],
                                   radii[rad]))

    def step(self):
        global finx, finy
        for x in range(len(self.drops)):
            coef = -0.5*c*np.pi*self.drops[x].r**2*rho
            mass = 4/3*np.pi*self.drops[x].r**3*1000
            a = np.power(self.drops[x].vel,2) * coef * np.sign(self.drops[x].vel)/mass
            a[2] = a[2]-g

            self.drops[x].pos += np.array(self.drops[x].vel)*dt +0.5*a*dt**2
            self.drops[x].vel += a*dt
            if self.drops[x].pos[2] < 0.0:
                self.drops[x].pos[2] = 0.0
                self.drops[x].vel = [0.0, 0.0, 0.0]
                finx = np.append(finx, self.drops[x].pos[0])
                finy = np.append(finy, self.drops[x].pos[1])
        return self.drops, finx, finy,

sprinkler()
python matplotlib animation 3d
1个回答
0
投票

更新功能似乎有两个问题:

  • 如错误消息'tuple' object is not callable所示,_offsets3d(x,y,z, 'b.')不是可调用函数。它是一个包含x,y和z值的元组。
  • 这些x,y,z不能为单个值:它们必须位于数组或列表中。因此,您需要同时设置所有液滴。

我将您的更新功能更改为:

    def update(self, frame):
        if time[frame] <(tmax-self.maxt*1.1):
            self.create(ndrops)
        self.step()

        x = np.array([d.pos[0] for d in self.drops])
        y = np.array([d.pos[1] for d in self.drops])
        z = np.array([d.pos[2] for d in self.drops])
        self.scat._offsets3d = (x, y, z)

        return self.scat,

如下所示,给出一个工作动画(我用5滴干了)。

我没有调查速度问题。您可以更改的一件事是,直接将液滴保存为_offsets3d所需的格式,但这并不是真正的罪魁祸首。可能更有用的是使用numpy's broadcasting进行计算。还请注意,您会不断向列表中添加越来越多的水滴。

demo plot

© www.soinside.com 2019 - 2024. All rights reserved.