# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
from PIL import Image, ImageTk
import math
from tkinter import scrolledtext
import json
# 对话页面 说明如何设置robotic griper
# 需要 from tkinter import scrolledtext
class ManualDialog:
def __init__(self, parent, title="吸盘夹具",txt=('line1','line2')):
self.parent = parent
self.top = tk.Toplevel(parent)
self.top.title(title)
self.top.geometry("300x300") # 增加了窗口的宽度和高度
#显示设备或打印介质的“DPI”(Dots Per Inch,每英寸点数)或“PPI”(Pixels Per Inch,每英寸像素数)
# 创建用于输出多行文本的 Text 小部件
# 使用 scrolledtext.ScrolledText 提供内置的滚动条
root = self.top
self.text_output = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=40, height=15)
self.text_output.pack(pady=10)
def add_text_output(text):
# 在输出区域的末尾插入文本
self.text_output.insert(tk.END, text + "\n") # 添加换行符以分隔不同的输出
# 添加一些示例文本输出
# add_text_output("这是第一行输出文本。")
# add_text_output("这是第二行输出文本,它可能更长一些,以展示文本换行效果。")
# add_text_output("这是第三行输出文本。")
for each in txt:
add_text_output(each)
# 你可以选择将 state 设置为 DISABLED 以防止用户编辑
self.text_output.config(state=tk.DISABLED)
# 对话窗口 输入绝对点坐标
class Point2DInputDialog:
def __init__(self, parent, title="Enter absolute 2D point"):
self.parent = parent
self.top = tk.Toplevel(parent)
self.top.title(title)
self.top.geometry("300x300") # 增加了窗口的宽度和高度
#显示设备或打印介质的“DPI”(Dots Per Inch,每英寸点数)或“PPI”(Pixels Per Inch,每英寸像素数)
self.label1 = tk.Label(self.top, text="Enter X Pixels:")
self.label1.pack(pady=10)
self.entry1 = tk.Entry(self.top)
self.entry1.pack(pady=5)
self.label2 = tk.Label(self.top, text="Enter Y Pixels:")
self.label2.pack(pady=5)
self.entry2 = tk.Entry(self.top)
self.entry2.pack(pady=10)
self.label3 = tk.Label(self.top, text="Enter Radius Pixels:")
self.label3.pack(pady=5)
self.entry3 = tk.Entry(self.top)
self.entry3.pack(pady=10)
# 使用 fill=X 和 expand=True 来让按钮水平扩展并填充剩余空间
self.done_button = tk.Button(self.top, text="Done", command=self.done)
self.done_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)
self.clear_button = tk.Button(self.top, text="Clear", command=self.clear)
self.clear_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)
self.cancel_button = tk.Button(self.top, text="Cancel", command=self.cancel)
self.cancel_button.pack(side=tk.RIGHT, padx=10, pady=10, fill=tk.X, expand=True)
self.numbers = [None, None]
def done(self):
num1_str = self.entry1.get().strip()
num2_str = self.entry2.get().strip()
num3_str = self.entry3.get().strip()
num1 = None
num2 = None
num3 = None
try:
num1 = float(num1_str)
except ValueError:
messagebox.showerror("Invalid Input", f"Invalid number for the first entry: {num1_str}")
try:
num2 = float(num2_str)
except ValueError:
messagebox.showerror("Invalid Input", f"Invalid number for the second entry: {num2_str}")
try:
num3 = float(num3_str)
except ValueError:
messagebox.showerror("Invalid Input", f"Invalid number for the second entry: {num2_str}")
if num1 is not None and num2 is not None and num3 is not None:
self.numbers = [num1, num2, num3]
# 对话框关闭后重新显示主窗口
# root.withdraw() # 隐藏主窗口
#self.parent.deiconify()
self.top.destroy()
def clear(self):
self.numbers = [None, None]
self.entry1.delete(0,tk.END)
self.entry2.delete(0,tk.END)
def cancel(self):
self.numbers = [None, None]
self.top.destroy()
def get_numbers(self):
return self.numbers
# 对话窗口 输入相对点坐标
class AngleDistanceInputDialog:
def __init__(self, parent, title="Enter relative 2D point"):
self.parent = parent
self.top = tk.Toplevel(parent)
self.top.title(title)
self.top.geometry("300x200") # 增加了窗口的宽度和高度
#显示设备或打印介质的“DPI”(Dots Per Inch,每英寸点数)或“PPI”(Pixels Per Inch,每英寸像素数)
self.label1 = tk.Label(self.top, text="Enter Angle Degrees:")
self.label1.pack(pady=10)
self.entry1 = tk.Entry(self.top)
self.entry1.pack(pady=5)
self.label2 = tk.Label(self.top, text="Enter Distance Pixels:")
self.label2.pack(pady=5)
self.entry2 = tk.Entry(self.top)
self.entry2.pack(pady=10)
# 使用 fill=X 和 expand=True 来让按钮水平扩展并填充剩余空间
self.done_button = tk.Button(self.top, text="Done", command=self.done)
self.done_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)
self.clear_button = tk.Button(self.top, text="Clear", command=self.clear)
self.clear_button.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.X, expand=True)
self.cancel_button = tk.Button(self.top, text="Cancel", command=self.cancel)
self.cancel_button.pack(side=tk.RIGHT, padx=10, pady=10, fill=tk.X, expand=True)
self.numbers = [None, None]
def done(self):
num1_str = self.entry1.get().strip()
num2_str = self.entry2.get().strip()
num1 = None
num2 = None
try:
num1 = float(num1_str)
except ValueError:
messagebox.showerror("Invalid Input", f"Invalid number for the first entry: {num1_str}")
try:
num2 = float(num2_str)
except ValueError:
messagebox.showerror("Invalid Input", f"Invalid number for the second entry: {num2_str}")
if num1 is not None and num2 is not None:
self.numbers = [num1, num2]
# 对话框关闭后重新显示主窗口
# root.withdraw() # 隐藏主窗口
#self.parent.deiconify()
self.top.destroy()
def clear(self):
self.numbers = [None, None]
self.entry1.delete(0,tk.END)
self.entry2.delete(0,tk.END)
def cancel(self):
self.numbers = [None, None]
self.top.destroy()
def get_numbers(self):
return self.numbers
# 用来机械爪抓取方式
class GripperDialog(tk.Toplevel):
def __init__(self, parent, title="Robotic Gripper Dialog"):
super().__init__(parent)
self.parent = parent
# 设置对话框的标题
self.title(title)
# 设置对话框的大小(可选)
self.geometry("640x500")
# 绑定关闭事件(可选,确保对话框可以通过窗口管理器关闭按钮正确关闭)
self.protocol("WM_DELETE_WINDOW", self.on_close)
# 设定爪方式值
self.json_dict = dict()
# 属性
self.image_path = None # 路径
self.image = None # 图片
self.tk_image = None
self.start_x = None
self.start_y = None
self.angle = None # 与x轴夹角
self.distance = None
# 绘图板控件 放图片
self.canvas = tk.Canvas(self, cursor="cross")
self.canvas.pack(fill=tk.BOTH, expand=True)
self.canvas.bind("<Motion>", self.show_pixel_info)#绑定鼠标移动事件,显示像素信息
# 菜单容器
self.menu = tk.Menu(self) #一级菜单容器
self.config(menu=self.menu)# 放入Toplevel容器中
self.file_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
# 吸盘:Suction Cup
# 两爪夹具:Two-Jaw Clamp(或者 Two-Finger Clamp,具体取决于上下文和使用的领域)
# 三抓夹具:Three-Jaw Chuck(在机床或工具制造中常用)或者 Three-Point Clamp(在更一般的夹持应用中)
self.suction_cup_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
self.two_jaw_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
self.three_jaw_menu = tk.Menu(self.menu, tearoff=0) #二级菜单容器
#一级菜单
self.menu.add_cascade(label="打开图片", menu=self.file_menu)
#一级菜单
self.menu.add_cascade(label="吸盘夹具", menu=self.suction_cup_menu)
#一级菜单
self.menu.add_cascade(label="两爪夹具", menu=self.two_jaw_menu)
#一级菜单
self.menu.add_cascade(label="三爪夹具", menu=self.three_jaw_menu)
#二级菜单 file
self.file_menu.add_command(label="读入图片", command=self.open_image)
self.file_menu.add_separator()
self.file_menu.add_command(label="Exit", command=self.on_close)
#二级菜单 吸盘夹具
self.suction_cup_menu.add_command(label="设置吸盘夹持中心", command=self.suction_cup_center)
self.suction_cup_menu.add_separator()
self.suction_cup_menu.add_command(label="设置零件夹持方向", command=self.suction_cup_direction)
self.suction_cup_menu.add_separator()
self.suction_cup_menu.add_command(label="设置吸盘夹具说明", command=self.suction_cup_manual)
# 二级菜单 两爪夹具
self.two_jaw_menu.add_command(label="设置夹持中心", command=self.two_jaw_center)
self.two_jaw_menu.add_separator()
self.two_jaw_menu.add_command(label="设置第一个夹持点", command=self.two_jaw_1point)
self.two_jaw_menu.add_separator()
self.two_jaw_menu.add_command(label="设置第二个夹持点", command=self.two_jaw_2point)
self.two_jaw_menu.add_separator()
self.two_jaw_menu.add_command(label="设置两爪夹具说明", command=self.two_jaw_manual)
# 二级菜单 三爪夹具
self.three_jaw_menu.add_command(label="设置夹持中心", command=self.three_jaw_center)
self.three_jaw_menu.add_separator()
self.three_jaw_menu.add_command(label="设置第一个夹持点", command=self.three_jaw_1point)
self.three_jaw_menu.add_separator()
self.three_jaw_menu.add_command(label="设置第二个夹持点", command=self.three_jaw_2point)
self.three_jaw_menu.add_separator()
self.three_jaw_menu.add_command(label="设置第三个夹持点", command=self.three_jaw_3point)
self.three_jaw_menu.add_separator()
self.three_jaw_menu.add_command(label="设置三爪夹具说明", command=self.three_jaw_manual)
# 提示标签 底部
self.label_var = tk.StringVar()
self.label = tk.Label(self, textvariable=self.label_var, anchor="w", bg="black", fg="white")
self.label.pack(side=tk.BOTTOM,fill=tk.X)
def on_ok(self):
# 对话框关闭后重新显示主窗口
# root.withdraw() # 隐藏主窗口
self.parent.deiconify()
# 关闭对话框
self.destroy()
def on_close(self):
# 如果没有其他操作需要执行,可以直接调用destroy方法关闭对话框
# 这里只是为了演示如何绑定关闭事件,所以直接调用on_ok方法
# 在实际应用中,你可能需要在这里添加额外的清理代码或确认对话框
self.destroy()
self.parent.deiconify() # 重新显示主窗口
# 将字典转为字符串并输出
def gripper_dict_label(self):
# 将字典转换为 JSON 字符串
#json_string = json.dumps(self.json_dict, indent=4) # indent 参数用于美化输出,使其更易读
json_string = json.dumps(self.json_dict, indent=None, separators=(', ', ':'))
self.label_var.set(json_string[1:-1])
# 根据圆心点,半径绘制圆
def draw_circle(self,center_x = 0,center_y = 0, radius = 0):
# 绘制圆形(椭圆)
self.canvas.create_oval(
center_x - radius, # 左上角x坐标
center_y - radius, # 左上角y坐标
center_x + radius, # 右下角x坐标
center_y + radius, # 右下角y坐标
tags="circle",
outline="blue", # 边框颜色
width=2 # 边框宽度
)
print("绘制夹持中心圆")
# 根据点,绘制直线
def draw_line(self):
if self.start_x is not None and self.start_y is not None:
if self.angle is not None and self.distance is not None:
# 将30度转换为弧度
angle_in_degrees = self.angle
angle_in_radians = math.radians(angle_in_degrees)
# 计算余弦值
cosine_value = math.cos(angle_in_radians)
sine_value = math.sin(angle_in_radians)
end_x = self.distance * cosine_value +self.start_x
end_y = self.distance * sine_value +self.start_y
self.canvas.create_line(self.start_x, self.start_y, end_x, end_y, tags="line",
width=2, capstyle=tk.ROUND, smooth=tk.TRUE,fill="blue")
self.angle = None # 与x轴夹角
self.distance = None
print("绘制夹持点和中心连线")
# 鼠标移动时,显示像素信息
def show_pixel_info(self, event):
if self.tk_image:
x, y = event.x, event.y
try:
pixel = self.image.getpixel((x, y))
self.label_var.set(f"Pixel at ({x}, {y}):RGB {pixel}")
except IndexError:
self.label_var.set(f"Pixel out of bounds at ({x}, {y})")
# 打开图像文件 #二级菜单 file
def open_image(self):
self.image_path = filedialog.askopenfilename(filetypes=[("PNG files", "*.png")])
if self.image_path:
self.image = Image.open(self.image_path)
self.tk_image = ImageTk.PhotoImage(self.image)
self.canvas.config(width=self.image.width, height=self.image.height)
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_image)
#二级菜单 吸盘夹具
def suction_cup_center(self):
# 对话窗口 输入绝对点坐标
dialog = Point2DInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None or input_numbers[2] is None:
print("Input numbers is None")
return
# 设置为直线段的起点
self.start_x,self.start_y, radius = input_numbers
# 保存 机械爪设置值
self.json_dict.clear()
self.json_dict["gripper_type"] = "suction_cup"
self.json_dict["gripper_center"] = input_numbers
self.gripper_dict_label() # 打印机器爪设置值
# 绘制 夹持中心圆
self.draw_circle( center_x=self.start_x, center_y=self.start_y, radius=radius)
#二级菜单 吸盘夹具
def suction_cup_direction(self):
# 对话窗口 输入相对点坐标
dialog = AngleDistanceInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict["gripper_firstPoint"] = input_numbers
# 设置为直线段的相对起点的终点
self.angle, self.distance = input_numbers# 与x轴夹角
self.gripper_dict_label() # 打印机器爪设置值
# 根据点,绘制直线
self.draw_line()
# 二级菜单 吸盘夹具
def suction_cup_manual(self):
txt=("1)打开零件图片,图片上有零件几何中心和外接矩形的尺寸,单位是像素。",
"2)设置夹持中心圆,圆心一般是零件的几何中心,单位是像素,给定半径绘制圆。",
"3)设置夹持方向,通过设置一个相对夹持中心点的点的距离和角度表示方向,角度以X轴为准。",
"4)观察零件图片,检查是否设置成功。")
# 对话窗口 输入绝对点坐标
dialog = ManualDialog(self, title="吸盘夹具", txt=txt)
self.wait_window(dialog.top) # 等待对话框关闭
# 二级菜单 两爪夹具
def two_jaw_center(self):
# 对话窗口 输入绝对点坐标
dialog = Point2DInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None or input_numbers[2] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict.clear()
self.json_dict["gripper_type"] = "two_jaw"
self.json_dict["gripper_center"] = input_numbers
# 设置为直线段的起点
self.start_x, self.start_y, radius = input_numbers
self.gripper_dict_label() # 打印机器爪设置值
# 绘制 夹持中心圆
self.draw_circle(center_x=self.start_x, center_y=self.start_y, radius=radius)
#二级菜单 两爪夹具
def two_jaw_1point(self):
# 对话窗口 输入相对点坐标
dialog = AngleDistanceInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict["gripper_firstPoint"] = input_numbers
# 设置为直线段的相对起点的终点
self.angle, self.distance = input_numbers# 与x轴夹角
self.gripper_dict_label() # 打印机器爪设置值
# 根据点,绘制直线
self.draw_line()
#二级菜单 两爪夹具
def two_jaw_2point(self):
# 对话窗口 输入相对点坐标
dialog = AngleDistanceInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict["gripper_secondPoint"] = input_numbers
# 设置为直线段的相对起点的终点
self.angle, self.distance = input_numbers# 与x轴夹角
self.gripper_dict_label() # 打印机器爪设置值
# 根据点,绘制直线
self.draw_line()
# 二级菜单 两爪夹具
def two_jaw_manual(self):
txt=("1)打开零件图片,图片上有零件几何中心和外接矩形的尺寸,单位是像素。",
"2)设置夹持中心圆,圆心一般是零件的几何中心,单位是像素,给定半径绘制圆。",
"3)设置第1夹持点,即设置第1夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
"4)设置第2夹持点,即设置第2夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
"5)观察零件图片,检查是否设置成功。")
# 对话窗口 输入绝对点坐标
dialog = ManualDialog(self, title="两爪夹具", txt=txt)
self.wait_window(dialog.top) # 等待对话框关闭
# 二级菜单 三爪夹具
def three_jaw_center(self):
# 对话窗口 输入绝对点坐标
dialog = Point2DInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None or input_numbers[2] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict.clear()
self.json_dict["gripper_type"] = "three_jaw"
self.json_dict["gripper_center"] = input_numbers
# 设置为直线段的起点
self.start_x, self.start_y, radius = input_numbers
self.gripper_dict_label() # 打印机器爪设置值
# 绘制 夹持中心圆
self.draw_circle(center_x=self.start_x, center_y=self.start_y, radius=radius)
#二级菜单 三爪夹具
def three_jaw_1point(self):
# 对话窗口 输入相对点坐标
dialog = AngleDistanceInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict["gripper_firstPoint"] = input_numbers
# 设置为直线段的相对起点的终点
self.angle, self.distance = input_numbers# 与x轴夹角
self.gripper_dict_label() # 打印机器爪设置值
# 根据点,绘制直线
self.draw_line()
#二级菜单 三爪夹具
def three_jaw_2point(self):
# 对话窗口 输入相对点坐标
dialog = AngleDistanceInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict["gripper_secondPoint"] = input_numbers
# 设置为直线段的相对起点的终点
self.angle, self.distance = input_numbers# 与x轴夹角
self.gripper_dict_label() # 打印机器爪设置值
# 根据点,绘制直线
self.draw_line()
#二级菜单 三爪夹具
def three_jaw_3point(self):
# 对话窗口 输入相对点坐标
dialog = AngleDistanceInputDialog(self)
self.wait_window(dialog.top) # 等待对话框关闭
input_numbers = dialog.get_numbers()
if input_numbers[0] is None or input_numbers[1] is None:
print("Input numbers is None")
return
# 保存 机械爪设置值
self.json_dict["gripper_thirdPoint"] = input_numbers
# 设置为直线段的相对起点的终点
self.angle, self.distance = input_numbers# 与x轴夹角
self.gripper_dict_label() # 打印机器爪设置值
# 根据点,绘制直线
self.draw_line()
# 二级菜单 三爪夹具
def three_jaw_manual(self):
txt=("1)打开零件图片,图片上有零件几何中心和外接矩形的尺寸,单位是像素。",
"2)设置夹持中心圆,圆心一般是零件的几何中心,单位是像素,给定半径绘制圆。",
"3)设置第1夹持点,即设置第1夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
"4)设置第2夹持点,即设置第2夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
"5)设置第3夹持点,即设置第3夹持点相对夹持中心点的距离和角度。角度以X轴为准。",
"6)观察零件图片,检查是否设置成功。")
# 对话窗口 输入绝对点坐标
dialog = ManualDialog(self, title="三爪夹具", txt=txt)
self.wait_window(dialog.top) # 等待对话框关闭
# 因为使用了菜单,容器只能是 Toplevel 容器 或 主窗口
# 因为使用了菜单,容器只能是 Toplevel 容器 或 主窗口
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.child_window = None
self.title("主窗口")
self.geometry("300x200")
self.button = tk.Button(self, text="打开子窗口", command=self.open_child_window)
self.button.pack(pady=20)
self.withdraw() # 隐藏主窗口,直到点击按钮
self.after(100, self.deiconify) # 延时100毫秒后显示主窗口
def open_child_window(self):
self.withdraw() # 隐藏主窗口
self.child_window = GripperDialog(self)
if __name__ == "__main__":
app = MainWindow()
app.mainloop()