pyqt 中的分形 --> 递归问题

fractal in pyqt --> problems with recursion

我正在尝试绘制这个简单的树形分形但没有成功... 我尝试了很多组合来使递归正常工作,但似乎从未成功获得我想要的形状。 这是我最后得到的代码。

from PyQt5 import QtGui, QtWidgets, QtCore, Qt
import sys

class Arena(QtWidgets.QWidget):
    def __init__(self):
        super(Arena, self).__init__()
    self.angle = 45
    self.transform = QtGui.QTransform()
    self.translate2 =  0
    self.recursions = 10

    self.setGeometry(2500, 400, 500, 500)

    self.origin = (self.width()/2, self.height())
    self.pal = QtGui.QPalette()
    self.pal.setColor(QtGui.QPalette.Background, QtGui.QColor(0, 0, 0))
    self.setPalette(self.pal)

    self.pen_branch = QtGui.QPen()
    self.pen_branch.setColor(QtGui.QColor(255, 255, 255))

初始化class

    self.init_UI()

def init_UI(self):
    self.slider_angle = QtWidgets.QSlider(QtCore.Qt.Horizontal, self)
    self.slider_angle.setMinimum(-8000)
    self.slider_angle.setMaximum(8000)
    self.slider_angle.setGeometry(0, 50, self.width(), 50)
    self.slider_angle.valueChanged.connect(
        lambda value, x=0 : self.setAngleValue(value))
    self.setAngleValue(4500)
    self.slider_angle.setValue(4500)

建立UI

def setAngleValue(self, value):
    self.angle = value/100
    self.update()

设置角度

def branch(self, p, len):
    p.drawLine(0, 0, 0, -len)
    p.translate(0,-len)
    if len > 3:
        len *= 0.66

        self.transform = p.transform()
        p.rotate(self.angle)
        self.branch(p, len)
        p.setTransform(self.transform)

        self.transform = p.transform()
        p.rotate(-self.angle)
        p.translate(0, len)
        self.branch(p, len)
        p.setTransform(self.transform)

分形的魔力应该在这里发生:递归方法(AFAIK ...) 我使用 'p.transform' 获取转换矩阵并在创建行后将其恢复。

def paintEvent(self, e):
    p = QtGui.QPainter(self)
    p.setRenderHint(QtGui.QPainter.Antialiasing)
    p.setPen(self.pen_branch)
    p.drawText(20, 20,  'angle: ' + str(self.angle))
    p.drawText(100, 20,  'trans2: ' + str(self.translate2))


    # Trunk
    p.translate(self.width()/2, self.height())
    p.drawLine(0, 0, 0, -100)

    self.branch(p, 100)

在 canvas 上绘制线条的绘制事件。

app = QtWidgets.QApplication(sys.argv)
Arena = Arena()
Arena.show()
app.exec_()

应用程序运行器。

经过一些摆弄,我已经达到了我正在寻找的结果,但仍然不是目标。我想创建一个完美对称的树,但这是我得到的:

新代码是这样的:

def branch(self, p, x, r, len):

    p.drawLine(0, 0, 0, -len)
    if len > 1 :
        p.translate(0, -len)
        p.rotate(r)
        self.branch(p, 10, self.angle, len * 0.66)

        p.rotate(-r)
        self.branch(p, 10, -self.angle, len * 0.66)
        p.translate(0, len)
        p.rotate(-r)

def paintEvent(self, e):
    p = QtGui.QPainter(self)
    p.setRenderHint(QtGui.QPainter.Antialiasing)
    p.setPen(self.pen_branch)
    p.drawText(20, 20,  'angle: ' + str(self.angle))
    p.drawText(100, 20,  'trans2: ' + str(self.translate2))

    p.translate (self.width()/2, self.height())
    self.branch(p, 10, self.angle, 200)

问题在于branch函数是如何实现的:你第一次调用rotate后,另一个分支需要使用doubled角度以便朝相反的方向移动。

考虑一下:假设角度是30°,如果你为右分支调用rotate(r),那么你必须为左分支调用rotate(-r * 2);通过这种方式,角度“重置”为原始值,然后在另一侧旋转。

这是您函数的修改版本:

    def branch(self, p, x, r, length):

        p.drawLine(0, 0, 0, -length)
        if length > 1 :
            p.translate(0, -length)
            p.rotate(r)
            self.branch(p, 10, r, length * 0.66)

            # rotate to the opposite side
            <b>p.rotate(-r * 2)</b>
            self.branch(p, 10, -r, length * 0.66)
            # note that translation and rotation are inverted, opposed to your
            # example, this is because you translated first and *then* applied
            # rotation, so you should "reset" those states in the opposite way
            <b>p.rotate(r)
            p.translate(0, length)</b>

为避免此类问题,通常最好使用 save() and restore() 函数,它允许您使用多个级别的画家状态:您 save 一个状态,应用您想要的所有修改(笔、画笔和任何种变换),然后你可以自动restore之前的状态;这使开发更容易,并获得更具可读性和可理解性的代码;请记住,状态必须 始终 恢复 到原始保存的“级别”。

下面是一个更好的 branch 函数的样子:

    def branch(self, p, x, r, length):
        p.drawLine(0, 0, 0, -length)
        if length > 3:
            # save state, first level (for the current function)
            p.save()
            p.translate(0, -length)

            # save state for the right branch
            p.save()
            p.rotate(r)
            self.branch(p, 10, r, length * .66)
            # restore to the previous "first" level
            p.restore()

            # again, for the left branch
            p.save()
            p.rotate(-r)
            self.branch(p, 10, -r, length * .66)
            # restore again
            p.restore()

            # restore the previous state
            p.restore()

结果如下:

一些建议:

  • 使用描述性变量名(pxr意义不大);
  • 永远不要使用内置变量名(在你的例子中,len);
  • valueChanged 的 lambda 连接似乎没用; x 变量有什么用?请尽量使您的示例尽可能少,避免不必要的变量或函数,这些变量或函数只会给 reader;
  • 为每个任务使用单独的小部件;创建一个主容器(可能是“顶层 window”),设置布局管理器,然后向其中添加滑块和自定义小部件;尽可能避免设置固定的几何形状:UI 应该能够自行调整到 window 大小(“响应式”,我们现在称之为);