命令行单行指令式python海龟画笔练习工具 也可以用鼠标
- 效果展示:
- 代码展示:
- 素描图片的生成 需要pillow库
效果展示:
本代码供练习海龟画笔的命令 和综合复习python语法使用,如有疑问请留言
代码展示:
话不多说 代码伺候
'''运行后直接在 控制台 输入代码指令 即可控制画笔 使用鼠标左中右键也阔以哦
首次读代码的时候 如果看到函数 简单看一眼就可以跳过 要找到函数的调用点再读
运行以后由于还要在控制台输入命令 所以可以适当把编辑器缩小 留下终端区即可
代码若是改坏了 可去这里下载原始版本
'''
import turtle
import threading
t = turtle.Pen() # 画笔一 用于画图
t2 = turtle.Pen() # 画笔二 用于常用海龟命令的打印
t3 = turtle.Pen() # 画笔三 用于在需要时给画布上的位置做标记
t2.hideturtle()
t3.hideturtle()
# 基础设置
width = 1000 # width变量存储画布的宽
height = 700 # height变量存储画布的高
draw_name = '花华哗画' # draw_name变量存储画布的标题
t.screen.setup(width, height) # 设置画布的宽高
t.screen.title(draw_name) # 设置画布窗口的标题
# t.screen.tracer(0)
is_order_detail_show = False
old_file=None # 之前老的代码文件名
# 用字典嵌套的格式存储常用海龟画笔的命令
normal_order = {
'请点击你要查看的指令集或功能\n1画笔移动': {'1画笔移动类:\n撤销':'undo()','前进': 'forward()', '后退': 'backward()', '左转': 'left()', '右转': 'right()', '文字书写': 'write("内容",font=("字体(可以空着)",大小)))',
'移动到某点': 'goto(x,y)', '抬笔': 'penup()', '落笔': 'pendown()', '隐藏画笔': 'hideturtle()','画笔位置': 'position()',
'显示画笔': 'showturtle()', '画笔速度': 'speed() # 0是最快的 1~10逐渐变快', '画笔颜色': 'color() # 颜色单词记得是引号包裹的字符串', '画笔粗细': 'pensize()', '画圆': 'circle(r:半径)'},
'2形状和颜色': {'2形状和颜色类\n画圆弧': 'circle(r:半径,L:弧度,p:内接多边形)', '画笔形状': 'shape()',
'打印所有画笔形状': 'screen().getshapes()', '画笔盖章': 'stamp()', '清除盖章': 'clearstamp()',
'颜色填充': 'fillcolor()', '开始填充前命令': 'begin_fill()','结束填充命令': 'end_fill()'},
'3窗口操作': {'3窗口操作类\n窗口标题': 'screen.title()', '背景颜色': 'screen.bgcolor()', '窗口大小': 'screen.setup(宽,高)',
'背景图片': 'screen.bgpic() 需要png 或 gif为后缀名的RGBA模式图片', '获取窗口宽度': 'screen.window_width()', '获取窗口高度': 'screen.window_height()',
'重新设置海归画笔坐标系': 'screen.setworldcoordindates(x1,y1,x2,y2)'},
'4键盘鼠标事件': {
'4键盘鼠标事件类\n监听鼠标点击': 'screen.onclick(fun) fun代表某函数名,该函数会接收传过来的鼠标点击的坐标x,y',
'监听键盘按下': 'screen.onkey(fun,key),此键盘监听需要配合screen.listen()命令一起使用',
'监听鼠标对画笔的拖动': 'ondrag(t.goto,1) 1是左键 2是滚轮 3是右键 ',
'弹出获取用户输入的弹框':'turtle.textinput("标题","说明内容") # 有返回值 取消为None'},
'5临摹模式': ' ',
'6继续上次绘制': '',
'7直接开始绘制吧': '',
'8结束绘制':''
}
# 显示海归画笔的常用命令
def show_normal_order(x=None):
global is_order_detail_show
if x != None : # 如果有参数传进来 则按照参数指定的具体指令集进行显示
if is_order_detail_show == False:
hide_normal_order()
is_order_detail_show = True
t2.color('red')
count = 0
for i in normal_order[x]:
t2.penup()
t2.goto(-t.screen.window_width()//2+10,
t.screen.window_height()//3-count*35)
t2.write(i+':'+normal_order[x][i], font=('', 20))
t2.pendown()
count += 1
t2.penup()
t2.goto(-t.screen.window_width()//2+10,
t.screen.window_height()//3-count*35)
t2.color('blue')
t2.write('点击画布右上角1/8区域,可再次显示指令集', font=('', 20))
else: # 如果没有参数传进来 则显示指令集的外层分类
is_order_detail_show = False
t2.color('blue')
count = 0
for i in normal_order:
t2.penup()
t2.goto(-t.screen.window_width()//2+10, t.screen.window_height()//3-count*45)
t2.write(i, font = ('', 30))
t2.pendown()
count += 1
t2.penup()
t2.goto(-t.screen.window_width()//2+10,
t.screen.window_height()//3-(count+5)*45)
t2.color('pink')
t2.write('''使用技巧:
点击画布右下角1/4区域,可隐藏指令集,点击右上角1/8再次显示
鼠标左键可以拖曳画笔(请慢一点拖动) 右键可以无痕移动位置 中键可以撤销操作\n
在终端区输入命令时 请都带上画笔名字t 例如 t.color('red')
在终端区使用键盘的上↑下↓方向键可以切换之前输入过的命令
必须在终端区输入break才可以完整结束绘制 然后生成记录绘制过程的python代码''', font=('', 20))
# 隐藏海归画笔的命令
def hide_normal_order():
global is_order_detail_show
is_order_detail_show = True
t2.clear()
def choose_order(x,y):
if x < -200 and is_order_detail_show == False:
if 0 < y - t.screen.window_height()//3 < 35:
show_normal_order('请点击你要查看的指令集或功能\n1画笔移动')
elif -45 < y - t.screen.window_height()//3 < 0:
show_normal_order('2形状和颜色')
elif -90 < y - t.screen.window_height()//3 < -45:
show_normal_order('3窗口操作')
elif -135 < y - t.screen.window_height()//3 < 90:
show_normal_order('4键盘鼠标事件')
elif -180 < y - t.screen.window_height()//3 < -135:
import tkinter
from tkinter import filedialog
new_bg = filedialog.askopenfilename(
title='请选择png格式的图片', initialfile='*.png')
try:
t.screen.bgpic(new_bg)
except:
print('\n\n路径有误 要求输入图片文件的路径必须是正确的 并且包括图片的后缀名一起 例如 code.png')
print('(下面光标闪烁区 依旧可以写命令行代码 并按回车执行:)')
elif -225 < y - t.screen.window_height()//3 < -180:
import tkinter
from tkinter import filedialog
global old_file
global old_file_orders
old_file = filedialog.askopenfilename(
title='请选择之前的绘制文件', initialfile='*.py')
if old_file != '' :
with open(old_file, 'r', encoding='utf8') as f:
old_file_orders = f.readlines()
for index, i in enumerate(old_file_orders):
if i.strip() == 'turtle.done()':
old_file_orders[index]=''
else:
exec(i)
elif -270 < y - t.screen.window_height()//3 < -225:
hide_normal_order()
new_name=turtle.textinput('给画起个新名字吧','有名字 请输入,取消 则将会用默认名字')
global draw_name # 使得之前函数外定义的draw_name全局变量 可以被函数内的代码修改
if new_name != '' and new_name != None: # 如果用户重新定义了画布名则直接设置 否则不处理
draw_name = new_name
t.screen.title(draw_name)
elif -315 < y - t.screen.window_height()//3 < -270:
hide_normal_order()
ready_draw('break')
elif x > 0 and y < 0:
hide_normal_order()
elif x > t.screen.window_height()//4 and y > t.screen.window_height()//4: # 画布右上角1/8区域
hide_normal_order()
show_normal_order()
# 定义鼠标滚轮点击的作用 为撤销上次操作
def go_back(x,y):
t.undo()
try :
order_list.pop()
except:
pass
# order_list.append('t.undo()')
# 定义鼠标左键可以拖动画笔
def drag_to(x,y):
order_list.append(f't.goto({x},{y})')
t.goto(x, y)
# 鼠标右键可以移动画笔
def go_somewhere(x,y):
t.penup()
t.goto(x, y)
print(f'画笔跳动到新位置({x},{y})')
t.pendown()
#传入列表时: extend() 可以把两个列表的数据融合到一个中去 而append()则会把一个列表添加到另一个列表 成为子母列表
order_list.extend(['t.penup()', f't.goto({x},{y})', 't.pendown()'])
''' 这也时一种注释的写法 叫做三引号法 首位三个单引号或三个双引号都可以 一致就行
自定义一个画弧线的方法 radian_line 供终端区随时调用 但不一定用的上 细练的话会用上
angle:旋转角度,distance:画笔单次前进距离,n:画笔总共重复旋转和前进几次,x:画笔粗细,**argv:接收画笔颜色参数 例如color='red'
'''
def radian_line(angle, distance, n, x=1, **argv):
t.pensize(x) # 设置画笔粗细
color = argv.get('color') # 从传进来的键值对形式的参数取color键对应的值 如果没传则get结果为None
if color != None: # 如果有颜色参数传进来 则对画笔颜色进行相应设置
t.color(color)
for i in range(n):
t.right(angle) # 向右转angle度 若要向左转传入负数即可
t.forward(distance) # 向前走dis的距离 若要向后走传入负数即可
# 对画笔进行指令控制和执行
order_list = []
import time
time_use=int(time.time())
def ready_draw(end=None):
global time_use
keep_going = True
while keep_going == True:
print('----按回车键可以执行输入的命令,输入break可以终止创作,点击画布右上角可以查看指令集----')
if end != 'break':
order = input('++++例如输入t.goto(10,20),请输入您对画笔的t命令 :')
else:
order = end
try:
if order != 'break':
t_last_pos = t.position()
# eval 函数可以将字符串形式的代码转化为python可执行的代码 但不能处理赋值语句(即带等号的字符)
# eval(order)
# 若要是想连赋值语句也一并执行 则可以用更牛掰的exec 替换一下eval就行 如下行 这内置函数简直帅呆了
exec(order)
t3.penup()
t3.goto(t.position()) # 画笔t3的位置随时跟随t1 主要是为了单独做标记用 写代码时名字要用t3
t3.pendown()
print('====您刚才输入的指令为:', order, '========\n')
order_list.append(order)
else:
keep_going = False
import time
# 两次输入时间差>60秒则调用一次命令保存功能 避免突然断电等意外而丢失已输入过的命令 如果没有意外发生则该文件自动删除
if (int(time.time()) - time_use) > 60:
with open('画笔命令临时记录表.json', 'w', encoding='utf8') as f:
f.write(str(order_list)) # 真有意外发生 以后需要自己动手从文件还原并拼接代码 届时考验你的时候就到了
time_use = int(time.time())
except Exception as e:
print(e)
print(f'!!!!!!刚才输入的-> {order} <-语法有误,请检查后重新输入--!!!!!!!!!!\n')
else :
if len(order_list) != 0:
print('\n本次绘制所有的未撤销指令为:\n {}'.format(order_list), '\n以上是所有输入的未撤销的指令')
import time
import os
dir_name = '画图成果文件夹'
if not os.path.exists(dir_name):
os.mkdir(dir_name)
if not old_file:
x = int(time.time())
with open(f'{dir_name}{os.sep}{draw_name}{x}.py', 'w', encoding='utf8') as f:
f.write('#'+time.asctime())
f.write(
f"# 画笔绘制记录本\nimport turtle \nt = turtle.Pen() # 画笔一 用于画图\n# 基础设置\nt.screen.setup({width}, {height}) # 设置画布的宽高\nt.screen.title('{draw_name}') # 设置画布窗口的标题\n")
for i in order_list:
f.write(i+'\n')
f.write('t.penup()\nt.home()\nt.pendown()\nt.hideturtle()\nturtle.done()')
print(f'本次所有指令已经存入 --{dir_name}{os.sep}{draw_name}{x}.py文档 --之后可打开查看')
try:
os.remove('画笔命令临时记录表.json')
except:
pass
else:
with open(old_file, 'w', encoding='utf8') as f2:
f2.write(''.join(old_file_orders))
for j in order_list:
f2.write(j+'\n')
f2.write('\nt.penup()\nt.home()\nt.pendown()\nt.hideturtle()\nturtle.done()')
print(f'本次所有指令已经新加入 --{old_file}文档 --之后可打开查看')
try :
os.remove('画笔命令临时记录表.json')
except:
pass
else:
print('再见咯~')
show_normal_order() # 调用函数让上面常用命令展示
t.screen.onclick(choose_order, 1) # 监听鼠标左键的点击事件 1是左键 2是滚轮 3 是右键
t.screen.onclick(go_back, 2) # 监听鼠标滚轮的点击 点击后可以撤销之前的操作
t.screen.onclick(go_somewhere, 3) # 监听鼠标右键的点击 点击移动画笔到指定位置
t.ondrag(drag_to) # 监听鼠标右键的拖动 当鼠标拖动画笔箭头进行移动的时候 画笔会跟随鼠标移动
# 用一个新开的线程去负责控制台指令的收集和执行 避免海龟画布卡机 可以理解1个线程就是1台机器
new_thread = threading.Thread(target=ready_draw) # target = 后面跟的是函数的名字 代表用这个线程启动某函数
new_thread.setDaemon(True) # 线程守护模式 主线程结束 则所有子线程也结束 本程序中 海龟画板就是子线程
new_thread.start() # 启动该线程
turtle.done()
素描图片的生成 需要pillow库
from PIL import Image, ImageOps, ImageFilter
import matplotlib.pyplot as plt
img = Image.open("微素描.jpg")
width, height = img.size
#创建画布
_, axes = plt.subplots(1, 5, figsize=(30, 30))
#原图
axes[0].imshow(img)
axes[0].set_title("original")
#转灰度图
img_gray = img.convert("L")
plt.rcParams["image.cmap"] = "gray"
axes[1].imshow(img_gray)
axes[1].set_title("gray")
#反色
img_invert = ImageOps.invert(img_gray)
axes[2].imshow(img_invert)
axes[2].set_title("inverse")
#高斯模糊
img_gaussian = img_invert.filter(ImageFilter.GaussianBlur(5))
axes[3].imshow(img_gaussian)
axes[3].set_title("gaussian")
#颜色减淡
for x in range(width):
for y in range(height):
pos = (x, y)
A = img_gray.getpixel(pos)
B = img_gaussian.getpixel(pos)
img_gray.putpixel(pos, min(int(A+A*B/(255-B)), 255))
axes[4].imshow(img_gray)
axes[4].set_title("sketch")
img_gray.save("素描素材.png")
plt.show()