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()
结果如下:
一些建议:
- 使用描述性变量名(
p
、x
和r
意义不大);
- 永远不要使用内置变量名(在你的例子中,
len
);
valueChanged
的 lambda 连接似乎没用; x
变量有什么用?请尽量使您的示例尽可能少,避免不必要的变量或函数,这些变量或函数只会给 reader;
- 为每个任务使用单独的小部件;创建一个主容器(可能是“顶层 window”),设置布局管理器,然后向其中添加滑块和自定义小部件;尽可能避免设置固定的几何形状:UI 应该能够自行调整到 window 大小(“响应式”,我们现在称之为);
我正在尝试绘制这个简单的树形分形但没有成功... 我尝试了很多组合来使递归正常工作,但似乎从未成功获得我想要的形状。 这是我最后得到的代码。
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()
结果如下:
一些建议:
- 使用描述性变量名(
p
、x
和r
意义不大); - 永远不要使用内置变量名(在你的例子中,
len
); valueChanged
的 lambda 连接似乎没用;x
变量有什么用?请尽量使您的示例尽可能少,避免不必要的变量或函数,这些变量或函数只会给 reader;- 为每个任务使用单独的小部件;创建一个主容器(可能是“顶层 window”),设置布局管理器,然后向其中添加滑块和自定义小部件;尽可能避免设置固定的几何形状:UI 应该能够自行调整到 window 大小(“响应式”,我们现在称之为);