as的Enter_Frame与Timer

As3中的Timer和Event.EnterFrame是有明显的区别的。

Evnet.EnterFrame是定时间隔多少时间出发。如果执行时间比间隔时间长,则会间隔执行时间这么久。

举个例子: Flash的stage.FrameRate设置为 25即,Event.EnterFrame的出发间隔为40ms,如果Event.EnterFrame的执行函数花费时间为25ms,那么Event.EnterFrame将以间隔40ms的恒定频率运行。

Timer就不一样了,Timer的参数就可以看出来,是delay多少时间,意思是当一个函数执行完成后再Delay多久。

举例: Timer 设置为 间隔40ms,而Timer的函数运行花费25ms,那么,Timer将以65ms的间隔恒定运行。

Timer类是ActionScript3.0新增的, 来代替早期的setInterval()和setTimeout()函数。
当创建Timer类的实例时,它会在每个时间间隔激活timer事件,你可以在事件之间指定延时,
然后就有足够的时间去激活Timer构造器了:
var timer:Timer = new Timer(delay, repeatCount);
使用addEventListener来设置一个函数处理这个事件,然后使用timer的start()方法启动或stop()停止它。

使用场合:
1,EnterFrame是恒定的,稳定的,比较适合用来更新渲染画面,如Tween。 但不适合做异步计算和处理, 如果能保证cpu每帧的计算效率在40ms以内,可以把计算放到enterframe时序中,这样可以稳定25fps,当然,这不包括渲染损耗。

2,Timer在100ms的倍数是很稳定的,pc和mac都能稳定,非100的倍数的值会有两个相差不大的值交替出现。

3,EnterFrame在12、20、30fps都比25fps更稳定,25fps会有两个相差不到大的数值交替出现。

4,EnterFrame在计算时优于Timer。

5,Timer更适合使用异步的长周期来改变数据的状态,并不适合短时间持续更新数据,或是渲染画面。

6,EnterFrame和Timer同样受到非活动创口的fps自动降低的影响。

转载于:https://www.cnblogs.com/zilongblog/p/3260533.html

import os import subprocess import platform import tkinter as tk from tkinter import ttk, messagebox, simpledialog, filedialog, Menu from openpyxl import Workbook, load_workbook from openpyxl.styles import Alignment, Font, Border, Side import json import webbrowser import datetime import time EXCEL_FILE = '零件登记.xlsx' PAIR_FILE = '零件配对.json' TITLE_FONT = Font(name='微软雅黑', size=18, bold=True) BODY_FONT = Font(name='微软雅黑', size=11) TITLE_ALIGN = Alignment(horizontal='center', vertical='center') BODY_ALIGN = Alignment(horizontal='center', vertical='center') thin = Side(border_style='thin', color='000000') all_border = Border(top=thin, left=thin, right=thin, bottom=thin) class PairManager: """零件配对管理类""" def __init__(self, master): self.master = master self.master.title("零件配对管理") self.master.geometry("600x400") self.master.resizable(False, False) # 设置为模态窗口,禁用主窗口 self.master.grab_set() self.master.transient(master.master) # 加载现有配对 self.pairs = self.load_pairs() # 创建界面 self.create_widgets() self.update_tree() # 设置窗口居中 self.center_window() def center_window(self): """使窗口居中显示""" self.master.update_idletasks() width = self.master.winfo_width() height = self.master.winfo_height() x = (self.master.winfo_screenwidth() // 2) - (width // 2) y = (self.master.winfo_screenheight() // 2) - (height // 2) self.master.geometry(f'+{x}+{y}') def create_widgets(self): # 主框架 main_frame = ttk.Frame(self.master) main_frame.pack(fill='both', expand=True, padx=10, pady=10) # 使用Treeview替代Listbox tree_frame = ttk.Frame(main_frame) tree_frame.pack(fill='both', expand=True) # 创建带滚动条的Treeview scrollbar = ttk.Scrollbar(tree_frame) scrollbar.pack(side='right', fill='y') self.tree = ttk.Treeview( tree_frame, columns=('零件编号', '零件名称'), show='headings', selectmode='extended', yscrollcommand=scrollbar.set ) self.tree.pack(fill='both', expand=True) scrollbar.config(command=self.tree.yview) # 设置列 self.tree.heading('零件编号', text='零件编号', anchor='center') self.tree.heading('零件名称', text='零件名称', anchor='center') self.tree.column('零件编号', width=150, anchor='center') self.tree.column('零件名称', width=400, anchor='center') # 按钮框架 btn_frame = ttk.Frame(main_frame) btn_frame.pack(fill='x', pady=10) ttk.Button(btn_frame, text="添加", command=self.add_pair).pack(side='left', padx=5) ttk.Button(btn_frame, text="编辑", command=self.edit_pair).pack(side='left', padx=5) ttk.Button(btn_frame, text="删除", command=self.delete_pair).pack(side='left', padx=5) ttk.Button(btn_frame, text="保存并关闭", command=self.save_and_close).pack(side='right', padx=5) def load_pairs(self): """加载零件配对数据""" try: if os.path.exists(PAIR_FILE): with open(PAIR_FILE, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: messagebox.showerror("错误", f"加载配对文件失败: {str(e)}") return {} def save_pairs(self): """保存零件配对数据""" try: with open(PAIR_FILE, 'w', encoding='utf-8') as f: json.dump(self.pairs, f, ensure_ascii=False, indent=2) return True except Exception as e: messagebox.showerror("错误", f"保存配对文件失败: {str(e)}") return False def update_tree(self): """更新Treeview显示""" # 清空现有数据 for item in self.tree.get_children(): self.tree.delete(item) # 添加新数据 for code, name in self.pairs.items(): self.tree.insert('', 'end', values=(code, name)) def add_pair(self): """添加新配对""" dialog = PairDialog(self.master, "添加零件配对") if dialog.result: code, name = dialog.result if code in self.pairs: messagebox.showwarning("警告", f"零件编号 {code} 已存在!") else: self.pairs[code] = name self.update_tree() def edit_pair(self): """编辑现有配对""" selection = self.tree.selection() if not selection: messagebox.showwarning("提示", "请先选择一个配对进行编辑") return # 如果只选了一个,执行单编辑 if len(selection) == 1: item = selection[0] code = self.tree.item(item, 'values')[0] name = self.pairs[code] dialog = PairDialog(self.master, "编辑零件配对", code, name) if dialog.result: new_code, new_name = dialog.result if new_code != code and new_code in self.pairs: messagebox.showwarning("警告", f"零件编号 {new_code} 已存在!") else: if new_code != code: del self.pairs[code] self.pairs[new_code] = new_name self.update_tree() else: # 多选编辑 - 批量编辑名称 selected_codes = [self.tree.item(item, 'values')[0] for item in selection] dialog = BatchEditDialog(self.master, "批量编辑零件名称", selected_codes, self.pairs) if dialog.result: for code in selected_codes: self.pairs[code] = dialog.result self.update_tree() def delete_pair(self): """删除配对""" selection = self.tree.selection() if not selection: messagebox.showwarning("提示", "请先选择一个配对") return selected_codes = [self.tree.item(item, 'values')[0] for item in selection] if messagebox.askyesno("确认", f"确定要删除选中的 {len(selected_codes)} 个配对吗?"): for code in selected_codes: del self.pairs[code] self.update_tree() def save_and_close(self): """保存并关闭窗口""" if self.save_pairs(): self.master.destroy() class PairDialog(simpledialog.Dialog): """零件配对对话框""" def __init__(self, parent, title, code="", name=""): self.initial_code = code self.initial_name = name super().__init__(parent, title) def body(self, frame): ttk.Label(frame, text="零件编号:").grid(row=0, column=0, padx=5, pady=5, sticky='e') self.code_var = tk.StringVar(value=self.initial_code) code_entry = ttk.Entry(frame, textvariable=self.code_var, width=25) code_entry.grid(row=0, column=1, padx=5, pady=5, sticky='we') ttk.Label(frame, text="零件名称:").grid(row=1, column=0, padx=5, pady=5, sticky='e') self.name_var = tk.StringVar(value=self.initial_name) name_entry = ttk.Entry(frame, textvariable=self.name_var, width=25) name_entry.grid(row=1, column=1, padx=5, pady=5, sticky='we') return code_entry # 初始焦点 def validate(self): code = self.code_var.get().strip() name = self.name_var.get().strip() if not code or not name: messagebox.showwarning("输入错误", "零件编号和名称都不能为空") return False return True def apply(self): self.result = (self.code_var.get().strip(), self.name_var.get().strip()) class BatchEditDialog(simpledialog.Dialog): """批量编辑对话框""" def __init__(self, parent, title, codes, pair_dict): self.codes = codes self.pair_dict = pair_dict super().__init__(parent, title) def body(self, frame): ttk.Label(frame, text=f"批量编辑 {len(self.codes)} 个零件").grid(row=0, column=0, columnspan=2, pady=10) # 显示前几个零件名称 preview_text = "\n".join([f"{code}: {self.pair_dict[code]}" for code in self.codes[:3]]) if len(self.codes) > 3: preview_text += f"\n...等 {len(self.codes)} 个零件" preview = tk.Text(frame, height=6, width=40, font=('微软雅黑', 9)) preview.grid(row=1, column=0, columnspan=2, padx=10, pady=5) preview.insert(tk.END, preview_text) preview.config(state=tk.DISABLED) ttk.Label(frame, text="新零件名称:").grid(row=2, column=0, padx=5, pady=10, sticky='e') self.name_var = tk.StringVar() name_entry = ttk.Entry(frame, textvariable=self.name_var, width=25) name_entry.grid(row=2, column=1, padx=5, pady=10, sticky='we') return name_entry def validate(self): name = self.name_var.get().strip() if not name: messagebox.showwarning("输入错误", "零件名称不能为空") return False return True def apply(self): self.result = self.name_var.get().strip() class HelpDialog: """帮助对话框""" def __init__(self, parent): self.parent = parent self.window = tk.Toplevel(parent) self.window.title("使用帮助") self.window.geometry("600x450") self.window.resizable(False, False) # 设置为模态窗口,禁用主窗口 self.window.grab_set() self.window.transient(parent) # 创建标签页 self.notebook = ttk.Notebook(self.window) self.notebook.pack(fill='both', expand=True, padx=10, pady=10) # 基本操作标签 basic_frame = ttk.Frame(self.notebook) self.notebook.add(basic_frame, text="基本操作") self.create_basic_tab(basic_frame) # 快捷键标签 shortcut_frame = ttk.Frame(self.notebook) self.notebook.add(shortcut_frame, text="快捷键") self.create_shortcut_tab(shortcut_frame) # 零件管理标签 pair_frame = ttk.Frame(self.notebook) self.notebook.add(pair_frame, text="零件管理") self.create_pair_tab(pair_frame) # 底部按钮 btn_frame = ttk.Frame(self.window) btn_frame.pack(fill='x', padx=10, pady=10) ttk.Button(btn_frame, text="关闭", command=self.window.destroy).pack(side='right') self.center_window() def center_window(self): """使窗口居中显示""" self.window.update_idletasks() width = self.window.winfo_width() height = self.window.winfo_height() x = (self.window.winfo_screenwidth() // 2) - (width // 2) y = (self.window.winfo_screenheight() // 2) - (height // 2) self.window.geometry(f'+{x}+{y}') def create_basic_tab(self, frame): content = """ 1. 添加零件 - 在左侧输入零件编号、名称和数量 - 点击【添加并写入 Excel】按钮或按 Ctrl+Enter - 零件信息将添加到右侧列表并保存到Excel 2. 编辑零件 - 在右侧列表右键点击零件 - 选择【编辑】修改单个零件 - 选择【批量编辑数量】修改多个零件数量 3. 删除零件 - 在右侧列表右键点击零件 - 选择【删除】删除选中零件 - 或按 Delete 键删除 4. 加载Excel - 点击【加载Excel】按钮导入已有数据 - 支持覆盖当前数据或追加数据 5. 打开Excel - 点击【打开 Excel】查看生成的Excel文件 """ text = tk.Text(frame, wrap=tk.WORD, font=('微软雅黑', 10)) text.pack(fill='both', expand=True, padx=10, pady=10) text.insert(tk.END, content.strip()) text.config(state=tk.DISABLED) def create_shortcut_tab(self, frame): content = """ 常用快捷键列表: Ctrl + Enter : 添加当前零件并写入Excel Delete : 删除选中的零件 Ctrl + E : 打开零件配对管理 Ctrl + O : 打开Excel文件 Ctrl + L : 加载Excel数据 Ctrl + H : 打开帮助文档 """ text = tk.Text(frame, wrap=tk.WORD, font=('微软雅黑', 10)) text.pack(fill='both', expand=True, padx=10, pady=10) text.insert(tk.END, content.strip()) text.config(state=tk.DISABLED) def create_pair_tab(self, frame): content = """ 零件配对管理: 1. 添加配对 - 点击【添加】按钮创建新零件配对 - 输入零件编号和名称 2. 编辑配对 - 选择单个配对点击【编辑】修改 - 选择多个配对点击【编辑】批量修改名称 3. 删除配对 - 选择单个或多个配对 - 点击【删除】按钮删除 4. 自动填充 - 在主界面输入零件编号时自动填充名称 - 输入零件名称时自动填充编号 """ text = tk.Text(frame, wrap=tk.WORD, font=('微软雅黑', 10)) text.pack(fill='both', expand=True, padx=10, pady=10) text.insert(tk.END, content.strip()) text.config(state=tk.DISABLED) class LogDialog: """日志对话框(只包含操作日志)""" def __init__(self, parent, logs): self.parent = parent self.logs = logs self.window = tk.Toplevel(parent) self.window.title("操作日志") self.window.geometry("800x500") self.window.resizable(True, True) # 设置为模态窗口,禁用主窗口 self.window.grab_set() self.window.transient(parent) # 创建框架 frame = ttk.Frame(self.window) frame.pack(fill='both', expand=True, padx=10, pady=10) # 创建带滚动条的文本框 scrollbar = ttk.Scrollbar(frame) scrollbar.pack(side='right', fill='y') self.log_text = tk.Text(frame, wrap=tk.WORD, font=('微软雅黑', 10), yscrollcommand=scrollbar.set) self.log_text.pack(fill='both', expand=True, padx=10, pady=10) scrollbar.config(command=self.log_text.yview) # 添加日志内容 for log in self.logs: self.log_text.insert(tk.END, log + "\n") self.log_text.config(state=tk.DISABLED) # 底部按钮 btn_frame = ttk.Frame(self.window) btn_frame.pack(fill='x', padx=10, pady=10) ttk.Button(btn_frame, text="关闭", command=self.window.destroy).pack(side='right') self.center_window() def center_window(self): """使窗口居中显示""" self.window.update_idletasks() width = self.window.winfo_width() height = self.window.winfo_height() x = (self.window.winfo_screenwidth() // 2) - (width // 2) y = (self.window.winfo_screenheight() // 2) - (height // 2) self.window.geometry(f'+{x}+{y}') class PartRegApp: def __init__(self, root): self.root = root root.title("零件登记工具") root.geometry("950x580") root.resizable(False, False) # 创建菜单栏 self.create_menu() # 加载零件配对数据 self.pair_dict = self.load_pair_data() # 初始化日志 self.logs = [] self.log("程序启动") # ---------- 顶部:设备型号 ---------- top_frame = ttk.Frame(root) top_frame.pack(fill='x', padx=5, pady=5) ttk.Label(top_frame, text="设备型号:").pack(side='left') self.device_model = tk.StringVar(value='DS-9C') ttk.Entry(top_frame, textvariable=self.device_model, width=15).pack(side='left', padx=5) # ---------- 主体 ---------- paned = ttk.PanedWindow(root, orient='horizontal') paned.pack(fill='both', expand=True, padx=5, pady=5) # 左侧 left = ttk.Frame(paned, width=300) paned.add(left) left.pack_propagate(False) # 零件信息输入框 frm = ttk.LabelFrame(left, text="零件信息", padding=10) frm.pack(fill='x') ttk.Label(frm, text="零件编号:").pack(anchor='w') self.code_var = tk.StringVar() # 使用Combobox替代Entry self.code_combo = ttk.Combobox(frm, textvariable=self.code_var, width=20) self.code_combo.pack(fill='x', pady=2) self.code_combo.bind('<KeyRelease>', self.on_code_keyrelease) self.code_combo.bind('<<ComboboxSelected>>', self.on_code_selected) # 绑定变量变化事件 self.code_var.trace_add('write', self.on_code_var_changed) self.code_timer = None # 用于延迟检索的计时器 ttk.Label(frm, text="零件名称:").pack(anchor='w', pady=(8, 0)) self.name_var = tk.StringVar() # 使用Combobox替代Entry self.name_combo = ttk.Combobox(frm, textvariable=self.name_var, width=20) self.name_combo.pack(fill='x', pady=2) self.name_combo.bind('<KeyRelease>', self.on_name_keyrelease) self.name_combo.bind('<<ComboboxSelected>>', self.on_name_selected) # 绑定变量变化事件 self.name_var.trace_add('write', self.on_name_var_changed) self.name_timer = None # 用于延迟检索的计时器 ttk.Label(frm, text="数量:").pack(anchor='w', pady=(8, 0)) self.qty_var = tk.StringVar() qty_entry = ttk.Entry(frm, textvariable=self.qty_var) qty_entry.pack(fill='x', pady=2) # 添加快捷键绑定 qty_entry.bind('<Return>', lambda event: self.add_and_write()) qty_entry.bind('<KP_Enter>', lambda event: self.add_and_write()) # 小键盘回车 btn = ttk.Button(frm, text="添加并写入 Excel (Ctrl+Enter)", command=self.add_and_write) btn.pack(pady=(15, 0)) # 操作按钮 - 垂直排列 btn_frame = ttk.Frame(left) btn_frame.pack(fill='x', pady=(10, 5)) # 按钮垂直排列 ttk.Button(btn_frame, text="加载Excel (Ctrl+L)", command=self.load_excel).pack(side='top', fill='x', padx=5, pady=2) ttk.Button(btn_frame, text="打开 Excel (Ctrl+O)", command=self.open_excel).pack(side='top', fill='x', padx=5, pady=2) ttk.Button(btn_frame, text="零件配对管理 (Ctrl+E)", command=self.open_pair_manager).pack(side='top', fill='x', padx=5, pady=2) # 右侧预览 right = ttk.Frame(paned) paned.add(right) self.tree = ttk.Treeview(right, columns=('零件编号', '零件名称', '数量'), show='headings', height=22) self.tree.pack(fill='both', expand=True) for col in ('零件编号', '零件名称', '数量'): self.tree.heading(col, text=col) self.tree.column(col, anchor='center', stretch=True) # 添加滚动条 scrollbar = ttk.Scrollbar(right, orient="vertical", command=self.tree.yview) scrollbar.pack(side='right', fill='y') self.tree.configure(yscrollcommand=scrollbar.set) self.tree.bind('<Button-3>', self.show_menu) self.menu = tk.Menu(root, tearoff=0) self.menu.add_command(label="编辑", command=self.edit_selected) self.menu.add_command(label="批量编辑数量", command=self.batch_edit_qty) self.menu.add_command(label="删除", command=self.delete_selected) # 状态栏 status_frame = ttk.Frame(root) status_frame.pack(side='bottom', fill='x') # 左侧状态信息 self.status = tk.StringVar(value="就绪") status_bar = ttk.Label(status_frame, textvariable=self.status, relief='sunken', anchor='w', padding=(5, 2)) status_bar.pack(side='left', fill='x', expand=True) # 右侧时间和公司信息 self.time_var = tk.StringVar() time_label = ttk.Label(status_frame, textvariable=self.time_var, relief='sunken', anchor='e', padding=(5, 2)) time_label.pack(side='right', fill='x') # 更新时间显示 self.update_time() self.data = [] self.updating = False # 防止自动填充时触发循环事件 self.cached_codes = list(self.pair_dict.keys()) self.cached_names = list(self.pair_dict.values()) # 绑定全局快捷键 self.root.bind('<Control-Return>', lambda event: self.add_and_write()) self.root.bind('<Control-o>', lambda event: self.open_excel()) self.root.bind('<Control-O>', lambda event: self.open_excel()) self.root.bind('<Control-l>', lambda event: self.load_excel()) self.root.bind('<Control-L>', lambda event: self.load_excel()) self.root.bind('<Control-e>', lambda event: self.open_pair_manager()) self.root.bind('<Control-E>', lambda event: self.open_pair_manager()) self.root.bind('<Control-h>', lambda event: self.show_help()) self.root.bind('<Control-H>', lambda event: self.show_help()) self.root.bind('<Delete>', lambda event: self.delete_selected()) self.root.bind('<KP_Delete>', lambda event: self.delete_selected()) # 小键盘Delete # 初始焦点 self.code_combo.focus_set() def create_menu(self): """创建菜单栏""" menubar = Menu(self.root) self.root.config(menu=menubar) # 文件菜单 file_menu = Menu(menubar, tearoff=0) file_menu.add_command(label="打开Excel (Ctrl+O)", command=self.open_excel) file_menu.add_command(label="加载Excel (Ctrl+L)", command=self.load_excel) file_menu.add_separator() file_menu.add_command(label="退出", command=self.root.quit) menubar.add_cascade(label="文件", menu=file_menu) # 日志菜单 log_menu = Menu(menubar, tearoff=0) log_menu.add_command(label="操作日志", command=lambda: self.show_logs()) menubar.add_cascade(label="日志", menu=log_menu) # 帮助菜单 help_menu = Menu(menubar, tearoff=0) help_menu.add_command(label="使用帮助 (Ctrl+H)", command=self.show_help) help_menu.add_command(label="关于", command=self.show_about) menubar.add_cascade(label="帮助", menu=help_menu) def log(self, message): """记录日志""" timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") log_entry = f"[{timestamp}] {message}" self.logs.append(log_entry) # 保持日志数量不超过100条 if len(self.logs) > 100: self.logs.pop(0) def show_logs(self): """显示日志对话框""" LogDialog(self.root, self.logs) def load_pair_data(self): """加载零件配对数据""" if os.path.exists(PAIR_FILE): try: with open(PAIR_FILE, 'r', encoding='utf-8') as f: return json.load(f) except: return {} return {} # ---------- 自动补全功能(优化版)---------- def filter_items(self, items, pattern): """根据输入模式过滤项目""" if not pattern: return items return [item for item in items if pattern.lower() in item.lower()] def on_code_keyrelease(self, event): """零件编号输入框按键释放事件(优化版)""" # 忽略方向键和回车键 if event.keysym in ('Up', 'Down', 'Return', 'Escape'): return # 取消之前的定时器(如果存在) if self.code_timer: self.root.after_cancel(self.code_timer) # 设置新的定时器(900毫秒后执行) self.code_timer = self.root.after(900, self.process_code_input) def process_code_input(self): """处理零件编号输入(延迟执行)""" code = self.code_var.get().strip() # 过滤并更新下拉列表 filtered_codes = self.filter_items(self.cached_codes, code) self.code_combo['values'] = filtered_codes # 如果输入框不为空,显示下拉列表 if code: self.code_combo.event_generate('<Down>') # 自动填充零件名称 if not self.updating and code in self.pair_dict: self.updating = True self.name_var.set(self.pair_dict[code]) self.updating = False def on_name_keyrelease(self, event): """零件名称输入框按键释放事件(优化版)""" # 忽略方向键和回车键 if event.keysym in ('Up', 'Down', 'Return', 'Escape'): return # 取消之前的定时器(如果存在) if self.name_timer: self.root.after_cancel(self.name_timer) # 设置新的定时器(500毫秒后执行) self.name_timer = self.root.after(500, self.process_name_input) def process_name_input(self): """处理零件名称输入(延迟执行)""" name = self.name_var.get().strip() # 过滤并更新下拉列表 filtered_names = self.filter_items(self.cached_names, name) self.name_combo['values'] = filtered_names # 如果输入框不为空,显示下拉列表 if name: self.name_combo.event_generate('<Down>') # 自动填充零件编号 if not self.updating: for code, n in self.pair_dict.items(): if n == name: self.updating = True self.code_var.set(code) self.updating = False break def on_code_selected(self, event): """零件编号下拉选项选择事件""" code = self.code_var.get().strip() if code in self.pair_dict: self.name_var.set(self.pair_dict[code]) def on_name_selected(self, event): """零件名称下拉选项选择事件""" name = self.name_var.get().strip() for code, n in self.pair_dict.items(): if n == name: self.code_var.set(code) break # ---------- 输入框变化事件 ---------- def on_code_var_changed(self, *args): """零件编号变化时的事件处理""" if self.updating: return code = self.code_var.get().strip() # 如果零件编号被清空,则同时清空零件名称 if not code: self.name_var.set('') def on_name_var_changed(self, *args): """零件名称变化时的事件处理""" if self.updating: return name = self.name_var.get().strip() # 如果零件名称被清空,则同时清空零件编号 if not name: self.code_var.set('') # ---------- 配对管理 ---------- def open_pair_manager(self): """打开零件配对管理窗口""" manager_window = tk.Toplevel(self.root) PairManager(manager_window) # 当管理窗口关闭后重新加载配对数据 manager_window.wait_window() self.pair_dict = self.load_pair_data() # 更新缓存 self.cached_codes = list(self.pair_dict.keys()) self.cached_names = list(self.pair_dict.values()) # 更新下拉列表 self.code_combo['values'] = self.cached_codes self.name_combo['values'] = self.cached_names self.log("零件配对数据已更新") # ---------- 工具 ---------- def set_status(self, msg): self.status.set(msg) self.root.update_idletasks() def update_time(self): """更新时间显示""" current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.time_var.set(f"广州图森机械设备有限公司 | {current_time}") self.root.after(1000, self.update_time) # 每秒更新一次 def add_and_write(self): code = self.code_var.get().strip() name = self.name_var.get().strip() qty_str = self.qty_var.get().strip() if not all([code, name, qty_str]): messagebox.showwarning("提示", "请完整填写零件信息!") return try: qty = int(qty_str) except ValueError: messagebox.showwarning("提示", "数量必须是整数!") return # 检查是否已存在相同零件 existing_index = None for i, item in enumerate(self.data): if item[0] == code and item[1] == name: existing_index = i break if existing_index is not None: # 更新现有零件的数量 old_qty = self.data[existing_index][2] new_qty = old_qty + qty self.data[existing_index] = (code, name, new_qty) self.set_status(f"已更新:{code} 数量从 {old_qty} 增加到 {new_qty}") self.log(f"更新零件: {code} ({name}) 数量 {old_qty} → {new_qty}") else: # 添加新零件 self.data.append((code, name, qty)) self.set_status(f"已添加:{code} × {qty}") self.log(f"添加零件: {code} ({name}) × {qty}") self.refresh_tree() self.write_excel() self.clear_inputs() def clear_inputs(self): self.code_var.set('') self.name_var.set('') self.qty_var.set('') self.code_combo.focus_set() # 焦点回到零件编号输入框 def refresh_tree(self): for item in self.tree.get_children(): self.tree.delete(item) for row in self.data: self.tree.insert('', 'end', values=row) def write_excel(self): wb = Workbook() ws = wb.active ws.title = "零件登记" # 标题 title = self.device_model.get().strip() or "零件登记" ws.merge_cells('A1:C1') ws['A1'] = title ws['A1'].font = TITLE_FONT ws['A1'].alignment = TITLE_ALIGN ws.row_dimensions[1].height = 45 * 0.75 # 表头 headers = ['零件编号', '零件名称', '数量'] for idx, h in enumerate(headers, 1): cell = ws.cell(row=2, column=idx, value=h) cell.font = BODY_FONT cell.alignment = BODY_ALIGN cell.border = all_border # 数据(数量列写入为数字) for r, (code, name, qty) in enumerate(self.data, 3): ws.cell(row=r, column=1, value=code).font = BODY_FONT ws.cell(row=r, column=2, value=name).font = BODY_FONT ws.cell(row=r, column=3, value=qty).font = BODY_FONT for c in range(1, 4): ws.cell(row=r, column=c).alignment = BODY_ALIGN ws.cell(row=r, column=c).border = all_border # 固定列宽 ws.column_dimensions['A'].width = 15 ws.column_dimensions['B'].width = 20 ws.column_dimensions['C'].width = 10 try: wb.save(EXCEL_FILE) self.log(f"Excel文件已保存: {EXCEL_FILE}") except PermissionError: messagebox.showerror("错误", "文件被占用,请关闭后重试!") self.log("保存Excel失败: 文件被占用") def open_excel(self): if not os.path.isfile(EXCEL_FILE): messagebox.showwarning("提示", f"未找到 {EXCEL_FILE}\n请先添加数据生成文件。") return system = platform.system() try: if system == 'Windows': os.startfile(EXCEL_FILE) elif system == 'Darwin': subprocess.run(['open', EXCEL_FILE]) else: subprocess.run(['xdg-open', EXCEL_FILE]) self.set_status(f"已打开 Excel:{EXCEL_FILE}") self.log(f"打开Excel文件: {EXCEL_FILE}") except Exception as e: messagebox.showerror("错误", f"无法打开文件:\n{e}") self.log(f"打开Excel失败: {str(e)}") # ---------- 加载Excel文件 ---------- def load_excel(self): """加载已有的Excel文件到预览区""" # 询问用户加载方式 if self.data: choice = messagebox.askquestion("加载选项", "请选择加载方式:\n\n'是' - 覆盖当前数据\n'否' - 追加到当前数据\n'取消' - 中止操作", icon='question', type='yesnocancel') if choice == 'cancel': return else: choice = 'yes' # 没有数据时默认覆盖 # 弹出文件选择对话框 file_path = filedialog.askopenfilename( title="选择零件登记表", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")] ) if not file_path: return # 用户取消选择 try: # 加载工作簿 wb = load_workbook(file_path) ws = wb.active # 如果是追加模式,保留当前数据 if choice == 'no': new_data = self.data.copy() self.log(f"开始追加Excel数据: {file_path}") else: # 覆盖模式 - 清空当前数据 new_data = [] # 读取标题(设备型号) if ws['A1'].value: self.device_model.set(ws['A1'].value) self.log(f"开始覆盖加载Excel数据: {file_path}") # 读取数据行(从第三行开始) for row in ws.iter_rows(min_row=3, values_only=True): # 跳过空行 if not any(row[:3]): continue # 确保数据格式正确 code = str(row[0]) if row[0] else "" name = str(row[1]) if row[1] else "" qty = row[2] if row[2] is not None else 0 # 如果是数字类型,直接使用 if isinstance(qty, (int, float)): qty = int(qty) else: try: qty = int(qty) except: qty = 0 # 添加到数据列表 new_data.append((code, name, qty)) # 更新数据 self.data = new_data # 刷新Treeview self.refresh_tree() # 更新状态 self.set_status(f"已加载文件: {os.path.basename(file_path)},共 {len(self.data)} 条记录") # 保存到默认位置 self.write_excel() self.log(f"成功加载Excel: {file_path},共 {len(self.data)} 条记录") except Exception as e: messagebox.showerror("加载错误", f"无法加载Excel文件:\n{str(e)}") self.log(f"加载Excel失败: {str(e)}") # ---------- 右键菜单功能 ---------- def show_menu(self, event): if self.tree.identify_row(event.y): self.menu.post(event.x_root, event.y_root) def edit_selected(self): """编辑单个选中的零件""" selected = self.tree.selection() if not selected or len(selected) > 1: return item = selected[0] values = self.tree.item(item, 'values') idx = self.tree.index(item) # 创建编辑对话框 dialog = tk.Toplevel(self.root) dialog.title("编辑零件") dialog.geometry("300x200") dialog.resizable(False, False) # 设置为模态窗口,禁用主窗口 dialog.grab_set() dialog.transient(self.root) # 零件编号 ttk.Label(dialog, text="零件编号:").place(x=20, y=20) code_var = tk.StringVar(value=values[0]) code_entry = ttk.Entry(dialog, textvariable=code_var, width=20) code_entry.place(x=100, y=20) # 零件名称 ttk.Label(dialog, text="零件名称:").place(x=20, y=60) name_var = tk.StringVar(value=values[1]) name_entry = ttk.Entry(dialog, textvariable=name_var, width=20) name_entry.place(x=100, y=60) # 数量 ttk.Label(dialog, text="数量:").place(x=20, y=100) qty_var = tk.StringVar(value=values[2]) qty_entry = ttk.Entry(dialog, textvariable=qty_var, width=10) qty_entry.place(x=100, y=100) def save_changes(): code = code_var.get().strip() name = name_var.get().strip() qty_str = qty_var.get().strip() if not all([code, name, qty_str]): messagebox.showwarning("输入错误", "所有字段都必须填写!") return try: qty = int(qty_str) except ValueError: messagebox.showwarning("输入错误", "数量必须是整数!") return # 记录原始值 original_code, original_name, original_qty = self.data[idx] # 更新数据 self.data[idx] = (code, name, qty) self.refresh_tree() self.write_excel() self.set_status(f"已更新: {code}") self.log(f"编辑零件: {original_code}({original_name})×{original_qty} → {code}({name})×{qty}") dialog.destroy() ttk.Button(dialog, text="保存", command=save_changes).place(x=120, y=140) ttk.Button(dialog, text="取消", command=dialog.destroy).place(x=200, y=140) # 居中对话框 dialog.update_idletasks() width = dialog.winfo_width() height = dialog.winfo_height() x = (dialog.winfo_screenwidth() // 2) - (width // 2) y = (dialog.winfo_screenheight() // 2) - (height // 2) dialog.geometry(f'+{x}+{y}') code_entry.focus_set() def batch_edit_qty(self): """批量编辑选中零件的数量""" selected = self.tree.selection() if not selected: return # 创建批量编辑对话框 dialog = tk.Toplevel(self.root) dialog.title("批量编辑数量") dialog.geometry("300x150") dialog.resizable(False, False) # 设置为模态窗口,禁用主窗口 dialog.grab_set() dialog.transient(self.root) # 显示选中零件数量 ttk.Label(dialog, text=f"选中 {len(selected)} 个零件").pack(pady=10) # 新数量输入框 ttk.Label(dialog, text="新数量:").pack() qty_var = tk.StringVar() qty_entry = ttk.Entry(dialog, textvariable=qty_var, width=10) qty_entry.pack() def apply_changes(): qty_str = qty_var.get().strip() if not qty_str: messagebox.showwarning("输入错误", "数量不能为空!") return try: new_qty = int(qty_str) except ValueError: messagebox.showwarning("输入错误", "数量必须是整数!") return # 更新所有选中零件的数量 for item in selected: idx = self.tree.index(item) code, name, old_qty = self.data[idx] self.data[idx] = (code, name, new_qty) self.log(f"批量更新: {code}({name}) 数量 {old_qty} → {new_qty}") self.refresh_tree() self.write_excel() self.set_status(f"已批量更新 {len(selected)} 个零件的数量为 {new_qty}") dialog.destroy() btn_frame = ttk.Frame(dialog) btn_frame.pack(pady=10) ttk.Button(btn_frame, text="应用", command=apply_changes).pack(side='left', padx=10) ttk.Button(btn_frame, text="取消", command=dialog.destroy).pack(side='left', padx=10) # 居中对话框 dialog.update_idletasks() width = dialog.winfo_width() height = dialog.winfo_height() x = (dialog.winfo_screenwidth() // 2) - (width // 2) y = (dialog.winfo_screenheight() // 2) - (height // 2) dialog.geometry(f'+{x}+{y}') qty_entry.focus_set() def delete_selected(self): """删除选中的零件""" selected = self.tree.selection() if not selected: return # 获取所有要删除的零件信息 to_delete = [] for item in selected: idx = self.tree.index(item) to_delete.append(self.data[idx]) if not messagebox.askyesno("确认", f"确定要删除选中的 {len(selected)} 个零件吗?"): return # 从后往前删除,避免索引变化问题 for item in sorted(selected, reverse=True): idx = self.tree.index(item) deleted_part = self.data.pop(idx) self.tree.delete(item) self.log(f"删除零件: {deleted_part[0]}({deleted_part[1]}) × {deleted_part[2]}") # 更新状态 if to_delete: deleted_codes = [item[0] for item in to_delete] if len(deleted_codes) > 3: codes_str = f"{', '.join(deleted_codes[:3])} 等共 {len(deleted_codes)} 个零件" else: codes_str = ", ".join(deleted_codes) self.set_status(f"已删除: {codes_str}") self.write_excel() # ---------- 帮助功能 ---------- def show_help(self): """显示帮助对话框""" HelpDialog(self.root) self.log("打开帮助文档") def show_about(self): """显示关于对话框""" about = tk.Toplevel(self.root) about.title("关于") about.geometry("360x250") about.resizable(False, False) # 设置为模态窗口,禁用主窗口 about.grab_set() about.transient(self.root) # 主框架 main_frame = ttk.Frame(about) main_frame.pack(fill='both', expand=True, padx=20, pady=20) # 公司名称 ttk.Label(main_frame, text="广州图森机械设备有限公司", font=('微软雅黑', 14, 'bold')).pack(pady=(10, 5)) # 软件名称和版本 ttk.Label(main_frame, text="零件登记工具", font=('微软雅黑', 12)).pack(pady=5) ttk.Label(main_frame, text="版本: 2.0", font=('微软雅黑', 10)).pack() # 分隔线 ttk.Separator(main_frame, orient='horizontal').pack(fill='x', pady=10) # 网站链接 website_frame = ttk.Frame(main_frame) website_frame.pack(pady=5) ttk.Label(website_frame, text="公司网站:", font=('微软雅黑', 9)).pack(side='left') # 创建可点击的链接 link = ttk.Label(website_frame, text="www.tusen.cn", font=('微软雅黑', 9, 'underline'), foreground="blue", cursor="hand2") link.pack(side='left', padx=5) link.bind("<Button-1>", lambda e: webbrowser.open("http://www.tusen.cn/")) # 版权信息 ttk.Label(main_frame, text="© 2025 广州图森机械设备有限公司", font=('微软雅黑', 9)).pack(side='bottom', pady=10) # 居中窗口 about.update_idletasks() width = about.winfo_width() height = about.winfo_height() x = (about.winfo_screenwidth() // 2) - (width // 2) y = (about.winfo_screenheight() // 2) - (height // 2) about.geometry(f'+{x}+{y}') # ---------- 其他功能 ---------- def clear_data(self): """清空当前数据""" if not self.data: return if messagebox.askyesno("确认", "确定要清空所有零件数据吗?"): self.log(f"清空所有零件数据,共 {len(self.data)} 条记录") self.data = [] self.refresh_tree() self.write_excel() self.set_status("已清空所有数据") # ---------------- 运行 ---------------- if __name__ == '__main__': root = tk.Tk() app = PartRegApp(root) root.mainloop() 这是我写的一个工具,现在的功能基本已经完善好了,你再帮我分析看下,我这个工具还需要哪些功能呢?或者哪些地方还需要优化呢?给我个建议看看
08-15
注释一下这段代码: # 机器人视觉竞技 A from sdk.data_layer.arm import arm_action_factory as arm_action from sdk.api import UpAPI from sdk.model import YoloModel from sdk.logic_layer.cross_planner import CrossLocator from sdk.logic_layer.pid import PIDController from sdk.logic_layer.time_meter import TimeMeter from enum import Enum, auto import time class MainState(Enum): """ 一级状态机:主状态(行动状态) """ IDLE = auto() TRANSITION = auto() LOCATION = auto() RELOCATION = auto() RECOGNITION = auto() LINE = auto() HOME = auto() FINISH = auto() class TargetState(Enum): """ 一级状态机:目标状态(行动区域) """ APRIL_TAG = auto() GESTURE = auto() YOLO = auto() FACE = auto() BACK_HOME = auto() class TransitionState(Enum): """ 二级状态机:转场状态 """ EXIT_CROSS = auto() # 驶出十字 MOVE_FORWARD = auto() # 在白色区域中行驶 SPAN = auto() # 旋转 class RelocationState(Enum): """ 二级状态机:重定位状态 """ LONG = auto() # 长距离后退,YOLO 和人脸检测区域使用 SHORT = auto() # 短距离后退,其余区域使用 COMPLETE = auto() # 重定位完成 class RecognitionState(Enum): """ 二级状态机:识别状态 """ TURN_LEFT = auto() # 仅有 YOLO 检测之前使用 PREPARE = auto() AIM = auto() EXECUTE = auto() class SpanState(Enum): """ 三级状态机:转场的旋转状态 """ FORWARD = auto() BACKWARD = auto() LEFT = auto() RIGHT = auto() class Controller: def __init__(self): # 参数设置 self.grayscale_threshold = 3240 # 灰度传感器检测阈值 self.speed_follow_line = 10 # 巡线前进移动速度 self.speed_move_in_white = 8 # 在白色区域前进移动速度 self.speed_hit_position = 14 # 进入退出击打位置的速度 self.speed_locate_move = 4 # 定位移动速度 self.speed_locate_turn = 20 # 定位旋转速度 self.speed_spin = 100 # 自旋速度 self.speed_aim = 4 # 瞄准移动速度 self.time_init = 1000 # 初始化时间,单位毫秒 self.time_left_turn = 1900 # 向左转时间,单位毫秒 self.time_right_turn = 1680 # 向右转时间,单位毫秒 self.time_backward_turn = 3580 # 向后转时间,单位毫秒 self.time_arm_action = 2500 # 手臂做动作时间,单位毫秒 self.time_enter_home = 1500 # 巡线结束到停车之间的时间,单位毫秒 self.time_hit_position = 1000 # 进入退出击打状态的移动时间,单位毫秒 self.time_back_short = 800 # 重定位短距离后退,单位毫秒 self.time_back_long = 1900 # 重定位长距离后退,单位毫秒 self.k_p = 16 # 巡线比例参数 self.k_i = 0.01 # 巡线积分参数 self.k_d = 170 # 巡线微分参数 self.target_face = "t_0" # 人脸识别标签 self.target_yolo = "tank" # 车辆识别标签 self.target_id = 3 # April Tag 识别 ID (实际是 1 号) self.target_number_right = 0 # 手势识别数字,举右手 self.target_number_both = 5 # 手势识别数字,举右手 self.target_center_offset = 35 # 目标中心屏幕中心偏移量,单位像素 # YOLO 目标参数 self.yolo_model = YoloModel.VEHICLE # 手臂动作 self.left_arm_actions = { "clamp": arm_action.left_arm_clamp(), "up": arm_action.left_arm_raise() } self.right_arm_actions = { "clamp": arm_action.right_arm_clamp(), "up": arm_action.right_arm_raise(), "pre_hit": arm_action.right_arm_prepare_beat(), "hit": arm_action.right_arm_beat() } # 状态机 self.state_main = MainState.IDLE self.state_target = TargetState.APRIL_TAG self.state_transition = TransitionState.EXIT_CROSS self.state_relocation = RelocationState.COMPLETE self.state_recognition = RecognitionState.PREPARE self.state_span = SpanState.FORWARD # 传感器和执行器 self.api = UpAPI(yolo_model=self.yolo_model, grayscale_threshold=self.grayscale_threshold) # 逻辑处理器 self.locator = CrossLocator() self.pid = PIDController(k_p=self.k_p, k_i=self.k_i, k_d=self.k_d) # 计时器 self.initializer = TimeMeter(self.time_init) # 初始化 self.spanner_left = TimeMeter(self.time_left_turn) # 向左转 self.spanner_right = TimeMeter(self.time_right_turn) # 向右转 self.spanner_backward = TimeMeter(self.time_backward_turn) # 向后转 self.timer_arm_action = TimeMeter(self.time_arm_action) # 手臂做动作 self.timer_enter_home = TimeMeter(self.time_enter_home) # 回家 self.timer_back_short = TimeMeter(self.time_back_short) # 短距离重定位 self.timer_back_long = TimeMeter(self.time_back_long) # 长距离重定位 # 相机稳定 self.count_stable = 0 # 相机稳定计数器 self.count_continuous_stable = 25 # 相机连续稳定阈值 # 已经定位十字的次数 self.count_cross_pass = 0 # 十字定位次数计数器 # 结束巡线的次数 self.count_out_line = 0 self.count_max_out_line = 5 # 定位旋转超出预计的次数 self.count_locate_spin_left = 0 self.count_locate_spin_right = 0 self.count_max_locate_spin = 25 def run(self): while True: # 传感器数据 grayscale_data = self.api.get_grayscale_data() # 数据处理 line_center_offset = self.api.follow_line() # 状态机 if self.state_main == MainState.IDLE: if self.initializer.complete(): print("初始化完成") self.state_main = MainState.TRANSITION self.state_transition = TransitionState.EXIT_CROSS else: print("初始化中...") self.__clamp_arms() elif self.state_main == MainState.TRANSITION: if self.state_transition == TransitionState.EXIT_CROSS: if self.locator.detect_black(grayscale_data): print("检测到黑色") self.api.move_forward(self.speed_move_in_white) else: print("准备进入白色区域前进") self.state_main = MainState.TRANSITION self.state_transition = TransitionState.MOVE_FORWARD elif self.state_transition == TransitionState.MOVE_FORWARD: if self.locator.detect_black(grayscale_data): print("检测到黑色,进入定位环节") self.state_main = MainState.LOCATION self.api.stop() else: print("白色区域中前进") self.api.move_forward(self.speed_move_in_white) elif self.state_transition == TransitionState.SPAN: if self.state_span == SpanState.FORWARD: print("向前进,准备驶出十字") self.state_main = MainState.TRANSITION self.state_transition = TransitionState.EXIT_CROSS elif self.state_span == SpanState.BACKWARD: if not self.spanner_backward.in_progress: self.spanner_backward.start() if self.spanner_backward.complete(): print("向后转完成,准备进入短距离重定位") self.state_main = MainState.RELOCATION self.state_relocation = RelocationState.SHORT else: print("向后转中") self.api.spin_left(self.speed_spin) elif self.state_span == SpanState.LEFT: if not self.spanner_left.in_progress: self.spanner_left.start() if self.spanner_left.complete(): print("向左转完成,准备进入短距离重定位") self.state_main = MainState.RELOCATION self.state_relocation = RelocationState.SHORT else: print("向左转中") self.api.spin_left(self.speed_spin) elif self.state_span == SpanState.RIGHT: if not self.spanner_right.in_progress: self.spanner_right.start() if self.spanner_right.complete(): if self.state_target == TargetState.YOLO: print("向右转完成,准备进入短距离重定位") self.state_main = MainState.RELOCATION self.state_relocation = RelocationState.SHORT elif (self.state_target == TargetState.BACK_HOME or self.state_target == TargetState.FACE): print("向右转完成,准备进入长距离重定位") self.state_main = MainState.RELOCATION self.state_relocation = RelocationState.LONG else: print("向右转中") self.api.spin_right(self.speed_spin) elif self.state_main == MainState.LOCATION: if self.locator.translate_to_center(grayscale_data): # 中心对齐了 if self.locator.reach_target(grayscale_data, False): print("定位成功!!!") self.api.stop() self.__correct_direction() if self.state_relocation == RelocationState.COMPLETE: if self.state_target == TargetState.APRIL_TAG: if self.count_cross_pass < 1: print("到达 April Tag 识别前的一个十字,准备左转") self.count_cross_pass += 1 self.state_main = MainState.TRANSITION self.state_transition = TransitionState.SPAN self.state_span = SpanState.LEFT else: print("到达 April Tag 识别十字,准备进入识别程序") self.count_cross_pass = 0 self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.PREPARE elif self.state_target == TargetState.GESTURE: if self.count_cross_pass < 1: print("到达手势识别前的一个十字,准备继续前进") self.count_cross_pass += 1 self.state_main = MainState.TRANSITION self.state_transition = TransitionState.EXIT_CROSS else: print("到达手势识别十字,准备进入识别程序") self.count_cross_pass = 0 self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.PREPARE elif self.state_target == TargetState.YOLO: if self.count_cross_pass < 1: print("到达 YOLO 识别前的一个十字,准备右转") self.count_cross_pass += 1 self.state_main = MainState.TRANSITION self.state_transition = TransitionState.SPAN self.state_span = SpanState.RIGHT else: print("到达 YOLO 识别十字,准备先左转,再进入识别程序") self.count_cross_pass = 0 self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.TURN_LEFT elif self.state_target == TargetState.FACE: print("到达人脸识别十字,进入识别程序") self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.PREPARE elif self.state_target == TargetState.BACK_HOME: print("准备进入巡线") self.state_main = MainState.LINE else: print("重定位完成,准备向前驶出十字") self.state_relocation = RelocationState.COMPLETE self.state_main = MainState.TRANSITION self.state_transition = TransitionState.EXIT_CROSS else: if self.locator.seeking_left(grayscale_data): print("中心是黑色了,前进左转") self.count_locate_spin_left += 1 self.api.spin_left(self.speed_locate_turn) elif self.locator.seeking_right(grayscale_data): self.count_locate_spin_right += 1 print("中心是黑色了,前进右转") self.api.spin_right(self.speed_locate_turn) else: print("中心是黑色,继续前进") self.api.move_forward(int(self.speed_locate_move)) else: # 中心未对齐,但会出现都是 False 的情况,应当先解决次情况 if self.locator.move_straight(grayscale_data): print("All False") self.api.move_forward(self.speed_locate_move) else: if self.locator.move_left(grayscale_data): print("中心未检测到黑色,左平移") self.api.move_left(self.speed_locate_move) elif self.locator.move_right(grayscale_data): print("中心未检测到黑色,右平移") self.api.move_right(self.speed_locate_move) else: print("无法判断了") pass elif self.state_main == MainState.RELOCATION: if self.state_relocation == RelocationState.SHORT: if not self.timer_back_short.in_progress: self.timer_back_short.start() if self.timer_back_short.complete(): print("短距离后退完成,准备在白色区域中前进") self.api.stop() self.state_main = MainState.TRANSITION self.state_transition = TransitionState.MOVE_FORWARD else: print("短距离后退中") self.api.move_backward(self.speed_move_in_white) elif self.state_relocation == RelocationState.LONG: if not self.timer_back_long.in_progress: self.timer_back_long.start() if self.timer_back_long.complete(): print("长距离后退完成,准备在白色区域中前进") self.api.stop() self.state_main = MainState.TRANSITION self.state_transition = TransitionState.MOVE_FORWARD else: print("长距离后退中") self.api.move_backward(self.speed_move_in_white) elif self.state_main == MainState.RECOGNITION: if self.state_recognition == RecognitionState.TURN_LEFT: if not self.spanner_left.in_progress: self.spanner_left.start() if self.spanner_left.complete(): print("YOLO 识别前,左转完成") self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.PREPARE else: print("YOLO 识别前,左转中") self.api.spin_left(self.speed_spin) elif self.state_recognition == RecognitionState.PREPARE: if self.count_stable < self.count_continuous_stable: print("等待相机稳定") self.api.stop() self.count_stable += 1 self.__clamp_arms() continue if self.state_target == TargetState.APRIL_TAG: print("演习区域,准备识别 April Tag") self.timer_arm_action.start() self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.EXECUTE elif self.state_target == TargetState.GESTURE: print("演习区域,准备识别手势图像") self.timer_arm_action.start() self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.EXECUTE elif self.state_target == TargetState.YOLO: print("YOLO 识别区域,预加载图像中") preload_complete = self.api.preload_yolo_pool() if preload_complete: print("预加载图像完成,准备瞄准") self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.AIM elif self.state_target == TargetState.FACE: print("人脸识别区域,准备瞄准") self.state_main = MainState.RECOGNITION self.state_recognition = RecognitionState.AIM elif self.state_recognition == RecognitionState.AIM: if self.state_target == TargetState.YOLO: print("YOLO 预加载图像完成,准备瞄准") find_target, offset_x = self.api.detect_yolo(label=self.target_yolo) if find_target: self.__aim_target(offset_x) elif self.state_target == TargetState.FACE: print("人脸识别区域,准备瞄准") find_target, offset_x = self.api.detect_face(label=self.target_face) if find_target: self.__aim_target(offset_x) elif self.state_recognition == RecognitionState.EXECUTE: if self.state_target == TargetState.APRIL_TAG: find_tag, tag_id, offset = self.api.detect_apriltag() if self.timer_arm_action.complete(): print("April Tag 动作完成") self.__clamp_arms() self.state_main = MainState.TRANSITION self.state_transition = TransitionState.SPAN self.state_span = SpanState.BACKWARD self.state_target = TargetState.GESTURE self.state_relocation = RelocationState.SHORT self.api.close_tag_window() else: print("做 April Tag 动作中") if find_tag: print(f"找到 April Tag 动作:{tag_id}") self.__do_arm_action(tag_id) elif self.state_target == TargetState.GESTURE: find_target, number = self.api.detect_gesture() if self.timer_arm_action.complete(): print("手势动作完成") self.__clamp_arms() self.state_main = MainState.TRANSITION self.state_transition = TransitionState.SPAN self.state_span = SpanState.BACKWARD self.state_target = TargetState.YOLO self.state_relocation = RelocationState.SHORT self.api.close_gesture_window() else: print("做手势动作中") if find_target: print(f"找到手势动作:{number}") self.__do_arm_action(number) elif self.state_target == TargetState.YOLO: print(f"开始击打 YOLO 目标:{self.target_yolo}") self.__hit_actions() self.state_main = MainState.TRANSITION self.state_transition = TransitionState.SPAN self.state_span = SpanState.RIGHT self.state_target = TargetState.FACE self.state_relocation = RelocationState.LONG self.__reset_yolo() elif self.state_target == TargetState.FACE: print(f"开始击打人脸目标:{self.target_face}") self.__hit_actions() self.state_main = MainState.TRANSITION self.state_transition = TransitionState.SPAN self.state_span = SpanState.RIGHT self.state_target = TargetState.BACK_HOME self.state_relocation = RelocationState.LONG self.api.close_face_window() elif self.state_main == MainState.LINE: if self.locator.detect_black(grayscale_data): print("巡线中") self.__follow_line(line_center_offset) self.count_out_line = 0 else: if self.count_out_line > self.count_max_out_line: print("巡线完成") self.state_main = MainState.HOME else: self.count_out_line += 1 elif self.state_main == MainState.HOME: if not self.timer_enter_home.in_progress: self.timer_enter_home.start() if self.timer_enter_home.complete(): print("回家,停车") self.state_main = MainState.FINISH else: print("回家中") self.api.move_forward(self.speed_follow_line) elif self.state_main == MainState.FINISH: print("任务完成") self.api.stop() break def __aim_target(self, offset): if offset is not None: if offset >= self.target_center_offset: self.api.move_right(self.speed_aim) elif offset <= -self.target_center_offset: self.api.move_left(self.speed_aim) else: self.api.stop() self.state_recognition = RecognitionState.EXECUTE else: print(f"没有发现目标") self.api.stop() def __follow_line(self, offset): turn_rate = self.pid.compute(offset) self.api.move_rotation(speed=self.speed_follow_line, turn_rate=turn_rate) def __correct_direction(self): if self.count_locate_spin_left > self.count_max_locate_spin: print("右转矫正方向") self.api.spin_right(self.speed_spin, self.time_right_turn) time.sleep(self.time_right_turn / 1000) elif self.count_locate_spin_right > self.count_max_locate_spin: print("左转矫正方向") self.api.spin_left(self.speed_spin, self.time_left_turn) time.sleep(self.time_left_turn / 1000) else: print("不需要矫正方向") self.count_locate_spin_left = 0 self.count_locate_spin_right = 0 def __hit_actions(self): # 前进 self.api.move_forward(speed=self.speed_hit_position, run_time=self.time_hit_position) time.sleep(self.time_hit_position / 1000) # 击打动作序列 self.__pre_hit() time.sleep(self.time_arm_action / 1000) self.__hit() time.sleep(self.time_arm_action / 1000) self.__clamp_arms() # 后退 self.api.move_backward(speed=self.speed_hit_position, run_time=self.time_hit_position) time.sleep(self.time_hit_position / 1000) def __do_arm_action(self, number): if number == self.target_id: # 举左手 left_action = self.left_arm_actions["up"] right_action = self.right_arm_actions["clamp"] self.api.execute_arm_action(left_action, right_action) elif number == self.target_number_right: # 举右手 left_action = self.left_arm_actions["clamp"] right_action = self.right_arm_actions["up"] self.api.execute_arm_action(left_action, right_action) elif number == self.target_number_both: # 举双手 left_action = self.left_arm_actions["up"] right_action = self.right_arm_actions["up"] self.api.execute_arm_action(left_action, right_action) def __clamp_arms(self): left_action = self.left_arm_actions["clamp"] right_action = self.right_arm_actions["clamp"] self.api.execute_arm_action(left_action, right_action) def __pre_hit(self): left_action = self.left_arm_actions["clamp"] right_action = self.right_arm_actions["pre_hit"] self.api.execute_arm_action(left_action, right_action) def __hit(self): left_action = self.left_arm_actions["clamp"] right_action = self.right_arm_actions["hit"] self.api.execute_arm_action(left_action, right_action) def __reset_yolo(self): self.api.close_yolo_window() self.api.reset_yolo_pool() if __name__ == '__main__': controller = Controller() controller.run()
05-29
#include "esp_camera.h" #include <WiFi.h> // // WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality // Ensure ESP32 Wrover Module or other board with PSRAM is selected // Partial images will be transmitted if image exceeds buffer size // // You must select partition scheme from the board menu that has at least 3MB APP space. // Face Recognition is DISABLED for ESP32 and ESP32-S2, because it takes up from 15 // seconds to process single frame. Face Detection is ENABLED if PSRAM is enabled as well // =================== // Select camera model // =================== //#define CAMERA_MODEL_WROVER_KIT // Has PSRAM //#define CAMERA_MODEL_ESP_EYE // Has PSRAM //#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM //#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM //#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM //#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM //#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM //#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM #define CAMERA_MODEL_AI_THINKER // Has PSRAM //#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM // ** Espressif Internal Boards ** //#define CAMERA_MODEL_ESP32_CAM_BOARD //#define CAMERA_MODEL_ESP32S2_CAM_BOARD //#define CAMERA_MODEL_ESP32S3_CAM_LCD #include "camera_pins.h" // =========================== // Enter your WiFi credentials // =========================== const char* ssid = "abc"; const char* password = "12345678"; void startCameraServer(); void setupLedFlash(int pin); void setup() { Serial.begin(115200); Serial.setDebugOutput(true); Serial.println(); camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.frame_size = FRAMESIZE_UXGA; config.pixel_format = PIXFORMAT_JPEG; // for streaming //config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; config.fb_location = CAMERA_FB_IN_PSRAM; config.jpeg_quality = 12; config.fb_count = 1; // if PSRAM IC present, init with UXGA resolution and higher JPEG quality // for larger pre-allocated frame buffer. if(config.pixel_format == PIXFORMAT_JPEG){ if(psramFound()){ config.jpeg_quality = 10; config.fb_count = 2; config.grab_mode = CAMERA_GRAB_LATEST; } else { // Limit the frame size when PSRAM is not available config.frame_size = FRAMESIZE_SVGA; config.fb_location = CAMERA_FB_IN_DRAM; } } else { // Best option for face detection/recognition config.frame_size = FRAMESIZE_240X240; #if CONFIG_IDF_TARGET_ESP32S3 config.fb_count = 2; #endif } #if defined(CAMERA_MODEL_ESP_EYE) pinMode(13, INPUT_PULLUP); pinMode(14, INPUT_PULLUP); #endif // camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } sensor_t * s = esp_camera_sensor_get(); // initial sensors are flipped vertically and colors are a bit saturated if (s->id.PID == OV2640_PID) { s->set_vflip(s, 1); // flip it back s->set_brightness(s, 1); // up the brightness just a bit s->set_saturation(s, -2); // lower the saturation } // drop down frame size for higher initial frame rate if(config.pixel_format == PIXFORMAT_JPEG){ s->set_framesize(s, FRAMESIZE_QVGA); } #if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM) s->set_vflip(s, 1); s->set_hmirror(s, 1); #endif #if defined(CAMERA_MODEL_ESP32S3_EYE) s->set_vflip(s, 1); #endif // Setup LED FLash if LED pin is defined in camera_pins.h #if defined(LED_GPIO_NUM) setupLedFlash(LED_GPIO_NUM); #endif WiFi.begin(ssid, password); WiFi.setSleep(false); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); startCameraServer(); Serial.print("Camera Ready! Use 'http://"); Serial.print(WiFi.localIP()); Serial.println("' to connect"); } void loop() { // Do nothing. Everything is done in another task by the web server delay(10000); } 请你分析此代码并给我说如何将显示图像垂直反转过来
07-04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值