为啥不更新重绘场景?

Posted

技术标签:

【中文标题】为啥不更新重绘场景?【英文标题】:Why doesn't update repaint a scene?为什么不更新重绘场景? 【发布时间】:2020-02-19 01:55:49 【问题描述】:

我正在使用 PyQt5 和 PyOpenGL 在 python 中制作一个行进立方体项目。我试图隐藏穿过屏幕的线框立方体,引用为mainWindow.marchingCube,以在循环后消失。我设法让消失周期发生,但立方体实际上并没有消失。我调用了QOpenGLWidget的更新函数,但立方体仍然没有消失。

import sys
from PyQt5.QtWidgets import (
                             QApplication, QMainWindow, QSlider,
                             QOpenGLWidget, QLabel, QPushButton
                            )
from PyQt5.QtCore import Qt
from OpenGL.GL import (
                       glLoadIdentity, glTranslatef, glRotatef,
                       glClear, glBegin, glEnd,
                       glColor3fv, glVertex3fv,
                       GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT,
                       GL_QUADS, GL_LINES
                      )
from OpenGL.GLU import gluPerspective
from numerics import sin, cos, tan, avg, rnd    #Numerics is my custom math library.
import random, time

class mainWindow(QMainWindow):    #Main class.
    shapes = []    #this will hold instances of the following classes: cube
    dataPoints = []
    zoomLevel = -10
    rotateDegreeV = -90
    rotateDegreeH = 0
    marchActive = False
    limit = -1
    meshPoints = []

    class cube():
        render = True
        solid = False
        color = (1, 1, 1)

        def config(self, x, y, z, size = 0.1, solid = False, color = (1, 1, 1)):
            self.solid = solid
            self.color = color
            self.size = size / 2
            s = self.size
            self.vertices = [
                    (-s + x, s + y, -s + z),
                    (s + x, s + y, -s + z),
                    (s + x, -s + y, -s + z),
                    (-s + x, -s + y, -s + z),
                    (-s + x, s + y, s + z),
                    (s + x, s + y, s + z),
                    (s + x, -s + y, s + z),
                    (-s + x, -s + y, s + z)
                   ]
            self.edges = [
                          (0,1), (0,3), (0,4), (2,1),
                          (2,3), (2,6), (7,3), (7,4),
                          (7,6), (5,1), (5,4), (5,6)
                         ]
            self.facets = [
                           (0, 1, 2, 3), (0, 1, 6, 5),
                           (0, 3, 7, 4), (6, 5, 1, 2),
                           (6, 7, 4, 5), (6, 7, 3, 2)
                          ]
        def show(self):
            self.render = True
        def hide(self):
            self.render = False

    class dataPoint():
        location = (0, 0, 0)
        value = 0
        shape = None
        def place(self, x, y, z):
            self.location = (x, y, z)
        def set(self, val):
            self.value = val
        def setShape(self, shape):
            self.shape = shape

    class meshPoint():
        location = (0, 0, 0)
        shape = None
        def place(self, x, y, z):
            self.location = (x, y, z)
        def setShape(self, shape):
            self.shape = shape

    def keyPressEvent(self, event):    #This is the keypress detector. I use this to determine input to edit grids.
        try:
            key = event.key()
            #print(key)
            if key == 87:
                self.rotateV(5)
            elif key == 65:
                self.rotateH(5)
            elif key == 83:
                self.rotateV(-5)
            elif key == 68:
                self.rotateH(-5)
            elif key == 67:
                self.zoom(1)
            elif key == 88:
                self.zoom(-1)
            elif key == 77:
                self.marchStep()
        except:
            pass

    def __init__(self):
        super(mainWindow, self).__init__()
        self.currentStep = 0
        self.width = 700    #Variables used for the setting of the size of everything
        self.height = 600
        self.setGeometry(0, 0, self.width + 50, self.height)    #Set the window size
        self.initData(3, 3, 3)
    def setupUI(self):
        self.openGLWidget = QOpenGLWidget(self)    #Create the GLWidget
        self.openGLWidget.setGeometry(0, 0, self.width, self.height)
        self.openGLWidget.initializeGL()
        self.openGLWidget.resizeGL(self.width, self.height)    #Resize GL's knowledge of the window to match the physical size?
        self.openGLWidget.paintGL = self.paintGL    #override the default function with my own?

        self.filterSlider = QSlider(Qt.Vertical, self)
        self.filterSlider.setGeometry(self.width + 10, int(self.height / 2) - 100, 30, 200)
        self.filterSlider.valueChanged[int].connect(self.filter)

        self.limitDisplay = QLabel(self)
        self.limitDisplay.setGeometry(self.width, int(self.height / 2) - 130, 50, 30)
        self.limitDisplay.setAlignment(Qt.AlignCenter)
        self.limitDisplay.setText('-1')

        self.marchButton = QPushButton(self)
        self.marchButton.setGeometry(self.width, int(self.height / 2) - 160, 50, 30)
        self.marchButton.setText('March!')
        self.marchButton.clicked.connect(self.marchStep)

    def marchStep(self):
        if not self.marchActive:
            marchAddr = len(self.shapes)
            self.shapes.append(self.cube())
            self.marchingCube = self.shapes[marchAddr]
            self.marchActive = True
            self.currentStep = 0
        if self.currentStep == len(self.marchPoints):
            self.currentStep = 0
            #print('meshPoints: '.format(self.meshPoints))
            for mp in self.meshPoints:
                #print(mp.shape)
                self.shapes.remove(mp.shape)
            self.meshPoints.clear()
            self.marchingCube.hide()
            return

        if self.currentStep == 0:
            self.marchingCube.show()

        p = self.marchPoints[self.currentStep]
        x, y, z = p
        self.marchingCube.config(x, y, z, size = 1)
        points = []
        for i in range(8):
            point = self.getDataPointByLocation(self.marchingCube.vertices[i])
            points.append(point)
        self.openGLWidget.update()
        #print('step:   x:   y:   z: '.format(self.currentStep, x, y, z))
        #for point in points:
        #    print(point.location, end = ' ')
        #print()
        for pair in self.marchingCube.edges:
            pointA = points[pair[0]]
            pointB = points[pair[1]]
            #print('pointA.value:   pointB.value:   limit: '.formatpointA.value, pointB.value, self.limit)
            if (pointA.value < self.limit and pointB.value > self.limit) or (pointA.value > self.limit and pointB.value < self.limit):
                xA, yA, zA = pointA.location
                xB, yB, zB = pointB.location
                valA = (pointA.value + 1) / 2
                valB = (pointB.value + 1) / 2
                xC = float(avg([xA, xB]))
                yC = float(avg([yA, yB]))
                zC = float(avg([zA, zB]))

                mp = self.meshPoint()
                mp.place(xC, yC, zC)
                mp.setShape(self.cube())
                mp.shape.config(xC, yC, zC, size = 0.05, solid = True, color = (1, 0, 0))
                self.shapes.append(mp.shape)
                self.meshPoints.append(mp)
        self.currentStep += 1
        self.openGLWidget.update()

    def zoom(self, value):
        self.zoomLevel += value
        self.openGLWidget.update()

    def rotateV(self, value):
        self.rotateDegreeV += value
        self.openGLWidget.update()

    def rotateH(self, value):
        self.rotateDegreeH += value
        self.openGLWidget.update()

    def filter(self, value):
        self.limit = rnd((value / 49.5) -1, -2)
        for d in self.dataPoints:
            if d.value < self.limit:
                d.shape.hide()
            else:
                d.shape.show()
        self.limitDisplay.setText(str(self.limit))
        self.openGLWidget.update()

    def getDataPointByLocation(self, coord):
        x, y, z = coord
        for dp in self.dataPoints:
            if dp.location == (x, y, z):
                return dp
        return False

    def paintGL(self):
        glLoadIdentity()
        gluPerspective(45, self.width / self.height, 0.1, 110.0)    #set perspective?
        glTranslatef(0, 0, self.zoomLevel)    #I used -10 instead of -2 in the PyGame version.
        glRotatef(self.rotateDegreeV, 1, 0, 0)    #I used 2 instead of 1 in the PyGame version.
        glRotatef(self.rotateDegreeH, 0, 0, 1)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        if len(self.shapes) != 0:
            glBegin(GL_LINES)
            for s in self.shapes:
                glColor3fv(s.color)
                if s.render and not s.solid:
                    for e in s.edges:
                        for v in e:
                            glVertex3fv(s.vertices[v])
            glEnd()

            glBegin(GL_QUADS)
            for s in self.shapes:
                glColor3fv(s.color)
                if s.render and s.solid:
                    for f in s.facets:
                        for v in f:
                            glVertex3fv(s.vertices[v])
            glEnd()

    def initData(self, sizeX, sizeY, sizeZ):
        marchSizeX = sizeX - 1
        marchSizeY = sizeY - 1
        marchSizeZ = sizeZ - 1
        xOff = -(sizeX / 2) + 0.5
        yOff = -(sizeY / 2) + 0.5
        zOff = -(sizeZ / 2) + 0.5
        xMarchOff = -(marchSizeX / 2) + 0.5
        yMarchOff = -(marchSizeY / 2) + 0.5
        zMarchOff = -(marchSizeZ / 2) + 0.5
        vals = []
        self.marchPoints = []
        for z in range(marchSizeZ):
            for y in range(marchSizeY):
                for x in range(marchSizeX):
                    self.marchPoints.append((x + xMarchOff, y + yMarchOff ,z + zMarchOff))

        for z in range(sizeZ):
            for y in range(sizeY):
                for x in range(sizeX):
                    loc = len(self.dataPoints)
                    val = self.generate(x + xOff, y + yOff, z + zOff)
                    self.dataPoints.append(self.dataPoint())
                    self.dataPoints[loc].place(x + xOff, y + yOff, z + zOff)
                    self.dataPoints[loc].set(val)
                    loc2 = len(self.shapes)
                    self.shapes.append(self.cube())
                    self.shapes[loc2].config(x + xOff, y + yOff, z + zOff, solid = True, color = (0, (val + 1) / 2, (val + 1) / -2 + 1))
                    self.dataPoints[loc].setShape(self.shapes[loc2])
                    vals.append(val)
        print(avg(vals))

    def generate(self, xIn, yIn, zIn):    #Function which produces semi-random values based on the supplied coordinates.
        i = -(xIn * yIn * (10 + zIn))
        j = xIn * yIn * (10 + zIn)
        if i < j:
            mixer = random.randint(i, j)
        else:
            mixer = random.randint(j, i + 1)
        a = avg([sin(cos(xIn)), tan(tan(yIn)), cos(tan(zIn))])
        out = mixer * a
        while out > 10:
            out -= 5
        while out < -10:
            out += 5
        return float(out / 10)

app = QApplication([])
window = mainWindow()
window.setupUI()
window.show()
sys.exit(app.exec_())

为什么立方体没有消失?在我的网络搜索过程中,我发现更新并不总是按预期工作。直接调用self.openGLWidget.paintGL() 也不起作用。我必须怎么做才能让立方体消失?

编辑: 如果我调用旋转、旋转或缩放,屏幕会刷新,网格点以及行进立方体都会消失。我想我最终可能会通过调用其中一个零值来解决问题。

要进行测试,请将以下代码保存在与其余代码相同的目录中名为 numerics.py 的文件中。

from decimal import Decimal as dec

degrad = 'deg'
pi = 3.14159265358979323846
terms = dec(9)    #number of terms used for the trig calculations

def version():
    print('numerics.py version 1.0.0')
    print('Packaged with the cubes project')
def mode(modeinput = ''):    #switch between degrees and radians or check the current mode
    global degrad
    if modeinput == 'deg':
        degrad = 'deg'
        return 'deg'
    if modeinput == 'rad':
        degrad = 'rad'
        return 'rad'
    if modeinput == '':
        return degrad
    else:
        return False

def accuracy(accinput = ''):
    global terms
    global pi
    if accinput == '':
        return terms
    terms = dec(accinput)
    PI = calculatePi(accinput)
    print('Pi is: '.format(PI))
    return terms

def calculatePi(placeIn = terms):
    if placeIn > 15:
        if input("Warning: You have chosen to calculate more than 20 digits of pi. This may take a LONG TIME and may be inacurate. Enter 'yes' if you wish to proceed. If you enter anything else, this function will revert to 10 digits.") == 'yes':
            place = placeIn
        else:
            place = 10
    else:
        place = placeIn
    print('Calculating Pi...\nPlease wait, as this may take a while.')
    PI = dec(3)
    addSub = True
    for i in range(2, 2 * (int(place) ** 6) + 1, 2):
        if addSub:
            PI += dec(4) / (dec(i) * dec(i + 1) * dec(i + 2))
        elif not addSub:
            PI -= dec(4) / (dec(i) * dec(i + 1) * dec(i + 2))
        addSub = not addSub
    return rnd(PI, -(place), mode = 'cutoff')

def radToDeg(radin):
    return (dec(radin) * dec(180 / pi))

def degToRad(degin):
    return (dec(degin) * dec(pi / 180))

def avg(numsIn):    #return the average of two numbers, specified as an integer or float
    num1 = dec(0)
    for i in numsIn:
        num1 += dec(i)
    return rnd(dec(num1 / dec(len(numsIn))))

def sin(anglein, dr = degrad):    #return sine of the supplied angle using the predetermined mode or the supplied mode
    if dr == 'deg':
        while anglein > 180:
            anglein -= 360
        while anglein < -180:
            anglein += 360
        angle = degToRad(anglein)
    if dr == 'rad':
        while anglein > pi:
            anglein -= (2 * pi)
        while anglein < -pi:
            anglein += (2 * pi)
        angle = anglein
    return rnd(rawsin(dec(angle)), -terms)

def arcsin(ratioin, dr = degrad):    #return arcsine of the supplied ratio using the predetermined mode or the supplied mode
    if ratioin > 1 or ratioin < -1:    #if the input is illegal
        return False
    attempt = dec(0)    #start at 0
    target = rnd(dec(ratioin), -terms)    #identify the target value
    #print('target is: '.format(target))
    for i in range(-1, int(terms) + 1):    #for each place from 10s to terms decimal place (use -i, not i)
        #print('Editing place 0'.format(10 ** -i))    #debugging
        for j in range(10):    #for 10 steps
            #print('current attempt: '.format(attempt), end = ' ')
            if rnd(sin(attempt, dr), -terms) == target:
                if attempt < 0:
                    final = (attempt * dec(-1))
                else:
                    final = attempt
                #print('attempt: 0 final: 1'.format(attempt, final))
                return final
            if rnd(sin(attempt, dr), -terms) < target:
                #add some
                attempt += (dec(10) ** -i)
                #print('attempt: '.format(attempt), end = ' ')
            if rnd(sin(attempt, dr), -terms) > target:
                #subtract some
                attempt -= (dec(10) ** -i)
                #print('attempt: '.format(attempt), end = ' ')
            #print('')
    if attempt < 0:
        final = (attempt * dec(-1))
    else:
        final = attempt
    #print('attempt: 0 final: 1'.format(attempt, final))
    return (final)

def cos(anglein, dr = degrad):    #return cosine of the supplied angle
    if dr == 'deg':
        return rawsin(degToRad(90 - anglein))
    else:
        angle = anglein
        return rnd(rawsin(90 - angle), -terms)

def arccos(ratioin, dr = degrad):    #return arccosine of the supplied ratio
    if ratioin > 1 or ratioin < -1:
        return False
    attempt = dec(0)    #start at 0
    target = rnd(dec(ratioin), -terms)    #identify the target value
    #print('target is: '.format(target))
    for i in range(-1, int(terms) + 1):    #for each place from 10s to terms decimal place (use -i, not i)
        #print('Editing place 0'.format(10 ** -i))    #debugging
        for j in range(10):    #for 10 steps
            #print('current attempt: '.format(attempt), end = ' ')
            if rnd(cos(attempt, dr), -terms) == target:
                if attempt < 0:
                    final = (attempt * dec(-1))
                else:
                    final = attempt
                #print('attempt: 0 final: 1'.format(attempt, final))
                return final
            if rnd(cos(attempt, dr), -terms) < target:
                #add some
                attempt += (dec(10) ** -i)
                #print('attempt: '.format(attempt), end = ' ')
            if rnd(cos(attempt, dr), -terms) > target:
                #subtract some
                attempt -= (dec(10) ** -i)
                #print('attempt: '.format(attempt), end = ' ')
            #print('')
    if attempt < 0:
        final = (attempt * dec(-1))
    else:
        final = attempt
    #print('attempt: 0 final: 1'.format(attempt, final))
    return (final)

def tan(anglein, dr = degrad):    #return tangent of the supplied angle
    a = sin(anglein, dr)
    b = cos(anglein, dr)
    if (not a == 0) and (not b == 0):
        return rnd((a / b), -terms)
    else:
        return False

def arctan(ratioin, dr = degrad):    #return arctangent of the supplied ratio
    if ratioin > 1 or ratioin < -1:
        return False
    attempt = dec(0)    #start at 0
    target = rnd(dec(ratioin), -terms)    #identify the target value
    #print('target is: '.format(target))
    for i in range(-1, int(terms) + 1):    #for each place from 10s to terms decimal place (use -i, not i)
        #print('Editing place 0'.format(10 ** -i))    #debugging
        for j in range(10):    #for 10 steps
            #print('current attempt: '.format(attempt), end = ' ')
            if rnd(tan(attempt, dr), -terms) == target:
                if attempt < 0:
                    final = (attempt * dec(-1))
                else:
                    final = attempt
                #print('attempt: 0 final: 1'.format(attempt, final))
                return final
            if rnd(tan(attempt, dr), -terms) < target:
                #add some
                attempt += (dec(10) ** -i)
                #print('attempt: '.format(attempt), end = ' ')
            if rnd(tan(attempt, dr), -terms) > target:
                #subtract some
                attempt -= (dec(10) ** -i)
                #print('attempt: '.format(attempt), end = ' ')
            #print('')
    if attempt < 0:
        final = (attempt * dec(-1))
    else:
        final = attempt
    #print('attempt: 0 final: 1'.format(attempt, final))
    return (final)

def rawsin(anglein):    #return the result of sine of the supplied angle, using radians
#This is the taylor series used.
#final = x - (x^3 / 3!) + (x^5 / 5!) - (x^7 / 7!) + (x^9 / 9!) - (x^11 / 11!)...
    angle = dec(anglein)
    final = angle
    add = False
    for i in range(3, int(terms) * 3, 2):
        if add:
            final += dec(angle ** i) / fact(i)
        elif not add:
            final -= dec(angle ** i) / fact(i)
        add = not add
    return final

def fact(intin):    #return the factorial of the given integer, return False if not given an int
    if intin == int(intin):
        intout = 1
        for i in range(1, intin + 1):
            intout *= i
        return intout
    else:
        return False

def rnd(numIn, decPlcIn = -terms, mode = 'fiveHigher'):    #return the given number, rounded to the given decimal place.
#use 1 to indicate 10s, 0 to indicate 1s, -2 to indicate 100ths, etc.
    num1 = dec(numIn)
    decPlc = dec(decPlcIn)
    if mode == 'fiveHigher':
        return dec(str(dec(round(num1 * (dec(10) ** -decPlc))) * (dec(10) ** decPlc)).rstrip('0'))
    elif mode == 'cutoff':
        return dec(str(dec(int(num1 * (dec(10) ** -decPlc))) * (dec(10) ** decPlc)).rstrip('0'))

def root(numIn, rootVal):
    num = dec(numIn)
    rt = dec(dec(1) / rootVal)
    num1 = num ** rt
    return rnd(num1, -terms)

def quad(aIn, bIn, cIn):    #Plugin for the quadratic formula. Provide a, b, and c.
    a = dec(aIn)
    b = dec(bIn)
    c = dec(cIn)
    try:
        posResult = (-b + root((b ** dec(2)) - (dec(4) * a * c), 2)) / (dec(2) * a)
    except:
        posResult = False
    try:
        negResult = (-b - root((b ** dec(2)) - (dec(4) * a * c), 2)) / (dec(2) * a)
    except:
        negResult = False
    return (posResult, negResult)

【问题讨论】:

什么是数字? @eyllanesc numerics 是一个基于十进制库的自定义数学库。它执行基本的数学函数。我使用它主要是因为我深入了解它,部分是因为我为它感到自豪。 分享一下你用来轻松测试MRE的函数代码会很好。 @eyllanesc 感谢您的提示。我已经添加了。 @Rabbid76 我终于有机会测试它了。是的,你的修复成功了! 【参考方案1】:

您错过了 1 个对 self.openGLWidget.update() 的呼叫。在if的指令块中有return语句。函数此时终止,代码末尾的self.openGLWidget.update()指令永远不会执行。

return之前添加self.openGLWidget.update(),解决问题:

class mainWindow(QMainWindow):  
    # [...]

    def marchStep(self):
        if not self.marchActive:
            # [...]
            self.currentStep = 0
        if self.currentStep == len(self.marchPoints):
            # [...]
            self.meshPoints.clear()
            self.marchingCube.hide()

            self.openGLWidget.update() # <--------- ADD 
            return

        if self.currentStep == 0:
            self.marchingCube.show()

        # [...]

【讨论】:

以上是关于为啥不更新重绘场景?的主要内容,如果未能解决你的问题,请参考以下文章

如何仅刷新/重绘 uicollectionview 中的补充视图?

克隆节点和性能、几何和性能; IOS 场景包

为啥我的场景在横向调整大小以不使用整个屏幕空间?

UE4 为啥粒子特效放到场景不播放 只有点击播放按钮 才出现

为啥攻防世界无法获取场景?

为啥unity场景中对象都变成了淡红色的