最近尝试用turtle写的作品,发出来供大家参考
主题:春节
特色:采用先进和完备的面向对象编程,继承并重写了turtle的画笔的部分功能以将其可面向对象化。素材复用方便,代码逻辑清晰明了,函数名称遵循大小驼峰规则,语意清晰。 制备了可复用的turtle轮子和debug tools,可以在开发阶段使用鼠标进行简单的绘画以加速作品制作。 使用分文件的项目,方便管理与查看
技术要点:类、对象、继承、重载、模块化思想
作品本身较为简单,但模块化设计为turtle设计了一个轮子,对于重写的函数已经放在Base里面了(其实base也可以继承turtle,但是并没有这么设计)
对于鼠标便捷操作,左键设定第一个点,然后一直左键,最后以右键提笔,提笔状态再次右键可以回到第一个点,中间的福字就是这么描出来的(让我用turtle自行设计一个福字还是饶了我吧)。
以下文件,类名即为文件名。
main.py:
import turtle
import Banner
import Fu
import Lantern
if __name__ == '__main__':
"""初始化turtle"""
turtle.setup(1280, 1024) # bba785
turtle.screensize(bg="#f2dcbb")
"""是否开启debug,不开启debug会导致绘画速度巨慢"""
debug = False
"""鼠标绘画,画福字用的,仅限测试"""
# draw = TurtleTool.MouseDraw(0, 0)
# tool = TurtleTool.Tool()
# tool.printMouseDraw(draw)
"""杂项测试,已过时"""
# square = Test.TestSquare(0, 260, 280, -100, size=0.6)
# square.draw()
"""对联和横批,顺序是上联-下联-横批"""
banner1 = Banner.Banner(260, -150, "一元复始龙增岁", rotate=-90, size=0.6, debug=debug)
banner1.draw()
banner2 = Banner.Banner(-260, -150, "万象生辉燕报春", rotate=-90, size=0.6, debug=debug)
banner2.draw()
banner = Banner.Banner(0, 230, "龙凤呈祥", rotate=180, size=0.8, debug=debug)
banner.draw()
"""灯笼,小灯笼在for里面,两个大灯笼在for外面,只需要改变参数即可调整"""
Lantern.Lantern(-300, 500, size=1, length=60, debug=debug).draw()
for i in range(3):
Lantern.Lantern(-140 + i * 140, 500, size=0.8, debug=debug).draw()
Lantern.Lantern(300, 500, size=1, length=60, debug=debug).draw()
"""福字"""
fu = Fu.Fu(0, -60, rotate=180, size=0.3, debug=debug) # 对于“大门上不能贴倒福”的问题,将rotate改为0即可解决。
fu.draw()
"""主循环"""
turtle.mainloop()
import turtle
import TurtleBasicDraw
class Lantern(TurtleBasicDraw.BaseDraw):
def __init__(self, x, y, length=40, size=1.0, widthSize=4, rotate=0, mainColor="#ba932a", debug=False):
super().__init__(x, y, size, widthSize, rotate, mainColor, debug)
# length:灯绳的长度, must > 20
if length < 20:
raise ValueError
self.length = length
# self.relocate(0,-20-10-80) # 中心对齐方案,不是我需要的
self.relocate(0, length - 20) # 绳头对齐方案,正是我需要的
def draw(self):
super().draw()
self.drawLine(0, self.length - 20, 0, -20)
if not self.debug:
# 灯笼头
turtle.fillcolor("#f9d800")
turtle.begin_fill()
self.drawLine(-30, -30, -30, -20)
self.setpos(30, -20)
self.setpos(30, -30)
self.setpos(-30, -30)
turtle.end_fill()
# 灯笼框架
turtle.fillcolor("#fb3949")
turtle.begin_fill()
self.drawLine(-60, -30, 60, -30)
self.setpos(60, -180)
self.setpos(-60, -180)
self.setpos(-60, -30)
turtle.end_fill()
if self.debug:
return
# 三条内部线
self.drawLine(-30, -30, -30, -180)
self.drawLine(0, -30, 0, -180)
self.drawLine(30, -30, 30, -180)
# 灯笼尾
turtle.fillcolor("#f9d800")
turtle.begin_fill()
self.drawLine(-30, -180, -30, -190)
self.setpos(30, -190)
self.setpos(30, -180)
self.setpos(-30, -180)
turtle.end_fill()
self.drawLine(-30, -190, -30, -230)
self.drawLine(-15, -190, -15, -230)
self.drawLine(0, -190, 0, -230)
self.drawLine(15, -190, 15, -230)
self.drawLine(30, -190, 30, -230)
# self.setpos(250, 0)
import TurtleBasicDraw
import turtle
class Fu(TurtleBasicDraw.BaseDraw):
"""福字来源是TurtleBasicDraw.MouseDraw的输出,类似于ps中钢笔的使用。"""
def __init__(self, x, y, size=1.0, widthSize=1, rotate=0, mainColor="black", debug=False):
super().__init__(x, y, size, widthSize, rotate, mainColor, debug)
def drawWordOut(self):
# 扣出透明区域
turtle.fillcolor("#d60d17")
turtle.begin_fill()
turtle.penup()
self.setpos(24.0, 32.0)
turtle.pendown()
self.setpos(33.0, 45.0)
self.setpos(47.0, 51.0)
self.setpos(51.0, 43.0)
self.setpos(43.0, 33.0)
self.setpos(28.0, 29.0)
self.setpos(24.0, 32.0)
turtle.penup()
turtle.end_fill()
turtle.begin_fill()
turtle.penup()
self.setpos(15.0, -12.0)
turtle.pendown()
self.setpos(27.0, -7.0)
self.setpos(40.0, -0.0)
self.setpos(57.0, -0.0)
self.setpos(66.0, -6.0)
self.setpos(18.0, -12.0)
self.setpos(15.0, -12.0)
turtle.penup()
turtle.end_fill()
turtle.begin_fill()
turtle.penup()
self.setpos(5.0, -54.0)
turtle.pendown()
self.setpos(9.0, -48.0)
self.setpos(13.0, -47.0)
self.setpos(19.0, -51.0)
self.setpos(11.0, -58.0)
self.setpos(3.0, -57.0)
self.setpos(5.0, -54.0)
turtle.penup()
turtle.end_fill()
turtle.begin_fill()
turtle.penup()
self.setpos(11.0, -94.0)
turtle.pendown()
self.setpos(16.0, -89.0)
self.setpos(24.0, -94.0)
self.setpos(15.0, -102.0)
self.setpos(11.0, -93.0)
self.setpos(11.0, -94.0)
turtle.penup()
turtle.end_fill()
turtle.begin_fill()
turtle.penup()
self.setpos(72.0, -49.0)
turtle.pendown()
self.setpos(69.0, -46.0)
self.setpos(69.0, -41.0)
self.setpos(77.0, -43.0)
self.setpos(84.0, -43.0)
self.setpos(91.0, -45.0)
self.setpos(97.0, -46.0)
self.setpos(97.0, -56.0)
self.setpos(73.0, -50.0)
self.setpos(72.0, -49.0)
turtle.penup()
turtle.end_fill()
turtle.begin_fill()
turtle.penup()
self.setpos(56.0, -89.0)
turtle.pendown()
self.setpos(86.0, -77.0)
self.setpos(81.0, -94.0)
self.setpos(64.0, -97.0)
self.setpos(57.0, -90.0)
self.setpos(56.0, -89.0)
turtle.penup()
turtle.end_fill()
def drawWord(self):
turtle.pencolor("#fcb15e")
turtle.fillcolor(self.mainColor)
turtle.begin_fill()
turtle.penup()
self.setpos(-59.0, 152.0)
turtle.pendown()
self.setpos(-59.0, 158.0)
self.setpos(-63.0, 165.0)
self.setpos(-80.0, 160.0)
self.setpos(-94.0, 151.0)
self.setpos(-110.0, 137.0)
self.setpos(-120.0, 118.0)
self.setpos(-119.0, 95.0)
self.setpos(-111.0, 80.0)
self.setpos(-103.0, 67.0)
self.setpos(-120.0, 45.0)
self.setpos(-137.0, 32.0)
self.setpos(-152.0, 12.0)
self.setpos(-150.0, -7.0)
self.setpos(-141.0, -23.0)
self.setpos(-134.0, -26.0)
self.setpos(-120.0, -26.0)
self.setpos(-114.0, -25.0)
self.setpos(-132.0, -43.0)
self.setpos(-145.0, -56.0)
self.setpos(-153.0, -79.0)
self.setpos(-147.0, -91.0)
self.setpos(-139.0, -102.0)
self.setpos(-126.0, -113.0)
self.setpos(-113.0, -116.0)
self.setpos(-106.0, -113.0)
self.setpos(-99.0, -110.0)
self.setpos(-97.0, -127.0)
self.setpos(-93.0, -140.0)
self.setpos(-87.0, -148.0)
self.setpos(-79.0, -155.0)
self.setpos(-70.0, -157.0)
self.setpos(-53.0, -155.0)
self.setpos(-41.0, -147.0)
self.setpos(-34.0, -135.0)
self.setpos(-37.0, -120.0)
self.setpos(-41.0, -102.0)
self.setpos(-49.0, -76.0)
self.setpos(-48.0, -46.0)
self.setpos(-43.0, -20.0)
self.setpos(-33.0, 7.0)
self.setpos(-24.0, 13.0)
self.setpos(-14.0, -8.0)
self.setpos(-28.0, -6.0)
self.setpos(-41.0, -26.0)
self.setpos(-50.0, -53.0)
self.setpos(-47.0, -86.0)
self.setpos(-33.0, -131.0)
self.setpos(-12.0, -145.0)
self.setpos(28.0, -139.0)
self.setpos(47.0, -139.0)
self.setpos(57.0, -157.0)
self.setpos(90.0, -161.0)
self.setpos(120.0, -141.0)
self.setpos(143.0, -88.0)
self.setpos(151.0, -61.0)
self.setpos(153.0, -29.0)
self.setpos(132.0, -7.0)
self.setpos(115.0, 3.0)
self.setpos(98.0, 8.0)
self.setpos(107.0, 36.0)
self.setpos(117.0, 46.0)
self.setpos(109.0, 60.0)
self.setpos(103.0, 78.0)
self.setpos(85.0, 84.0)
self.setpos(27.0, 73.0)
self.setpos(116.0, 97.0)
self.setpos(120.0, 127.0)
self.setpos(112.0, 141.0)
self.setpos(101.0, 153.0)
self.setpos(63.0, 153.0)
self.setpos(26.0, 144.0)
self.setpos(-6.0, 133.0)
self.setpos(-14.0, 121.0)
self.setpos(-15.0, 99.0)
self.setpos(2.0, 86.0)
self.setpos(5.0, 82.0)
self.setpos(-19.0, 68.0)
self.setpos(-29.0, 45.0)
self.setpos(-44.0, 61.0)
self.setpos(-76.0, 57.0)
self.setpos(-103.0, 44.0)
self.setpos(-76.0, 57.0)
self.setpos(-44.0, 61.0)
self.setpos(-25.0, 63.0)
self.setpos(-14.0, 83.0)
self.setpos(-12.0, 100.0)
self.setpos(-18.0, 123.0)
self.setpos(-37.0, 143.0)
self.setpos(-57.0, 151.0)
self.setpos(-58.0, 159.0)
self.setpos(-59.0, 152.0)
turtle.penup()
turtle.end_fill()
self.drawWordOut()
def draw(self):
super().draw()
# 外壳
turtle.pencolor("#d60d17")
turtle.fillcolor("#d60d17")
turtle.begin_fill()
self.drawLine(0, 350, -350, 0)
self.setpos(0, -350)
self.setpos(350, 0)
self.setpos(0, 350)
turtle.end_fill()
if self.debug:
return
# 内边
turtle.pencolor("#fcb15e")
turtle.penup()
self.setpos(0, 325)
turtle.pendown()
self.setpos(-325, 0)
self.setpos(0, -325)
self.setpos(325, 0)
self.setpos(0, 325)
self.drawWord()
import turtle
import TurtleBasicDraw
class BannerSide(TurtleBasicDraw.BaseDraw):
def __init__(self, x, y, size=1.0, widthSize=3, rotate=0, mainColor="#ba932a", debug=False):
super().__init__(x, y, size, widthSize, rotate, mainColor, debug)
def draw(self):
super().draw()
# 上半圆
turtle.fillcolor("#e37e14")
turtle.begin_fill()
self.drawLine(10, -10, -10, -10)
self.setheading(90)
self.circle(-10, extent=180)
turtle.end_fill()
# 下半圆
self.drawLine(-10, -10, -10, -110)
turtle.begin_fill()
self.setheading(-90)
self.circle(10, extent=180)
self.setpos(-10, -110)
turtle.end_fill()
# 卷轴
turtle.fillcolor("#ffce31")
turtle.begin_fill()
self.drawLine(-10, -10, 10, -10)
self.setpos(10, -110)
self.setpos(-10, -110)
self.setpos(-10, -10)
turtle.end_fill()
turtle.fillcolor("#fd4043")
turtle.begin_fill()
self.drawLine(-10, -20, 10, -20)
self.setpos(10, -100)
self.setpos(-10, -100)
self.setpos(-10, -20)
turtle.end_fill()
class Banner(TurtleBasicDraw.BaseDraw):
def __init__(self, x, y, word, size=1.0, widthSize=4, rotate=0, mainColor="black", debug=False):
super().__init__(x, y, size, widthSize, rotate, mainColor, debug)
self.relocate(0,-60)
self.word = word
self.posX = 100 * (len(self.word) / 2) + 50
pos1 = self.getpos(-self.posX - 10, 0)
pos2 = self.getpos(self.posX + 10, 0)
self.bs1 = BannerSide(pos1[0], pos1[1], size, widthSize, rotate, mainColor)
self.bs2 = BannerSide(pos2[0], pos2[1], size, widthSize, rotate, mainColor)
def draw(self):
# r = 10
# length = 10+10+200+10+10
# height = 10+100+10
super().draw()
if not self.debug:
self.bs1.draw()
self.bs2.draw()
posX = self.posX
# 中间部分
turtle.fillcolor("#ffce31")
turtle.begin_fill()
self.drawLine(-posX, -10, posX, -10)
self.setpos(posX, -110)
self.setpos(-posX, -110)
self.setpos(-posX, -10)
turtle.end_fill()
if self.debug:
return
turtle.fillcolor("#fd4043")
turtle.begin_fill()
self.drawSquare(-posX, -20, posX, -100) # 方便多了
turtle.end_fill()
# 字样 (注意,字底边对齐,这是坑点。
turtle.penup()
turtle.pencolor(self.mainColor)
self.setpos(-posX + 100, -60) # 第一个字正中央 需要适应turtle的中心底部对齐
for w in self.word:
turtle.setheading(-90) # 强制向下,因为turtle的中心底部对齐
self.forward(50) # 移动到中心底部对齐位置
self.write(w, align="center", font=("华文行楷", 55, "normal")) # write不需要pendown,up的时候移速更快。
self.back(50) # 回到中心
self.setheading(0) # 旋转适应 向前,准备下一个字
self.forward(100)
import math
import turtle
class BaseDraw:
"""
智能对象,允许根据size和rotate算出真实的位置
"""
def __init__(self, x, y, size=1.0, widthSize=4, rotate=0, mainColor="#ba932a", debug=False):
self.x = x
self.y = y
self.size = size
self.rotate = rotate
self.widthSize = widthSize # 画笔尺寸缩放倍率
self.mainColor = mainColor # 对于部分复杂图案,mainColor可能不会被使用,仅供参考。
self.debug = debug
def relocate(self, offsetX, offsetY):
"""
根据所传入的内容倒推出自身的定位,属于取巧操作
:param offsetX: 自身中心点距离自身零点的距离X
:param offsetY: 自身中心点距离自身零点的距离Y
:return:
"""
pos = self.getpos(-offsetX, -offsetY)
self.x = pos[0]
self.y = pos[1]
def forward(self, length):
turtle.forward(length * self.size)
def back(self, length):
turtle.back(length * self.size)
# def sety(self,offsetY):
# nowPos = turtle.pos()
# nowX = nowPos[0]
# nowY = nowPos[1]
# offsetX =
# turtle.setpos(self.x )
def getpos(self, offsetX, offsetY):
return (self.x +
offsetX * self.size * math.cos(math.radians(self.rotate)) -
offsetY * self.size * math.sin(math.radians(self.rotate)),
self.y +
offsetY * self.size * math.cos(math.radians(self.rotate)) +
offsetX * self.size * math.sin(math.radians(self.rotate)))
def setpos(self, offsetX, offsetY):
pos = self.getpos(offsetX, offsetY)
turtle.setpos(pos[0], pos[1])
def setheading(self, to_angle):
turtle.setheading(self.rotate + to_angle)
def circle(self, radius, extent=None):
turtle.circle(radius * self.size, extent)
def draw(self):
turtle.penup()
self.setpos(0, 0)
turtle.width(self.widthSize)
turtle.color(self.mainColor)
turtle.left(self.rotate)
turtle.pendown()
def drawLine(self, offsetX1, offsetY1, offsetX2, offsetY2):
turtle.penup()
self.setpos(offsetX1, offsetY1)
turtle.pendown()
self.setpos(offsetX2, offsetY2)
def drawSquare(self, offsetX1, offsetY1, offsetX2, offsetY2):
# 方便的画出一个方形,我怎么才想到呢
# 提供两个对角的顶点。
self.drawLine(offsetX1, offsetY1, offsetX2, offsetY1)
self.setpos(offsetX2, offsetY2)
self.setpos(offsetX1, offsetY2)
self.setpos(offsetX1, offsetY1)
def write(self, arg: object, move: bool = False, align: str = "left",
font: tuple[str, int, str] = ("Arial", 8, "normal")) -> None:
font1 = (font[0], int(font[1] * self.size), font[2])
turtle.write(arg, move, align, font1)
class Ellipse(BaseDraw):
def __init__(self, x, y, a, b, size=1.0, widthSize=2, rotate=0, mainColor="#ba932a", n=100):
super().__init__(x, y, size, widthSize, rotate, mainColor)
self.a = a
self.b = b
self.n = n
def draw(self):
super().draw()
a = self.a / self.size
b = self.b / self.size
turtle.penup()
self.setpos(a, 0)
turtle.pendown()
for i in range(self.n): # i == per n
radian = 2 * math.pi / self.n # l = 2pi(弧度制)
theta = (i + 1) * radian # 下一个坐标弧度
self.setpos(a * math.cos(theta), b * math.sin(theta))
↑这个文件除外↑,文件名是TurtleBaseDraw.py
import turtle
import TurtleBasicDraw
class MouseDraw(TurtleBasicDraw.BaseDraw):
penDown = False
startPos = (0, 0)
def __init__(self, x, y, size=1.0, widthSize=4, rotate=0, mainColor="#ba932a", debug=False):
super().__init__(x, y, size, widthSize, rotate, mainColor, debug)
def mouseLeftClickCallBack(self, x, y):
if not self.penDown:
self.draw()
payload = "turtle.penup()"
print(payload)
eval(payload)
self.startPos = (x, y)
# self.penDown = False
payload = "self.setpos({}, {})".format(x, y)
print(payload)
eval(payload)
if not self.penDown:
payload = "turtle.pendown()"
print(payload)
eval(payload)
self.penDown = True
def mouseRightClickCallBack(self, x, y):
if self.penDown:
payload = "turtle.penup()"
print(payload)
eval(payload)
self.penDown = False
else:
payload = "self.setpos({}, {})".format(self.startPos[0], self.startPos[1])
print(payload)
eval(payload)
payload = "turtle.penup()"
print(payload)
class Tool:
def printMouseDraw(self, draw: MouseDraw):
turtle.Screen().onclick(draw.mouseLeftClickCallBack)
turtle.Screen().onclick(draw.mouseRightClickCallBack, 3)
鼠标工具,挺好用的,只可惜等到我画福了才想起来这个操作