Python 3D动画:更新函数出错

2024-05-23 21:36:35 发布

您现在位置:Python中文网/ 问答频道 /正文

我在用3D模拟喷水器。在previous question中,我被帮助用2D制作动画(谢谢@William Miller)。我试着用同样的逻辑把它实现成3D。在update函数中,我得到以下错误:

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

我在编辑代码时查看了this answer,这就是我使用_offsets3d的原因。我查阅了Matplotlib文档,试图找出为什么要得到tuple对象,但没有成功。我也尝试在给定的轴上使用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()

Tags: inposselfforlennprandomsin
1条回答
网友
1楼 · 发布于 2024-05-23 21:36:35

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

  • 正如错误消息'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滴),如下图所示

我没有调查速度问题。你可以改变的一点是,直接将水滴保存到偏移量3d所需的格式中,但这并不是真正的罪魁祸首。可能更有用的是使用numpy's broadcasting进行计算。还要注意的是,你不断地在你的列表中添加越来越大的下拉列表

demo plot

相关问题 更多 >