yolo自动化项目实例解析(七)自建UI--工具栏选项
在上一章我们基本实现了关于预览窗口的显示,现在我们主要完善一下工具栏菜单按键
一、添加任务ui
先加个ui页面,不想看ui的复制完这个文件到ui目录下转下py直接从第二步开始看
vi ui/formpy.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>formpy</class>
<widget class="QWidget" name="formpy">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>316</width>
<height>196</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="4">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="led_name">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>999</width>
<height>9999</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 255, 255);</string>
</property>
<property name="text">
<string>蒙德锄地</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cb_is_checked">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>999999</width>
<height>9999999</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="bt_arg">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>参数</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="bt_opendir">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>99999</width>
<height>99999</height>
</size>
</property>
<property name="text">
<string>打开目录</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QPushButton" name="bt_start">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>999999</width>
<height>999999</height>
</size>
</property>
<property name="text">
<string>启动</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="bt_chuansong">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>999999</width>
<height>999999</height>
</size>
</property>
<property name="text">
<string>传送
脚本</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="bt_moban">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>99999</width>
<height>99999</height>
</size>
</property>
<property name="text">
<string>传送
模板</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="bt_moban_maodian">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>999</width>
<height>9999</height>
</size>
</property>
<property name="text">
<string>锚点
模板</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QPushButton" name="bt_del">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>99999</width>
<height>99999</height>
</size>
</property>
<property name="text">
<string>删除
任务</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
1、ui手动操作
1、打开qt,新建QWidget项目 大小316*196
2、新增按钮Push button 加7个 68*55
3、添加水平布局
4、在水平布局中添加一个line edit输入框,和check box多选框
5、右键主窗口--布局--珊格布局
2、按钮按键重命名
3、类重命名
后面对接py使用
formpy
bt_arg
bt_chuansong
bt_del
bt_moban
bt_moban_maodian
bt_opendir
bt_start
horizontalLayout
cb_is_checked
led_name
感觉貌似漏了点啥,下面做不成功的话直接用一开始的xml 后面补充这个
二、添加工具栏--任务包按钮
1、配置文件读取
我们后面要改的东西越来越多了,先加个变量文件方便我们后面调用
下面我们使用的config.get意思是从./datas/setting.ini文件中读取关键字PACKS_TASK对应的路径,如果没有的话默认值为./datas/Task/
vi state.py
# -*- coding: utf-8 -*-
import configparser
import json
import os
import sys
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 加载 INI 文件
config.read("./datas/setting.ini")
#Task任务目录路径
PACKS_TASK = config.get('seting', 'PACKS_TASK', fallback='./datas/Task/')
2、工具栏添加按钮
#添加任务包名为按钮
def add_tool_item(self, dir_):
action_item = QWidgetAction(self)
dir_ = dir_.replace("/", "\\")
if dir_[-1] == "\\":
dir_ = dir_[:-1]
#这里把路径下的目录名取出来当作按钮名称
bt_item = QPushButton(dir_.split("\\")[-1].strip())
bt_item.setMaximumWidth(int(80 * ratio))
#读取项目的说明打印出来
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
txt = "无 \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"
bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
bt_item.setStyleSheet("border: none; padding: 3px;")
#绑定按钮的功能
bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))
#添加动作
action_item.setDefaultWidget(bt_item)
#按钮添加到工具栏
self.toolBar.addAction(action_item)
#按钮触发函数,先放着有个东西
def show_tool_item(self, dir_):
pass
3、自动触发添加
class MainWindow(QMainWindow, Ui_mainWindow):
def __init__(self):
...
#添加任务包
self.load_task_packs()
#取任务包的路径,循环触发工具栏添加按钮
def load_task_packs(self):
self.toolBar.clear()
packs = state.PACKS_TASK.split("#@@#")
try:
if len(packs) >= 1:
for pack in packs:
if pack != "":
self.add_tool_item(pack)
else:
self.add_tool_item(state.PACKS_TASK)
except:
pass
4、工具栏移动、任务删除
这里在add_tool_item中添加了关于上下移动和删除的按钮,以及对应的触发函数
def add_tool_item(self, dir_):
action_item = QWidgetAction(self)
dir_ = dir_.replace("/", "\\")
if dir_[-1] == "\\":
dir_ = dir_[:-1]
#这里把路径下的目录名取出来当作按钮名称
bt_item = QPushButton(dir_.split("\\")[-1].strip())
bt_item.setMaximumWidth(int(80 * ratio))
#读取项目的说明打印出来
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
txt = "无 \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"
bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
bt_item.setStyleSheet("border: none; padding: 3px;")
#绑定按钮的功能
bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))
#添加动作
action_item.setDefaultWidget(bt_item)
#按钮添加到工具栏
self.toolBar.addAction(action_item)
menu = QMenu(self)
action_menu_down = QAction('顺序 ↓', self)
action_menu_down.triggered.connect(functools.partial(self.down_tool_item, len(self.toolBar.actions())))
menu.addAction(action_menu_down)
action_menu_up = QAction('顺序 ↑', self)
action_menu_up.triggered.connect(functools.partial(self.up_tool_item, len(self.toolBar.actions())))
menu.addAction(action_menu_up)
action_menu_del = QAction('删除任务包', self)
action_menu_del.triggered.connect(functools.partial(self.del_tool_item, action_item, bt_item, dir_))
menu.addAction(action_menu_del)
# 将菜单关联到工具栏上
bt_item.setContextMenuPolicy(Qt.CustomContextMenu)
bt_item.customContextMenuRequested.connect(lambda pos: menu.exec_(bt_item.mapToGlobal(pos)))
self.change_tool_show_style(dir_)
def del_tool_item(self, action_item, bt_item, dir_):
self.toolBar.removeAction(action_item)
if state.PATH_TASK.replace("/", "\\") == dir_:
self.datas = []
self.row = 0
self.column = -1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
del bt_item, action_item # 删除动作对象和bt对象
txt_ = ""
packs = state.PACKS_TASK.split("#@@#")
if len(packs) >= 1:
for pack in packs:
if os.path.realpath(dir_) != os.path.realpath(pack) and pack != "":
txt_ = txt_ + pack + "#@@#"
state.PACKS_TASK = txt_
print(f"成功移除{dir_}任务包")
self.sg.mysig_tishi.emit(f"成功移除{dir_}任务包")
def down_tool_item(self, idex):
task_list = state.PACKS_TASK.split("#@@#")
if idex < len(task_list) - 1:
# 将指定索引位置的成员与其前一个成员交换位置
task_list[idex], task_list[idex + 1] = task_list[idex + 1], task_list[idex]
state.PACKS_TASK = ""
for item in task_list:
if item != "":
state.PACKS_TASK += "#@@#" + item
self.load_task_packs()
def up_tool_item(self, idex):
task_list = state.PACKS_TASK.split("#@@#")
if idex > 0:
# 将指定索引位置的成员与其后一个成员交换位置
task_list[idex], task_list[idex - 1] = task_list[idex - 1], task_list[idex]
state.PACKS_TASK = ""
for item in task_list:
if item != "":
state.PACKS_TASK += "#@@#" + item
self.load_task_packs()
不知道按钮为什么时灵时不灵,不重要先忽略
三、任务包--设置工作目录
1、更新变量
# -*- coding: utf-8 -*-
import configparser
import json
import os
import sys
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 加载 INI 文件
config.read("./datas/setting.ini")
#Task任务目录路径
PACKS_TASK = config.get('seting', 'PACKS_TASK', fallback='./datas/Task/')
PATH_TASK = config.get('seting', 'PATH_TASK', fallback='./datas/Task/')
PROVIDERS = config.get('seting', 'PROVIDERS', fallback="""["CUDAExecutionProvider", "CPUExecutionProvider"]""")
PROVIDERS = json.loads(PROVIDERS.replace("'", '"'))
LIANZHAOFUWU = config.get('seting', 'LIANZHAOFUWU', fallback='./datas/jiaoben/躺宝连招插件.exe')
DUANGKOUHAO = config.get('seting', 'DUANGKOUHAO', fallback='29943')
GAME_TITLE = config.get('seting', 'GAME_TITLE', fallback='原神')
LIANZHAO = config.get('seting', 'LIANZHAO', fallback='阵容1草神2久岐忍3钟离4雷神.txt')
PATH_TASK = config.get('seting', 'PATH_TASK', fallback='./datas/Task/')
PATH_JIAOBEN = config.get('seting', 'PATH_JIAOBEN', fallback='./datas/JiaoBen/')
PATH_JUESE = config.get('seting', 'PATH_JUESE', fallback='./datas/img/juese/')
PATH_ADDRESS = config.get('seting', 'PATH_ADDRESS', fallback='./datas/img/address/')
WEIGHTS = config.get('seting', 'WEIGHTS', fallback='./datas/yolov5s_320.onnx')
IMGSIZE_WIDTH = int(config.get('seting', 'IMGSIZE_WIDTH', fallback='320'))
IMGSIZE_HEIGHT = int(config.get('seting', 'IMGSIZE_HEIGHT', fallback='320'))
WINDOW_WIDTH = int(config.get('seting', 'WINDOW_WIDTH', fallback="640"))
WINDOW_HEIGHT = int(config.get('seting', 'WINDOW_HEIGHT', fallback="900"))
WINDOW_LEFT = int(config.get('seting', 'WINDOW_LEFT', fallback="0"))
WINDOW_TOP = int(config.get('seting', 'WINDOW_TOP', fallback="300"))
CMD_WIDTH = int(config.get('seting', 'CMD_WIDTH', fallback="800"))
CMD_HEIGHT = int(config.get('seting', 'CMD_HEIGHT', fallback="400"))
CMD_LEFT = int(config.get('seting', 'CMD_LEFT', fallback="500"))
CMD_TOP = int(config.get('seting', 'CMD_TOP', fallback="300"))
ON_SHUTDOWN = int(config.get('seting', 'ON_SHUTDOWN', fallback="0"))
ON_JIXING = int(config.get('seting', 'ON_JIXING', fallback="0"))
ON_NEXTPACK = int(config.get('seting', 'ON_NEXTPACK', fallback="0"))
ON_LIANZHAOBUJIANCE = int(config.get('seting', 'ON_LIANZHAOBUJIANCE', fallback="0"))
ON_STARTWITH = int(config.get('seting', 'ON_STARTWITH', fallback="0"))
TIMEOUT_DAGUAI = int(config.get('seting', 'TIMEOUT_DAGUAI', fallback='120'))
2、保存窗口配置
def save_ini_seting(self):
try:
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
if hwnd:
rect = win32gui.GetWindowRect(hwnd)
x, y, w, h = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
else:
x, y, w, h = state.CMD_LEFT, state.CMD_TOP, state.CMD_WIDTH, state.CMD_HEIGHT
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 添加节和键-值对
config['seting'] = {
"GAME_TITLE": state.GAME_TITLE,
'LIANZHAO': state.LIANZHAO,
'LIANZHAOFUWU': state.LIANZHAOFUWU,
'DUANGKOUHAO': state.DUANGKOUHAO,
'PATH_TASK': state.PATH_TASK,
'PATH_JIAOBEN': state.PATH_JIAOBEN,
'PACKS_TASK': state.PACKS_TASK,
'WEIGHTS': state.WEIGHTS,
'PROVIDERS': state.PROVIDERS,
'IMGSIZE_WIDTH': str(state.IMGSIZE_WIDTH),
'IMGSIZE_HEIGHT': str(state.IMGSIZE_HEIGHT),
'WINDOW_WIDTH': str(self.width()),
'WINDOW_HEIGHT': str(self.height()),
'WINDOW_LEFT': str(self.x()),
'WINDOW_TOP': str(self.y()),
'CMD_WIDTH': str(w),
'CMD_HEIGHT': str(h),
'CMD_LEFT': str(x),
'CMD_TOP': str(y),
'ON_SHUTDOWN': str(state.ON_SHUTDOWN),
'ON_JIXING': str(state.ON_JIXING),
'ON_NEXTPACK': str(state.ON_NEXTPACK),
'ON_STARTWITH': str(state.ON_STARTWITH),
'ON_LIANZHAOBUJIANCE': str(state.ON_LIANZHAOBUJIANCE),
'TIMEOUT_DAGUAI': str(state.TIMEOUT_DAGUAI)
}
# 写入配置到 INI 文件
with open("./datas/setting.ini", 'w') as configfile:
config.write(configfile)
except:
pass
3、获取工作栏动作
def change_tool_show_style(self, dir_):
# 获取工具栏上的所有动作
actions_list = self.toolBar.actions()
# 遍历处理每个动作
for action in actions_list:
# 在这里对每个动作进行处理
bt_item = action.defaultWidget()
if dir_ == bt_item.toolTip().replace("/", "\\").split("\n")[0]:
bt_item.setStyleSheet("border-width: 1px; padding: 3px;")
else:
bt_item.setStyleSheet("border: none; padding: 3px;")
4、更新任务-路由
后续这里打算通过判断脚本的id来选择不同的任务,比如说我们下面注释的内容中,我们会通过工具栏中目录下的jiaoben.ini 配置文件中的type 去判断这个任务具体是要做什么
def update_tasks(self):
try:
self.datas = []
self.row = 0
self.column = -1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
# 遍历文件夹下有哪些目录
# 判断这个路径是否存在
if not os.path.exists(state.PATH_TASK):
state.PATH_TASK = "./datas/Task/"
# # 创建 ConfigParser 对象
# config_main = configparser.ConfigParser()
# # 加载 INI 文件
#
# config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
# self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
# self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
#
# # 获取文件夹列表
# folders = [item for item in os.listdir(state.PATH_TASK) if
# os.path.isdir(os.path.join(state.PATH_TASK, item))]
# # 将文件夹名称中的数字提取出来,并按照数字大小排序
# sorted_folders = sorted(folders, key=lambda x: extract_number(x))
# for item in sorted_folders:
# item_path = os.path.join(state.PATH_TASK, item)
# if os.path.isdir(item_path):
# # 创建 ConfigParser 对象
# config = configparser.ConfigParser()
# # 加载 INI 文件
# config.read(os.path.join(item_path, "jiaoben.ini"))
# if config.get('seting', 'type', fallback='1') == "2":
# # 副本任务
# self.add_taskfuben(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "3":
# # 脚本任务
# self.add_taskjiaoben(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "4":
# # 脚本任务
# self.add_xuanjue(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "5":
# # 脚本任务
# self.add_xuanlianzhao(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "6":
# self.add_taskpy(item_path, config)
# else:
# # 锄地任务
# self.add_task(item_path, config)
except Exception:
print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")
5、保存
def save(self):
for idex in range(len(self.datas)):
self.returnPressed_name(idex)
# 重写closeEvent方法,在窗口关闭时调用quit()退出应用程序
for i, data in enumerate(self.datas):
dir_ = os.path.join(state.PATH_TASK, data["name"])
self.save_ini(dir_, data)
self.update_tasks()
self.save_ini_seting()
6、补全触发函数
这里的show_tool在原程序上的作用是,当我们点击工具栏的任务包时,在我们预览窗口的下面去显示单个任务,下面的函数我们可以视为,先更新保存了配置,然后修改了样式
def show_tool_item(self, dir_):
self.save()
state.PATH_TASK = dir_
self.save_ini_seting()
#self.update_tasks() 不知道为什么作者在这里又写了一遍,先注释了
#self.change_tool_show_style(dir_)
print(f"成功设置{state.PATH_TASK}为当前任务文件夹")
7、全量代码
import configparser
import ctypes
import functools
import os
import re
import sys
import win32gui
# 导入PyQt5模块
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import state
# 导入UI文件生成的类
from ui.show import Ui_DockWidget
from ui.main import Ui_mainWindow
def extract_number(folder_name):
numbers = re.findall(r'\d+', folder_name)
return int(numbers[0]) if numbers else float('inf')
class MySignal(QObject):
# 无参数信号,可能用于触发显示某个路径或轨迹的操作
mysig_show_xunlu = pyqtSignal()
# 无参数信号,可能与YOLOv模型有关,用于触发显示模型输出或其他相关操作
mysig_show_yolov = pyqtSignal()
# 定义FormShow类,继承自QDockWidget和Ui_DockWidget
class FormShow(QDockWidget, Ui_DockWidget):
def __init__(self, parent=None):
super(QDockWidget, self).__init__(parent) # 调用父类构造函数
self.parent = parent # 保存父窗口引用
self.setParent(parent) # 设置父窗口
self.setupUi(self) # 初始化UI界面
self.set_ui() # 自定义设置UI界面
# 设置窗口标题
self.setWindowTitle("检测预览")
# 设置标签控件属性
self.lb_yolov.setScaledContents(True)
self.lb_yolov.setAlignment(Qt.AlignCenter)
self.lb_xunlu.setAlignment(Qt.AlignCenter)
# 移动窗口位置
self.move(0, 0)
# 设置窗口保持在最顶层
self.setWindowFlags(Qt.WindowStaysOnTopHint)
# 计算窗口大小
self.window_height = int(270 * ratio)
self.window_width = int(480 * ratio)
# 设置窗口最小尺寸
self.setMinimumSize(self.window_width, self.window_height * 2)
# 连接按钮点击事件
self.bt_jia.clicked.connect(self.clicked_jia)
self.bt_jian.clicked.connect(self.clicked_jian)
# 自定义UI设置
def set_ui(self):
pass # 此处可添加更多UI设置
# 按钮“加”点击事件处理
def clicked_jia(self):
# 如果窗口高度加上增量后超过屏幕高度,则返回
if self.window_height + 108 * ratio > 1080 * ratio:
return
# 更新窗口大小
self.window_height += int(108 * ratio)
self.window_width += int(190 * ratio)
# 设置标签固定高度
self.lb_xunlu.setFixedHeight(self.window_height)
# 根据窗口宽度调整标签位置
if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
self.lb_yolov.move(self.lb_xunlu.width(), 0)
else:
self.lb_yolov.move(0, self.lb_xunlu.height())
# 设置标签固定大小
self.lb_yolov.setFixedHeight(self.window_height)
self.lb_yolov.setFixedWidth(self.window_width)
# 按钮“减”点击事件处理
def clicked_jian(self):
# 如果窗口高度减去增量后小于最小高度,则返回
if self.window_height - 108 * ratio < 270 * ratio:
return
# 更新窗口大小
self.window_height -= int(108 * ratio)
self.window_width -= int(190 * ratio)
# 设置标签固定高度
self.lb_xunlu.setFixedHeight(self.window_height)
# 根据窗口宽度调整标签位置
if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
self.lb_yolov.move(self.lb_xunlu.width(), 0)
else:
self.lb_yolov.move(0, self.lb_xunlu.height())
# 设置标签固定大小
self.lb_yolov.setFixedHeight(self.window_height)
self.lb_yolov.setFixedWidth(self.window_width)
# 重写关闭事件
def closeEvent(self, event):
# 关闭窗口时取消主窗口上的动作选中状态
self.parent.action_isShow.setChecked(False)
# 重写调整大小事件
def resizeEvent(self, event):
# 根据窗口宽度调整标签位置
if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
self.lb_yolov.move(self.lb_xunlu.width(), 0)
else:
self.lb_yolov.move(0, self.lb_xunlu.height())
# 定义MainWindow类,继承自QMainWindow和Ui_mainWindow
class MainWindow(QMainWindow, Ui_mainWindow):
def __init__(self):
super(MainWindow, self).__init__() # 调用父类构造函数
self.setupUi(self) # 设置UI布局
self.retranslateUi(self) # 重新翻译UI组件的文字
# 修改窗口样式为黑灰色,高亮
self.set_ui()
# 设置窗口标题
self.setWindowTitle(f"修改主页标题")
# 设置窗口尺寸
self.resize(640, 900)
# 设置窗口起始位置
self.move(0, 300)
# 创建网格布局
self.g_box = QGridLayout()
# 创建一个中间部件
self.container_widget = QWidget()
# 将网格布局设置给中间部件
self.container_widget.setLayout(self.g_box)
# 设置布局左上对齐
self.g_box.setAlignment(Qt.AlignTop | Qt.AlignLeft)
# 设置布局边距
self.g_box.setContentsMargins(0, 0, 0, 0)
# 设置布局左对齐
self.g_box.setAlignment(Qt.AlignTop)
# 将中间部件设置为QScrollArea的子部件
self.sa_main.setWidget(self.container_widget)
# 获取纵向滚动条控件
vertical_scrollbar = self.findChild(QScrollArea)
self.v_scrollbar = vertical_scrollbar.verticalScrollBar()
# 设置窗口保持在最顶层
self.setWindowFlags(Qt.WindowStaysOnTopHint)
# 初始化变量
self.row = 0
self.column = -1
self.run_times = 1
self.column_step = 310
# 获取主窗口的状态栏对象
self.statusbar = self.statusBar()
# 设置状态栏文本
self.statusbar.showMessage('欢迎使用')
# 初始化数据列表
self.datas = []
# 创建一个计时器,用于延迟布局
self.timer = QTimer(self)
self.timer.setSingleShot(True) # 设置为单次触发
self.timer.timeout.connect(self.update_layout)
# 绑定信号槽
self.connect_set()
# 创建定时器 用于周期显示图片
self.timer = QTimer(self)
self.timer.timeout.connect(self.on_timer_timeout)
self.timer.start(5000) # 每5秒触发一次
# 添加任务包
self.load_task_packs()
#显示默认的任务
# self.show_tool_item(state.PATH_TASK)
def load_task_packs(self):
self.toolBar.clear()
packs = state.PACKS_TASK.split("#@@#")
try:
if len(packs) >= 1:
for pack in packs:
if pack != "":
self.add_tool_item(pack)
else:
self.add_tool_item(state.PACKS_TASK)
except:
pass
def add_tool_item(self, dir_):
action_item = QWidgetAction(self)
dir_ = dir_.replace("/", "\\")
if dir_[-1] == "\\":
dir_ = dir_[:-1]
# 这里把路径下的目录名取出来当作按钮名称
bt_item = QPushButton(dir_.split("\\")[-1].strip())
bt_item.setMaximumWidth(int(80 * ratio))
# 读取项目的说明打印出来
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
txt = "无 \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"
bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
bt_item.setStyleSheet("border: none; padding: 3px;")
# 绑定按钮的功能
bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))
# 添加动作
action_item.setDefaultWidget(bt_item)
# 按钮添加到工具栏
self.toolBar.addAction(action_item)
menu = QMenu(self)
action_menu_down = QAction('顺序 ↓', self)
action_menu_down.triggered.connect(functools.partial(self.down_tool_item, len(self.toolBar.actions())))
menu.addAction(action_menu_down)
action_menu_up = QAction('顺序 ↑', self)
action_menu_up.triggered.connect(functools.partial(self.up_tool_item, len(self.toolBar.actions())))
menu.addAction(action_menu_up)
action_menu_del = QAction('删除任务包', self)
action_menu_del.triggered.connect(functools.partial(self.del_tool_item, action_item, bt_item, dir_))
menu.addAction(action_menu_del)
# 将菜单关联到工具栏上
bt_item.setContextMenuPolicy(Qt.CustomContextMenu)
bt_item.customContextMenuRequested.connect(lambda pos: menu.exec_(bt_item.mapToGlobal(pos)))
self.change_tool_show_style(dir_)
def del_tool_item(self, action_item, bt_item, dir_):
self.toolBar.removeAction(action_item)
if state.PATH_TASK.replace("/", "\\") == dir_:
self.datas = []
self.row = 0
self.column = -1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
del bt_item, action_item # 删除动作对象和bt对象
txt_ = ""
packs = state.PACKS_TASK.split("#@@#")
if len(packs) >= 1:
for pack in packs:
if os.path.realpath(dir_) != os.path.realpath(pack) and pack != "":
txt_ = txt_ + pack + "#@@#"
state.PACKS_TASK = txt_
print(f"成功移除{dir_}任务包")
self.sg.mysig_tishi.emit(f"成功移除{dir_}任务包")
def down_tool_item(self, idex):
task_list = state.PACKS_TASK.split("#@@#")
if idex < len(task_list) - 1:
# 将指定索引位置的成员与其前一个成员交换位置
task_list[idex], task_list[idex + 1] = task_list[idex + 1], task_list[idex]
state.PACKS_TASK = ""
for item in task_list:
if item != "":
state.PACKS_TASK += "#@@#" + item
self.load_task_packs()
def up_tool_item(self, idex):
task_list = state.PACKS_TASK.split("#@@#")
if idex > 0:
# 将指定索引位置的成员与其后一个成员交换位置
task_list[idex], task_list[idex - 1] = task_list[idex - 1], task_list[idex]
state.PACKS_TASK = ""
for item in task_list:
if item != "":
state.PACKS_TASK += "#@@#" + item
self.load_task_packs()
def save_ini_seting(self):
try:
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
if hwnd:
rect = win32gui.GetWindowRect(hwnd)
x, y, w, h = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
else:
x, y, w, h = state.CMD_LEFT, state.CMD_TOP, state.CMD_WIDTH, state.CMD_HEIGHT
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 添加节和键-值对
config['seting'] = {
"GAME_TITLE": state.GAME_TITLE,
'LIANZHAO': state.LIANZHAO,
'LIANZHAOFUWU': state.LIANZHAOFUWU,
'DUANGKOUHAO': state.DUANGKOUHAO,
'PATH_TASK': state.PATH_TASK,
'PATH_JIAOBEN': state.PATH_JIAOBEN,
'PACKS_TASK': state.PACKS_TASK,
'WEIGHTS': state.WEIGHTS,
'PROVIDERS': state.PROVIDERS,
'IMGSIZE_WIDTH': str(state.IMGSIZE_WIDTH),
'IMGSIZE_HEIGHT': str(state.IMGSIZE_HEIGHT),
'WINDOW_WIDTH': str(self.width()),
'WINDOW_HEIGHT': str(self.height()),
'WINDOW_LEFT': str(self.x()),
'WINDOW_TOP': str(self.y()),
'CMD_WIDTH': str(w),
'CMD_HEIGHT': str(h),
'CMD_LEFT': str(x),
'CMD_TOP': str(y),
'ON_SHUTDOWN': str(state.ON_SHUTDOWN),
'ON_JIXING': str(state.ON_JIXING),
'ON_NEXTPACK': str(state.ON_NEXTPACK),
'ON_STARTWITH': str(state.ON_STARTWITH),
'ON_LIANZHAOBUJIANCE': str(state.ON_LIANZHAOBUJIANCE),
'TIMEOUT_DAGUAI': str(state.TIMEOUT_DAGUAI)
}
# 写入配置到 INI 文件
with open("./datas/setting.ini", 'w') as configfile:
config.write(configfile)
except:
pass
def change_tool_show_style(self, dir_):
# 获取工具栏上的所有动作
actions_list = self.toolBar.actions()
# 遍历处理每个动作
for action in actions_list:
# 在这里对每个动作进行处理
bt_item = action.defaultWidget()
if dir_ == bt_item.toolTip().replace("/", "\\").split("\n")[0]:
bt_item.setStyleSheet("border-width: 1px; padding: 3px;")
else:
bt_item.setStyleSheet("border: none; padding: 3px;")
def show_tool_item(self, dir_):
self.save()
state.PATH_TASK = dir_
self.save_ini_seting()
#self.update_tasks() 不知道为什么作者在这里又写了一遍,先注释了
#self.change_tool_show_style(dir_)
print(f"成功设置{state.PATH_TASK}为当前任务文件夹")
def save(self):
for idex in range(len(self.datas)):
self.returnPressed_name(idex)
# 重写closeEvent方法,在窗口关闭时调用quit()退出应用程序
for i, data in enumerate(self.datas):
dir_ = os.path.join(state.PATH_TASK, data["name"])
self.save_ini(dir_, data)
self.update_tasks()
self.save_ini_seting()
def update_tasks(self):
try:
self.datas = []
self.row = 0
self.column = -1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
# 遍历文件夹下有哪些目录
# 判断这个路径是否存在
if not os.path.exists(state.PATH_TASK):
state.PATH_TASK = "./datas/Task/"
# # 创建 ConfigParser 对象
# config_main = configparser.ConfigParser()
# # 加载 INI 文件
#
# config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
# self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
# self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
#
# # 获取文件夹列表
# folders = [item for item in os.listdir(state.PATH_TASK) if
# os.path.isdir(os.path.join(state.PATH_TASK, item))]
# # 将文件夹名称中的数字提取出来,并按照数字大小排序
# sorted_folders = sorted(folders, key=lambda x: extract_number(x))
# for item in sorted_folders:
# item_path = os.path.join(state.PATH_TASK, item)
# if os.path.isdir(item_path):
# # 创建 ConfigParser 对象
# config = configparser.ConfigParser()
# # 加载 INI 文件
# config.read(os.path.join(item_path, "jiaoben.ini"))
# if config.get('seting', 'type', fallback='1') == "2":
# # 副本任务
# self.add_taskfuben(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "3":
# # 脚本任务
# self.add_taskjiaoben(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "4":
# # 脚本任务
# self.add_xuanjue(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "5":
# # 脚本任务
# self.add_xuanlianzhao(item_path, config)
# elif config.get('seting', 'type', fallback='1') == "6":
# self.add_taskpy(item_path, config)
# else:
# # 锄地任务
# self.add_task(item_path, config)
except Exception:
print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")
def on_timer_timeout(self):
# 每隔一段时间自动显示图片
self.sg.mysig_show_xunlu.emit()
self.sg.mysig_show_yolov.emit()
# 绑定信号槽
def connect_set(self):
# 创建一个顶级菜单
self.menu = self.menuBar()
self.menu_view = self.menu.addMenu("视图")
# 创建一个动作
self.action_isShow = QAction("显示检测结果窗口", self)
self.action_isShow.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F11))
self.action_isShow.triggered.connect(self.hotkey_isShow)
self.action_isShow.setCheckable(True)
self.action_isShow.setChecked(True)
self.menu_view.addAction(self.action_isShow)
# 打开预测窗口
self.hotkey_isShow()
self.sg = MySignal()
self.sg.mysig_show_xunlu.connect(self.show_xunlu)
self.sg.mysig_show_yolov.connect(self.show_yolov)
def show_xunlu(self, image_path="./datas/111.png"):
# 加载本地图片
pixmap = QPixmap(image_path)
# 设置图片到 lb_xunlu 控件
self.fromshow.lb_xunlu.setPixmap(pixmap)
# 设置宽度固定,高度自适应
self.fromshow.window_width = int(self.fromshow.window_height * pixmap.width() / pixmap.height())
self.fromshow.lb_xunlu.setFixedHeight(self.fromshow.window_height)
self.fromshow.lb_xunlu.setFixedWidth(self.fromshow.window_width)
self.fromshow.lb_xunlu.setScaledContents(True)
def show_yolov(self, image_path="./datas/111.png"):
# 加载本地图片
pixmap1 = QPixmap(image_path)
# 设置图片到 lb_yolov 控件
self.fromshow.lb_yolov.setPixmap(pixmap1)
# 根据窗口宽度调整 lb_yolov 的位置
if self.fromshow.width() >= self.fromshow.lb_yolov.width() + self.fromshow.lb_xunlu.width():
self.fromshow.lb_yolov.move(self.fromshow.lb_xunlu.width(), 0)
else:
self.fromshow.lb_yolov.move(0, self.fromshow.lb_xunlu.height())
# 打开预测窗口
def hotkey_isShow(self):
# 尝试创建悬浮窗口实例
if not hasattr(self, "fromshow"):
self.fromshow = FormShow(self) # 创建悬浮窗口实例
self.addDockWidget(Qt.TopDockWidgetArea, self.fromshow) # 添加悬浮窗口到主窗口
# 更新布局
def update_layout(self):
try:
# 获取主窗口的宽度
width = self.centralWidget().width()
# 计算每行可以容纳的组件数量
num_per_row = (width) // (self.column_step * ratio)
if num_per_row < 1:
num_per_row = 1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
# 重新添加组件到网格布局
for i, data in enumerate(self.datas):
self.row = int(i // num_per_row)
self.column = int(i % num_per_row)
self.g_box.addWidget(data["f_item"], self.row, self.column)
except:
pass
# 设置主窗口样式
def set_ui(self):
# 设置主题样式为 Flatwhite
from qt_material import apply_stylesheet
apply_stylesheet(app, theme='dark_pink.xml')
# 设置窗口样式表
self.setStyleSheet("""
QScrollBar::handle:horizontal {
background-color: #A50F2C; /* 设置滑块颜色 */
}
QScrollBar::handle:horizontal:hover {
background-color: #FF1744; /* 设置滑块颜色 */
}
QPushButton:hover {
background-color: #DFC472; /* 设置颜色 */
}
QPlainTextEdit {
padding: 0px;
margin: 0px;
}
QPushButton {
padding: 0px;
margin: 1px;
}
""" + "font-family: {}; font-size: {}pt;".format(font.family(), font.pointSize()))
# 主程序入口
if __name__ == '__main__':
# 获取屏幕宽度和高度
screen_width = ctypes.windll.user32.GetSystemMetrics(0)
screen_height = ctypes.windll.user32.GetSystemMetrics(1)
# 初始化QT应用
app = QApplication(sys.argv)
# 计算分辨率比例
ratio = screen_width / 2560
# 设置全局字体大小
base_font_size = 13
new_font_size = int(base_font_size * ratio)
font = QFont("Arial", new_font_size)
# 创建主窗口实例
window_main = MainWindow()
# 显示主窗口
window_main.show()
# 监听消息不关闭
sys.exit(app.exec_())
8、多选项设置
我们上面具体做了个什么事呢,说白了就是给窗口的工具栏添加了一个选项,而且还没有具体的功能(づ ̄ 3 ̄)づ ,他这个主要逻辑就在于show函数中的 state.PATH_TASK = dir_这么一条,当我们点工具栏的任意按钮时,都会去触发一次修改目录,然后保存配置文件,也就是./datas/setting.ini ,也就是所谓他输出的设置为当前目录。
刚开始看的时候我还有个问题,他是从哪里设置的多个选项哇,stats.py只定义了一个路径(那个config.get之前没用过)想不明白了,然后在他的setting.ini中发现他是直接定义了多个路径,反过来一想确实唉,只要该过一次就行,后面他是可以直接读到的,如下
setting.ini
...
packs_task = ./datas/Task#@@#./datas/自行车#@@#./datas/摩托#@@#./datas/地铁#@@#./datas/高铁#@@#./datas/飞机#@@#
...
四、添加具体任务
前面做了ui的按钮,和相关的触发函数,但是现在点击页面还是什么都没反应,是因为我们没有做点击后显示什么的具体逻辑,搞一下
1 、添加执行次数
datas/Task/细节参数.ini
[seting]
run_times = 1
2 、查找字符串数字
注意这个不在类里,独立的函数
def extract_number(folder_name):
numbers = re.findall(r'\d+', folder_name)
return int(numbers[0]) if numbers else float('inf')
3、添加菜单
def connect_set(self):
....
# 创建一个顶级菜单
self.menu_run = self.menu.addMenu("操作")
# 创建一个动作
self.action_run_times = QAction(f"设置:当前任务包 执行次数:{self.run_times}", self)
self.action_run_times.triggered.connect(self.hotkey_run_times)
self.menu_run.addAction(self.action_run_times)
4、添加函数
def hotkey_run_times(self):
try:
text, ok = QInputDialog.getText(self, '执行次数', f'当前任务包:{state.PATH_TASK}\n请输入执行次数:',
text=str(self.run_times))
if not ok:
return
if text == "":
return
if int(text) < 1:
self.run_times = 1
else:
self.run_times = int(text)
self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
print(f"设置:当前任务包 执行次数:{self.run_times}")
# 创建 ConfigParser 对象
config_main = configparser.ConfigParser()
# 添加节和键-值对
config_main['seting'] = {
"run_times": str(self.run_times),
}
# 写入配置到 INI 文件
with open(os.path.join(state.PATH_TASK, "细节参数.ini"), 'w') as configfile:
config_main.write(configfile)
except:
pass
5、读取任务包目录下的子目录配置文件
从指定路径下的子目录中读取配置文件,并根据配置文件中的特定值执行不同的操作
def update_tasks(self):
try:
self.datas = []
self.row = 0
self.column = -1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
# 遍历文件夹下有哪些目录
# 判断这个路径是否存在
if not os.path.exists(state.PATH_TASK):
state.PATH_TASK = "./datas/Task/"
# 创建 ConfigParser 对象
config_main = configparser.ConfigParser()
# 加载 INI 文件
config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
# 获取文件夹列表
folders = [item for item in os.listdir(state.PATH_TASK) if
os.path.isdir(os.path.join(state.PATH_TASK, item))]
# 将文件夹名称中的数字提取出来,并按照数字大小排序
sorted_folders = sorted(folders, key=lambda x: extract_number(x))
for item in sorted_folders:
item_path = os.path.join(state.PATH_TASK, item)
if os.path.isdir(item_path):
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 加载 INI 文件
config.read(os.path.join(item_path, "jiaoben.ini"))
if config.get('seting', 'type', fallback='1') == "2":
# 副本任务
# self.add_taskfuben(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "3":
# 脚本任务
# self.add_taskjiaoben(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "4":
# 脚本任务
# self.add_xuanjue(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "5":
# 脚本任务
# self.add_xuanlianzhao(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "6":
# self.add_taskpy(item_path, config)
pass
else:
# 锄地任务
# self.add_task(item_path, config)
pass
except Exception:
print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")
6、添加任务包配置
datas/Task/Py任务模板/jiaoben.ini
[seting]
type = 6
name = Py任务模板
arg = .\datas\Task\Py任务模板\参数.ini
opendir = .\datas\Task\Py任务模板
chuansong =
cbb_address = 手动录制
chuansongmoban =
maodianmoban =
cb_is_checked = True
datas/Task/Py任务模板/参数.ini
[seting]
times = 1
arg1=123
arg2=ai123
7、添加模板任务
def add_taskpy(self, dir_, config):
'''
添加任务
:return:
'''
# 根据导入的文件
# 获取主窗口的宽度
width = self.centralWidget().width()
# 计算每行可以容纳的组件数量
num_per_row = (width) // (self.column_step * ratio) # 假设每个组件的宽度为200
if num_per_row < 1:
num_per_row = 1
self.column += 1
if self.column == num_per_row:
self.column = 0
self.row += 1
# 加载ini配置文件
f_item = FramePY()
f_item.setObjectName("frame_cont")
f_item.raise_()
f_item.setStyleSheet("""
#frame_cont:hover {
border: 1px solid red;
}
#frame_cont {
background-color: rgba(0, 0, 0, 0);
}
""")
d = {}
dir_ = dir_.replace("/", "\\")
if dir_[-1] == "\\":
dir_ = dir_[:-1]
d["type"] = "6"
try:
config.set('seting', 'name', dir_.split("\\")[-1].strip())
except:
# 添加一个名为 'seting' 的配置节
config.add_section('seting')
config.set('seting', 'name', dir_.split("\\")[-1].strip())
d["name"] = config.get('seting', 'name', fallback=dir_.split("\\")[-1]).strip()
d["arg"] = config.get('seting', 'arg', fallback=dir_ + "\\参数.ini")
d["opendir"] = config.get('seting', 'opendir', fallback=dir_)
d["chuansong"] = config.get('seting', 'chuansong', fallback='')
d["chuansongmoban"] = config.get('seting', 'chuansongmoban', fallback='')
d["maodianmoban"] = config.get('seting', 'maodianmoban', fallback='')
d["cb_is_checked"] = config.get('seting', 'cb_is_checked', fallback='True')
d["cbb_address"] = config.get('seting', 'cbb_address', fallback='手动录制')
d["cb_wakuang"] = config.get('seting', 'cb_wakuang', fallback='False')
d["cb_caiji"] = config.get('seting', 'cb_caiji', fallback='False')
d["cb_daguai"] = config.get('seting', 'cb_daguai', fallback='True')
f_item.bt_chuansong.setProperty("OK_chuansong", d["chuansong"] != "")
f_item.bt_moban.setProperty("OK_chuansongmoban", d["chuansongmoban"] != "")
f_item.bt_moban_maodian.setProperty("OK_maodianmoban", d["maodianmoban"] != "")
f_item.bt_chuansong.setObjectName("bt_chuansong")
f_item.bt_moban.setObjectName("bt_moban")
f_item.bt_moban_maodian.setObjectName("bt_moban_maodian")
f_item.led_name.setText(d["name"])
f_item.bt_arg.setStyleSheet("""
color: rgb(237,182,43);
border-color: rgb(237,182,43);
""")
f_item.bt_opendir.setStyleSheet("""
color: rgb(237,182,43);
border-color: rgb(237,182,43);
""")
f_item.bt_chuansong.setStyleSheet("""
#bt_chuansong[OK_chuansong="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
f_item.bt_moban.setStyleSheet("""
#bt_moban[OK_chuansongmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
f_item.bt_moban_maodian.setStyleSheet("""
#bt_moban_maodian[OK_maodianmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
if d["cb_is_checked"] == "True":
f_item.cb_is_checked.setChecked(True)
else:
f_item.cb_is_checked.setChecked(False)
idex = len(self.datas)
# f_item.led_name.editingFinished.connect(self.save)
f_item.bt_arg.clicked.connect(lambda: subprocess.Popen(['notepad.exe', d["arg"]]))
f_item.bt_opendir.clicked.connect(lambda: subprocess.Popen(['explorer', d["opendir"]]))
f_item.bt_chuansong.clicked.connect(functools.partial(self.clickd_bt_chuansong, idex))
f_item.bt_moban.clicked.connect(functools.partial(self.clickd_bt_moban, idex))
f_item.bt_moban_maodian.clicked.connect(functools.partial(self.clickd_bt_moban_maodian, idex))
f_item.bt_start.clicked.connect(functools.partial(self.clickd_bt_start, idex))
f_item.bt_del.clicked.connect(functools.partial(self.clickd_bt_del, idex))
f_item.bt_arg.setVisible(False)
f_item.bt_opendir.setVisible(False)
f_item.bt_chuansong.setVisible(False)
f_item.bt_moban.setVisible(False)
f_item.bt_moban_maodian.setVisible(False)
f_item.bt_start.setVisible(False)
f_item.bt_del.setVisible(False)
f_item.bt_moban.setToolTip(self.tanChu_img(dir_ + "/" + d["chuansongmoban"]))
f_item.bt_moban_maodian.setToolTip(self.tanChu_img(dir_ + "/" + d["maodianmoban"]))
d["f_item"] = f_item
self.datas.append(d)
self.g_box.addWidget(f_item, self.row, self.column)
# 将py模板文件复制一份
dir_py = os.path.join(d['opendir'], 'main.py')
dir_ini = os.path.join(d['opendir'], '参数.ini')
try:
if not os.path.exists(dir_py):
shutil.copy('datas/pymode.py', dir_py)
if not os.path.exists(dir_ini):
shutil.copy('datas/pymode.ini', dir_ini)
except:
pass
8、按键函数
def clickd_bt_chuansong(self, idex):
# dl_chuansong = DialogChuansong(self, idex)
# dl_chuansong.cbb_address.setCurrentText(self.datas[idex]['cbb_address'])
# dl_chuansong.exec_()
pass
def clickd_bt_moban(self, idex):
temp_path, ok = QFileDialog.getOpenFileName(self, "传送模板图片", "", "图片文件 (*.png)")
if ok:
dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
if not os.path.exists(dir_):
os.makedirs(dir_)
# 并且将jiaoben.ini文件也填入数据然后生成
# 将图片复制到指定目录
path = dir_ + "/传送模板.png"
shutil.copy(temp_path, path)
self.datas[idex]["chuansongmoban"] = "传送模板.png"
self.datas[idex]["f_item"].bt_moban.setProperty("OK_chuansongmoban", True)
self.datas[idex]["f_item"].bt_moban.setStyleSheet("""
#bt_moban[OK_chuansongmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
self.save_ini(dir_, self.datas[idex])
self.sg.mysig_tishi.emit(f"导入传送模板成功:{path}")
logger.info(f"导入传送模板成功:{path}")
def clickd_bt_moban_maodian(self, idex):
temp_path, ok = QFileDialog.getOpenFileName(self, "锚点模板图片", "", "图片文件 (*.png)")
if ok:
dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
if not os.path.exists(dir_):
os.makedirs(dir_)
# 并且将jiaoben.ini文件也填入数据然后生成
# 将图片复制到指定目录
path = dir_ + "/锚点模板.png"
shutil.copy(temp_path, path)
self.datas[idex]["maodianmoban"] = "锚点模板.png"
self.datas[idex]["f_item"].bt_moban_maodian.setProperty("OK_maodianmoban", True)
self.datas[idex]["f_item"].bt_moban_maodian.setStyleSheet("""
#bt_moban_maodian[OK_maodianmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
self.save_ini(dir_, self.datas[idex])
logger.info(f"导入锚点模板成功:{path}")
self.sg.mysig_tishi.emit(f"导入锚点模板成功:{path}")
def clickd_bt_start(self, idex):
global ysyl
hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE) # 替换成你实际的窗口句柄
if hwnd == 0:
logger.info("错误:请先开游戏!")
self.sg.mysig_tishi.emit("错误:请先开游戏!")
return
left, top, right, bottom = win32gui.GetClientRect(hwnd)
width = right - left
height = bottom - top
if width != 1920 or height != 1080:
logger.info("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080 建议重新设置分辨率")
self.sg.mysig_tishi.emit("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080 建议重新设置分辨率")
return
self.save()
def run(idex):
ret = False
self.sg.mysig_mouse_through.emit(True)
state.计次_传送重试次数 = 0
state.状态_全局暂停 = False
self.datas[idex]['f_item'].cb_is_checked.setChecked(True)
for rt in range(self.run_times):
if self.action_runstartwith.isChecked():
logger.info(f"任务包:{state.PATH_TASK} {rt + 1}/{self.run_times}")
while idex < len(self.datas):
if state.状态_检测中:
time.sleep(1)
continue
if self.datas[idex]['f_item'].cb_is_checked.isChecked():
ret, arg = self.run(idex)
if arg is None:
arg = "0"
if arg != "0":
idex = self.arg_jisuan(arg, idex)
if state.状态_循环开关 == False:
break
if ret == False:
if state.状态_需重新传送 == True:
state.计次_传送重试次数 += 1
if state.计次_传送重试次数 >= 5:
state.计次_传送重试次数 = 0
logger.info("传送重试次数过多,放弃该任务!")
else:
logger.info(f"传送重试{state.计次_传送重试次数}")
time.sleep(2)
continue
else:
break
if self.action_runstartwith.isChecked() == False:
break
state.计次_传送重试次数 = 0
idex += 1
if self.action_runstartwith.isChecked() == False:
break
if state.状态_循环开关 == False:
break
idex = 0
if state.状态_循环开关 != False:
if self.action_runstartwith.isChecked():
# 切下一个任务包 继续
next_tas = self.get_next_task()
if state.ON_NEXTPACK == 1 and next_tas != None:
logger.info("切下一个任务包!")
state.PATH_TASK = next_tas
self.sg.mysig_mouse_through.emit(False)
self.sg.mysig_next_pack.emit()
return
if ret == True and state.ON_JIXING == 1:
ysyl.run_jixing()
if ret == True and state.ON_SHUTDOWN == 1:
self.sg.mysig_shutdown.emit()
self.sg.mysig_mouse_through.emit(False)
state.状态_循环开关 = False
t = threading.Thread(target=run, args=(idex,))
if state.python_var > 9:
t.daemon = True
else:
t.setDaemon(True)
t.start()
def clickd_bt_del(self, idex):
dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
try:
# 删除整个文件夹
shutil.rmtree(dir_)
logger.info(f"文件夹 {dir_} 已被删除")
self.update_tasks()
except Exception as e:
self.update_tasks()
logger.info(f"删除文件夹时出现错误:{e}")
如果要配置传送按钮,把clickd_bt_chuansong 函数打开补全对应ui及调用即可
9、添加任务ui配置
class FramePY(QFrame, Ui_formpy):
def __init__(self, parent=None):
super(QFrame, self).__init__(parent)
self.setupUi(self)
self.set_ui()
self.is_zd = False
w = int(item_width * ratio)
h = int(item_height_min * ratio)
self.setMinimumSize(w + int(1 * ratio), h)
self.setMaximumSize(w, h)
# self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint)
def set_ui(self):
pass
def mousePressEvent(self, event):
if not self.is_zd:
self.is_zd = not self.is_zd
w = int(item_width * ratio)
h = int(item_height * ratio)
self.setMinimumSize(w + int(1 * ratio), h)
self.setMaximumSize(w, h)
self.bt_arg.setVisible(True)
self.bt_opendir.setVisible(True)
self.bt_chuansong.setVisible(True)
self.bt_moban.setVisible(True)
self.bt_moban_maodian.setVisible(True)
self.bt_start.setVisible(True)
self.bt_del.setVisible(True)
else:
self.is_zd = not self.is_zd
w = int(item_width * ratio)
h = int(item_height_min * ratio)
self.setMinimumSize(w + int(1 * ratio), h)
self.setMaximumSize(w, h)
self.bt_arg.setVisible(False)
self.bt_opendir.setVisible(False)
self.bt_chuansong.setVisible(False)
self.bt_moban.setVisible(False)
self.bt_moban_maodian.setVisible(False)
self.bt_start.setVisible(False)
self.bt_del.setVisible(False)
10、添加通用py模板
demo1/datas/pymode.ini
[seting]
times = 1
arg1=懒人
arg2=ai
demo1/datas/pymode.py
这个文件下有好多依赖前面的模块的,我们这里是单开的目录所以没有文件,这个东西我们暂时也不用,上面的代码就是把这俩文件拷贝下到自己的目录
import configparser
import time
import traceback # 异常处理
import tangbaowss # 躺宝通讯
from lanrenauto.findpic.findimg import * # 找图 搜图
from lanrenauto.tools.screenshot import screenshot # 截图
from lanrenauto.yolov.lanrenonnx import LanRenOnnxYolov
class LanRenPY():
def __init__(self, name: str, arg: str, opendir: str, yolov: LanRenOnnxYolov):
self.name = name # 任务名
self.arg = arg # 用这个可以提前设定一些参数 比如 循环次数等
self.opendir = opendir # 用这个可以找到任务文件夹目录 注意: ./是项目根目录
self.yolov = yolov # 全局的yolov对象
if os.path.isfile(self.arg):
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 加载 INI 文件
config.read(self.arg)
logger.info(str(config.getint('seting', 'times', fallback=1)))
logger.info(config.get('seting', 'arg1', fallback="arg1"))
logger.info(config.get('seting', 'arg2', fallback="arg2"))
def yolov_show(self):
state.QT_信号.mysig_show_yolov.emit()
def run(self):
'''
入口函数 这个函数格式不支持修改,参数也不要改 是模板函数
:return: bool 必须return bool类型 True or False
'''
# 寻找游戏窗口句柄
hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)
left, top, right, bottom = 0, 0, 1920, 1080
try:
# 从这里开始写执行代码
logger.info(f"py脚本开始执行代码了!")
time.sleep(3)
# -----------------全局暂停/停止-------------------------
while True:
if state.状态_循环开关 == False:
state.状态_是否回放中 = False
state.状态_检测中 = False
state.状态_需重新传送 = False
tangbaowss.send_msg("是否回放#@@#假")
return False, "0"
if not state.状态_全局暂停:
break
else:
tangbaowss.send_msg("是否回放#@@#假")
time.sleep(2)
# ----------------------------------------------------
# ---------------------激活游戏窗口-------------------------------
if get_window_handle_at_mouse_position() != hwnd:
tangbaowss.send_msg("是否回放#@@#假")
time.sleep(0.1)
# 激活hwnd
hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE)
set_window_activate(hwnd)
time.sleep(0.2)
# yolov目标检测案例
img_big = screenshot(hwnd, left, top, right, bottom, filename=None)
datas, state.图片_YOLOV = self.yolov.detect(img_big, plot_box=state.开关_是否展预测结果)
#展示预测结果
if state.开关_是否展预测结果: self.yolov_show();
logger.info(str(datas))
# 识图点击 案例
img_small = cv2.imdecode(np.fromfile(file="./datas/派蒙.png", dtype=np.uint8), cv2.IMREAD_UNCHANGED)
find_img_and_click(img_small, hwnd, left, top, right, bottom, label="点击 派蒙",is_click=True,is_show=True)
# 完成后返回结果
state.状态_需重新传送 = False #如果为True则会重试 最多5次
logger.info(f"{self.name} py执行完毕!")
#返回时可以指定 下一个任务 从1 开始 值超过总数 则为这 整个任务包 结束了
#支持 +xxx -xxx 比如 +1 -1
#"0"或者不提供 则为不干扰顺序
return True,"0"
except:
state.状态_需重新传送 = False
logger.error(f"{self.name} py执行错误: {traceback.format_exc()}")
return False,"0"
11、窗口弹出模板
def tanChu_img(self, imgpath):
'''
弹出图片
:param imgpath:
:return:
'''
# ----------------------
try:
return f'''<img src='{imgpath}' > '''
except Exception as err:
return str(err)
12、全量代码
import configparser
import ctypes
import functools
import os
import re
import shutil
import subprocess
import sys
import threading
import time
import win32gui
# 导入PyQt5模块
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import state
# 导入UI文件生成的类
from ui.show import Ui_DockWidget
from ui.main import Ui_mainWindow
from ui.formpy import Ui_formpy
def extract_number(folder_name):
numbers = re.findall(r'\d+', folder_name)
return int(numbers[0]) if numbers else float('inf')
class FramePY(QFrame, Ui_formpy):
def __init__(self, parent=None):
super(QFrame, self).__init__(parent)
self.setupUi(self)
self.set_ui()
self.is_zd = False
w = int(item_width * ratio)
h = int(item_height_min * ratio)
self.setMinimumSize(w + int(1 * ratio), h)
self.setMaximumSize(w, h)
# self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint)
def set_ui(self):
pass
def mousePressEvent(self, event):
if not self.is_zd:
self.is_zd = not self.is_zd
w = int(item_width * ratio)
h = int(item_height * ratio)
self.setMinimumSize(w + int(1 * ratio), h)
self.setMaximumSize(w, h)
self.bt_arg.setVisible(True)
self.bt_opendir.setVisible(True)
self.bt_chuansong.setVisible(True)
self.bt_moban.setVisible(True)
self.bt_moban_maodian.setVisible(True)
self.bt_start.setVisible(True)
self.bt_del.setVisible(True)
else:
self.is_zd = not self.is_zd
w = int(item_width * ratio)
h = int(item_height_min * ratio)
self.setMinimumSize(w + int(1 * ratio), h)
self.setMaximumSize(w, h)
self.bt_arg.setVisible(False)
self.bt_opendir.setVisible(False)
self.bt_chuansong.setVisible(False)
self.bt_moban.setVisible(False)
self.bt_moban_maodian.setVisible(False)
self.bt_start.setVisible(False)
self.bt_del.setVisible(False)
class MySignal(QObject):
# 无参数信号,可能用于触发显示某个路径或轨迹的操作
mysig_show_xunlu = pyqtSignal()
# 无参数信号,可能与YOLOv模型有关,用于触发显示模型输出或其他相关操作
mysig_show_yolov = pyqtSignal()
# 定义FormShow类,继承自QDockWidget和Ui_DockWidget
class FormShow(QDockWidget, Ui_DockWidget):
def __init__(self, parent=None):
super(QDockWidget, self).__init__(parent) # 调用父类构造函数
self.parent = parent # 保存父窗口引用
self.setParent(parent) # 设置父窗口
self.setupUi(self) # 初始化UI界面
self.set_ui() # 自定义设置UI界面
# 设置窗口标题
self.setWindowTitle("检测预览")
# 设置标签控件属性
self.lb_yolov.setScaledContents(True)
self.lb_yolov.setAlignment(Qt.AlignCenter)
self.lb_xunlu.setAlignment(Qt.AlignCenter)
# 移动窗口位置
self.move(0, 0)
# 设置窗口保持在最顶层
self.setWindowFlags(Qt.WindowStaysOnTopHint)
# 计算窗口大小
self.window_height = int(270 * ratio)
self.window_width = int(480 * ratio)
# 设置窗口最小尺寸
self.setMinimumSize(self.window_width, self.window_height * 2)
# 连接按钮点击事件
self.bt_jia.clicked.connect(self.clicked_jia)
self.bt_jian.clicked.connect(self.clicked_jian)
# 自定义UI设置
def set_ui(self):
pass # 此处可添加更多UI设置
# 按钮“加”点击事件处理
def clicked_jia(self):
# 如果窗口高度加上增量后超过屏幕高度,则返回
if self.window_height + 108 * ratio > 1080 * ratio:
return
# 更新窗口大小
self.window_height += int(108 * ratio)
self.window_width += int(190 * ratio)
# 设置标签固定高度
self.lb_xunlu.setFixedHeight(self.window_height)
# 根据窗口宽度调整标签位置
if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
self.lb_yolov.move(self.lb_xunlu.width(), 0)
else:
self.lb_yolov.move(0, self.lb_xunlu.height())
# 设置标签固定大小
self.lb_yolov.setFixedHeight(self.window_height)
self.lb_yolov.setFixedWidth(self.window_width)
# 按钮“减”点击事件处理
def clicked_jian(self):
# 如果窗口高度减去增量后小于最小高度,则返回
if self.window_height - 108 * ratio < 270 * ratio:
return
# 更新窗口大小
self.window_height -= int(108 * ratio)
self.window_width -= int(190 * ratio)
# 设置标签固定高度
self.lb_xunlu.setFixedHeight(self.window_height)
# 根据窗口宽度调整标签位置
if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
self.lb_yolov.move(self.lb_xunlu.width(), 0)
else:
self.lb_yolov.move(0, self.lb_xunlu.height())
# 设置标签固定大小
self.lb_yolov.setFixedHeight(self.window_height)
self.lb_yolov.setFixedWidth(self.window_width)
# 重写关闭事件
def closeEvent(self, event):
# 关闭窗口时取消主窗口上的动作选中状态
self.parent.action_isShow.setChecked(False)
# 重写调整大小事件
def resizeEvent(self, event):
# 根据窗口宽度调整标签位置
if self.width() >= self.lb_yolov.width() + self.lb_xunlu.width():
self.lb_yolov.move(self.lb_xunlu.width(), 0)
else:
self.lb_yolov.move(0, self.lb_xunlu.height())
# 定义MainWindow类,继承自QMainWindow和Ui_mainWindow
class MainWindow(QMainWindow, Ui_mainWindow):
def __init__(self):
super(MainWindow, self).__init__() # 调用父类构造函数
self.setupUi(self) # 设置UI布局
self.retranslateUi(self) # 重新翻译UI组件的文字
# 修改窗口样式为黑灰色,高亮
self.set_ui()
# 设置窗口标题
self.setWindowTitle(f"修改主页标题")
# 设置窗口尺寸
self.resize(640, 900)
# 设置窗口起始位置
self.move(0, 300)
# 创建网格布局
self.g_box = QGridLayout()
# 创建一个中间部件
self.container_widget = QWidget()
# 将网格布局设置给中间部件
self.container_widget.setLayout(self.g_box)
# 设置布局左上对齐
self.g_box.setAlignment(Qt.AlignTop | Qt.AlignLeft)
# 设置布局边距
self.g_box.setContentsMargins(0, 0, 0, 0)
# 设置布局左对齐
self.g_box.setAlignment(Qt.AlignTop)
# 将中间部件设置为QScrollArea的子部件
self.sa_main.setWidget(self.container_widget)
# 获取纵向滚动条控件
vertical_scrollbar = self.findChild(QScrollArea)
self.v_scrollbar = vertical_scrollbar.verticalScrollBar()
# 设置窗口保持在最顶层
self.setWindowFlags(Qt.WindowStaysOnTopHint)
# 初始化变量
self.row = 0
self.column = -1
self.run_times = 1
self.column_step = 310
# 获取主窗口的状态栏对象
self.statusbar = self.statusBar()
# 设置状态栏文本
self.statusbar.showMessage('欢迎使用')
# 初始化数据列表
self.datas = []
# 创建一个计时器,用于延迟布局
self.timer = QTimer(self)
self.timer.setSingleShot(True) # 设置为单次触发
self.timer.timeout.connect(self.update_layout)
# 绑定信号槽
self.connect_set()
# 创建定时器 用于周期显示图片
self.timer = QTimer(self)
self.timer.timeout.connect(self.on_timer_timeout)
self.timer.start(5000) # 每5秒触发一次
# 添加任务包
self.load_task_packs()
#显示默认的任务
# self.show_tool_item(state.PATH_TASK)
def load_task_packs(self):
self.toolBar.clear()
packs = state.PACKS_TASK.split("#@@#")
try:
if len(packs) >= 1:
for pack in packs:
if pack != "":
self.add_tool_item(pack)
else:
self.add_tool_item(state.PACKS_TASK)
except:
pass
def add_tool_item(self, dir_):
action_item = QWidgetAction(self)
dir_ = dir_.replace("/", "\\")
if dir_[-1] == "\\":
dir_ = dir_[:-1]
# 这里把路径下的目录名取出来当作按钮名称
bt_item = QPushButton(dir_.split("\\")[-1].strip())
bt_item.setMaximumWidth(int(80 * ratio))
# 读取项目的说明打印出来
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
try:
with open(dir_ + "/" + "使用说明.txt", "r", encoding="utf-8") as f:
txt = f.read()
except:
txt = "无 \n(可以在任务包文件夹下创建一个 使用说明.txt 文件 来添加说明)"
bt_item.setToolTip(dir_ + "\n使用说明:" + txt + "\n")
bt_item.setStyleSheet("border: none; padding: 3px;")
# 绑定按钮的功能
bt_item.clicked.connect(functools.partial(self.show_tool_item, dir_))
# 添加动作
action_item.setDefaultWidget(bt_item)
# 按钮添加到工具栏
self.toolBar.addAction(action_item)
menu = QMenu(self)
action_menu_down = QAction('顺序 ↓', self)
action_menu_down.triggered.connect(functools.partial(self.down_tool_item, len(self.toolBar.actions())))
menu.addAction(action_menu_down)
action_menu_up = QAction('顺序 ↑', self)
action_menu_up.triggered.connect(functools.partial(self.up_tool_item, len(self.toolBar.actions())))
menu.addAction(action_menu_up)
action_menu_del = QAction('删除任务包', self)
action_menu_del.triggered.connect(functools.partial(self.del_tool_item, action_item, bt_item, dir_))
menu.addAction(action_menu_del)
# 将菜单关联到工具栏上
bt_item.setContextMenuPolicy(Qt.CustomContextMenu)
bt_item.customContextMenuRequested.connect(lambda pos: menu.exec_(bt_item.mapToGlobal(pos)))
self.change_tool_show_style(dir_)
def del_tool_item(self, action_item, bt_item, dir_):
self.toolBar.removeAction(action_item)
if state.PATH_TASK.replace("/", "\\") == dir_:
self.datas = []
self.row = 0
self.column = -1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
del bt_item, action_item # 删除动作对象和bt对象
txt_ = ""
packs = state.PACKS_TASK.split("#@@#")
if len(packs) >= 1:
for pack in packs:
if os.path.realpath(dir_) != os.path.realpath(pack) and pack != "":
txt_ = txt_ + pack + "#@@#"
state.PACKS_TASK = txt_
print(f"成功移除{dir_}任务包")
self.sg.mysig_tishi.emit(f"成功移除{dir_}任务包")
def down_tool_item(self, idex):
task_list = state.PACKS_TASK.split("#@@#")
if idex < len(task_list) - 1:
# 将指定索引位置的成员与其前一个成员交换位置
task_list[idex], task_list[idex + 1] = task_list[idex + 1], task_list[idex]
state.PACKS_TASK = ""
for item in task_list:
if item != "":
state.PACKS_TASK += "#@@#" + item
self.load_task_packs()
def up_tool_item(self, idex):
task_list = state.PACKS_TASK.split("#@@#")
if idex > 0:
# 将指定索引位置的成员与其后一个成员交换位置
task_list[idex], task_list[idex - 1] = task_list[idex - 1], task_list[idex]
state.PACKS_TASK = ""
for item in task_list:
if item != "":
state.PACKS_TASK += "#@@#" + item
self.load_task_packs()
def save_ini_seting(self):
try:
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
if hwnd:
rect = win32gui.GetWindowRect(hwnd)
x, y, w, h = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]
else:
x, y, w, h = state.CMD_LEFT, state.CMD_TOP, state.CMD_WIDTH, state.CMD_HEIGHT
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 添加节和键-值对
config['seting'] = {
"GAME_TITLE": state.GAME_TITLE,
'LIANZHAO': state.LIANZHAO,
'LIANZHAOFUWU': state.LIANZHAOFUWU,
'DUANGKOUHAO': state.DUANGKOUHAO,
'PATH_TASK': state.PATH_TASK,
'PATH_JIAOBEN': state.PATH_JIAOBEN,
'PACKS_TASK': state.PACKS_TASK,
'WEIGHTS': state.WEIGHTS,
'PROVIDERS': state.PROVIDERS,
'IMGSIZE_WIDTH': str(state.IMGSIZE_WIDTH),
'IMGSIZE_HEIGHT': str(state.IMGSIZE_HEIGHT),
'WINDOW_WIDTH': str(self.width()),
'WINDOW_HEIGHT': str(self.height()),
'WINDOW_LEFT': str(self.x()),
'WINDOW_TOP': str(self.y()),
'CMD_WIDTH': str(w),
'CMD_HEIGHT': str(h),
'CMD_LEFT': str(x),
'CMD_TOP': str(y),
'ON_SHUTDOWN': str(state.ON_SHUTDOWN),
'ON_JIXING': str(state.ON_JIXING),
'ON_NEXTPACK': str(state.ON_NEXTPACK),
'ON_STARTWITH': str(state.ON_STARTWITH),
'ON_LIANZHAOBUJIANCE': str(state.ON_LIANZHAOBUJIANCE),
'TIMEOUT_DAGUAI': str(state.TIMEOUT_DAGUAI)
}
# 写入配置到 INI 文件
with open("./datas/setting.ini", 'w') as configfile:
config.write(configfile)
except:
pass
def change_tool_show_style(self, dir_):
# 获取工具栏上的所有动作
actions_list = self.toolBar.actions()
# 遍历处理每个动作
for action in actions_list:
# 在这里对每个动作进行处理
bt_item = action.defaultWidget()
if dir_ == bt_item.toolTip().replace("/", "\\").split("\n")[0]:
bt_item.setStyleSheet("border-width: 1px; padding: 3px;")
else:
bt_item.setStyleSheet("border: none; padding: 3px;")
def show_tool_item(self, dir_):
self.save()
state.PATH_TASK = dir_
self.save_ini_seting()
#self.update_tasks() 不知道为什么作者在这里又写了一遍,先注释了
#self.change_tool_show_style(dir_)
print(f"成功设置{state.PATH_TASK}为当前任务文件夹")
def save(self):
for idex in range(len(self.datas)):
self.returnPressed_name(idex)
# 重写closeEvent方法,在窗口关闭时调用quit()退出应用程序
for i, data in enumerate(self.datas):
dir_ = os.path.join(state.PATH_TASK, data["name"])
self.save_ini(dir_, data)
self.update_tasks()
self.save_ini_seting()
def update_tasks(self):
try:
self.datas = []
self.row = 0
self.column = -1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
# 遍历文件夹下有哪些目录
# 判断这个路径是否存在
if not os.path.exists(state.PATH_TASK):
state.PATH_TASK = "./datas/Task/"
# 创建 ConfigParser 对象
config_main = configparser.ConfigParser()
# 加载 INI 文件
config_main.read(os.path.join(state.PATH_TASK, "细节参数.ini"))
self.run_times = int(config_main.get('seting', 'run_times', fallback='1'))
self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
# 获取文件夹列表
folders = [item for item in os.listdir(state.PATH_TASK) if
os.path.isdir(os.path.join(state.PATH_TASK, item))]
print(folders)
# 将文件夹名称中的数字提取出来,并按照数字大小排序
sorted_folders = sorted(folders, key=lambda x: extract_number(x))
for item in sorted_folders:
item_path = os.path.join(state.PATH_TASK, item)
if os.path.isdir(item_path):
# 创建 ConfigParser 对象
config = configparser.ConfigParser()
# 加载 INI 文件
config.read(os.path.join(item_path, "jiaoben.ini"))
if config.get('seting', 'type', fallback='1') == "2":
# 副本任务
# self.add_taskfuben(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "3":
# 脚本任务
# self.add_taskjiaoben(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "4":
# 脚本任务
# self.add_xuanjue(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "5":
# 脚本任务
# self.add_xuanlianzhao(item_path, config)
pass
elif config.get('seting', 'type', fallback='1') == "6":
self.add_taskpy(item_path, config)
else:
# 锄地任务
# self.add_task(item_path, config)
pass
except Exception:
print(f"请选择任务文件夹,目前没有这个文件夹{state.PATH_TASK}")
def add_taskpy(self, dir_, config):
'''
添加任务
:return:
'''
# 根据导入的文件
# 获取主窗口的宽度
width = self.centralWidget().width()
# 计算每行可以容纳的组件数量
num_per_row = (width) // (self.column_step * ratio) # 假设每个组件的宽度为200
if num_per_row < 1:
num_per_row = 1
self.column += 1
if self.column == num_per_row:
self.column = 0
self.row += 1
# 加载ini配置文件
f_item = FramePY()
f_item.setObjectName("frame_cont")
f_item.raise_()
f_item.setStyleSheet("""
#frame_cont:hover {
border: 1px solid red;
}
#frame_cont {
background-color: rgba(0, 0, 0, 0);
}
""")
d = {}
dir_ = dir_.replace("/", "\\")
if dir_[-1] == "\\":
dir_ = dir_[:-1]
d["type"] = "6"
try:
config.set('seting', 'name', dir_.split("\\")[-1].strip())
except:
# 添加一个名为 'seting' 的配置节
config.add_section('seting')
config.set('seting', 'name', dir_.split("\\")[-1].strip())
d["name"] = config.get('seting', 'name', fallback=dir_.split("\\")[-1]).strip()
d["arg"] = config.get('seting', 'arg', fallback=dir_ + "\\参数.ini")
d["opendir"] = config.get('seting', 'opendir', fallback=dir_)
d["chuansong"] = config.get('seting', 'chuansong', fallback='')
d["chuansongmoban"] = config.get('seting', 'chuansongmoban', fallback='')
d["maodianmoban"] = config.get('seting', 'maodianmoban', fallback='')
d["cb_is_checked"] = config.get('seting', 'cb_is_checked', fallback='True')
d["cbb_address"] = config.get('seting', 'cbb_address', fallback='手动录制')
d["cb_wakuang"] = config.get('seting', 'cb_wakuang', fallback='False')
d["cb_caiji"] = config.get('seting', 'cb_caiji', fallback='False')
d["cb_daguai"] = config.get('seting', 'cb_daguai', fallback='True')
f_item.bt_chuansong.setProperty("OK_chuansong", d["chuansong"] != "")
f_item.bt_moban.setProperty("OK_chuansongmoban", d["chuansongmoban"] != "")
f_item.bt_moban_maodian.setProperty("OK_maodianmoban", d["maodianmoban"] != "")
f_item.bt_chuansong.setObjectName("bt_chuansong")
f_item.bt_moban.setObjectName("bt_moban")
f_item.bt_moban_maodian.setObjectName("bt_moban_maodian")
f_item.led_name.setText(d["name"])
f_item.bt_arg.setStyleSheet("""
color: rgb(237,182,43);
border-color: rgb(237,182,43);
""")
f_item.bt_opendir.setStyleSheet("""
color: rgb(237,182,43);
border-color: rgb(237,182,43);
""")
f_item.bt_chuansong.setStyleSheet("""
#bt_chuansong[OK_chuansong="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
f_item.bt_moban.setStyleSheet("""
#bt_moban[OK_chuansongmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
f_item.bt_moban_maodian.setStyleSheet("""
#bt_moban_maodian[OK_maodianmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
if d["cb_is_checked"] == "True":
f_item.cb_is_checked.setChecked(True)
else:
f_item.cb_is_checked.setChecked(False)
idex = len(self.datas)
# f_item.led_name.editingFinished.connect(self.save)
f_item.bt_arg.clicked.connect(lambda: subprocess.Popen(['notepad.exe', d["arg"]]))
f_item.bt_opendir.clicked.connect(lambda: subprocess.Popen(['explorer', d["opendir"]]))
f_item.bt_chuansong.clicked.connect(functools.partial(self.clickd_bt_chuansong, idex))
f_item.bt_moban.clicked.connect(functools.partial(self.clickd_bt_moban, idex))
f_item.bt_moban_maodian.clicked.connect(functools.partial(self.clickd_bt_moban_maodian, idex))
f_item.bt_start.clicked.connect(functools.partial(self.clickd_bt_start, idex))
f_item.bt_del.clicked.connect(functools.partial(self.clickd_bt_del, idex))
f_item.bt_arg.setVisible(False)
f_item.bt_opendir.setVisible(False)
f_item.bt_chuansong.setVisible(False)
f_item.bt_moban.setVisible(False)
f_item.bt_moban_maodian.setVisible(False)
f_item.bt_start.setVisible(False)
f_item.bt_del.setVisible(False)
f_item.bt_moban.setToolTip(self.tanChu_img(dir_ + "/" + d["chuansongmoban"]))
f_item.bt_moban_maodian.setToolTip(self.tanChu_img(dir_ + "/" + d["maodianmoban"]))
d["f_item"] = f_item
self.datas.append(d)
self.g_box.addWidget(f_item, self.row, self.column)
# 将py模板文件复制一份
dir_py = os.path.join(d['opendir'], 'main.py')
dir_ini = os.path.join(d['opendir'], '参数.ini')
try:
if not os.path.exists(dir_py):
shutil.copy('datas/pymode.py', dir_py)
if not os.path.exists(dir_ini):
shutil.copy('datas/pymode.ini', dir_ini)
except:
pass
def tanChu_img(self, imgpath):
'''
弹出图片
:param imgpath:
:return:
'''
# ----------------------
try:
return f'''<img src='{imgpath}' > '''
except Exception as err:
return str(err)
def clickd_bt_chuansong(self, idex):
# dl_chuansong = DialogChuansong(self, idex)
# dl_chuansong.cbb_address.setCurrentText(self.datas[idex]['cbb_address'])
# dl_chuansong.exec_()
pass
def clickd_bt_moban(self, idex):
temp_path, ok = QFileDialog.getOpenFileName(self, "传送模板图片", "", "图片文件 (*.png)")
if ok:
dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
if not os.path.exists(dir_):
os.makedirs(dir_)
# 并且将jiaoben.ini文件也填入数据然后生成
# 将图片复制到指定目录
path = dir_ + "/传送模板.png"
shutil.copy(temp_path, path)
self.datas[idex]["chuansongmoban"] = "传送模板.png"
self.datas[idex]["f_item"].bt_moban.setProperty("OK_chuansongmoban", True)
self.datas[idex]["f_item"].bt_moban.setStyleSheet("""
#bt_moban[OK_chuansongmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
self.save_ini(dir_, self.datas[idex])
self.sg.mysig_tishi.emit(f"导入传送模板成功:{path}")
print(f"导入传送模板成功:{path}")
def clickd_bt_moban_maodian(self, idex):
temp_path, ok = QFileDialog.getOpenFileName(self, "锚点模板图片", "", "图片文件 (*.png)")
if ok:
dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
if not os.path.exists(dir_):
os.makedirs(dir_)
# 并且将jiaoben.ini文件也填入数据然后生成
# 将图片复制到指定目录
path = dir_ + "/锚点模板.png"
shutil.copy(temp_path, path)
self.datas[idex]["maodianmoban"] = "锚点模板.png"
self.datas[idex]["f_item"].bt_moban_maodian.setProperty("OK_maodianmoban", True)
self.datas[idex]["f_item"].bt_moban_maodian.setStyleSheet("""
#bt_moban_maodian[OK_maodianmoban="true"] {
color: rgb(237,182,43);
border-color: rgb(237,182,43);
}
""")
self.save_ini(dir_, self.datas[idex])
print(f"导入锚点模板成功:{path}")
self.sg.mysig_tishi.emit(f"导入锚点模板成功:{path}")
def clickd_bt_start(self, idex):
global ysyl
hwnd = win32gui.FindWindow("UnityWndClass", state.GAME_TITLE) # 替换成你实际的窗口句柄
if hwnd == 0:
print("错误:请先开游戏!")
self.sg.mysig_tishi.emit("错误:请先开游戏!")
return
left, top, right, bottom = win32gui.GetClientRect(hwnd)
width = right - left
height = bottom - top
if width != 1920 or height != 1080:
print("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080 建议重新设置分辨率")
self.sg.mysig_tishi.emit("错误:游戏分辨率不对!请确保游戏分辨率是1920x1080 建议重新设置分辨率")
return
self.save()
def run(idex):
ret = False
self.sg.mysig_mouse_through.emit(True)
state.计次_传送重试次数 = 0
state.状态_全局暂停 = False
self.datas[idex]['f_item'].cb_is_checked.setChecked(True)
for rt in range(self.run_times):
if self.action_runstartwith.isChecked():
print(f"任务包:{state.PATH_TASK} {rt + 1}/{self.run_times}")
while idex < len(self.datas):
if state.状态_检测中:
time.sleep(1)
continue
if self.datas[idex]['f_item'].cb_is_checked.isChecked():
ret, arg = self.run(idex)
if arg is None:
arg = "0"
if arg != "0":
idex = self.arg_jisuan(arg, idex)
if state.状态_循环开关 == False:
break
if ret == False:
if state.状态_需重新传送 == True:
state.计次_传送重试次数 += 1
if state.计次_传送重试次数 >= 5:
state.计次_传送重试次数 = 0
print("传送重试次数过多,放弃该任务!")
else:
print(f"传送重试{state.计次_传送重试次数}")
time.sleep(2)
continue
else:
break
if self.action_runstartwith.isChecked() == False:
break
state.计次_传送重试次数 = 0
idex += 1
if self.action_runstartwith.isChecked() == False:
break
if state.状态_循环开关 == False:
break
idex = 0
if state.状态_循环开关 != False:
if self.action_runstartwith.isChecked():
# 切下一个任务包 继续
next_tas = self.get_next_task()
if state.ON_NEXTPACK == 1 and next_tas != None:
print("切下一个任务包!")
state.PATH_TASK = next_tas
self.sg.mysig_mouse_through.emit(False)
self.sg.mysig_next_pack.emit()
return
if ret == True and state.ON_JIXING == 1:
ysyl.run_jixing()
if ret == True and state.ON_SHUTDOWN == 1:
self.sg.mysig_shutdown.emit()
self.sg.mysig_mouse_through.emit(False)
state.状态_循环开关 = False
t = threading.Thread(target=run, args=(idex,))
if state.python_var > 9:
t.daemon = True
else:
t.setDaemon(True)
t.start()
def clickd_bt_del(self, idex):
dir_ = os.path.join(state.PATH_TASK, self.datas[idex]["name"])
try:
# 删除整个文件夹
shutil.rmtree(dir_)
print(f"文件夹 {dir_} 已被删除")
self.update_tasks()
except Exception as e:
self.update_tasks()
print(f"删除文件夹时出现错误:{e}")
def on_timer_timeout(self):
# 每隔一段时间自动显示图片
self.sg.mysig_show_xunlu.emit()
self.sg.mysig_show_yolov.emit()
# 绑定信号槽
def connect_set(self):
# 创建一个顶级菜单
self.menu = self.menuBar()
self.menu_view = self.menu.addMenu("视图")
# 创建一个动作
self.action_isShow = QAction("显示检测结果窗口", self)
self.action_isShow.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F11))
self.action_isShow.triggered.connect(self.hotkey_isShow)
self.action_isShow.setCheckable(True)
self.action_isShow.setChecked(True)
self.menu_view.addAction(self.action_isShow)
# 打开预测窗口
self.hotkey_isShow()
self.sg = MySignal()
self.sg.mysig_show_xunlu.connect(self.show_xunlu)
self.sg.mysig_show_yolov.connect(self.show_yolov)
# 创建一个顶级菜单
self.menu_run = self.menu.addMenu("操作")
# 创建一个动作
self.action_run_times = QAction(f"设置:当前任务包 执行次数:{self.run_times}", self)
self.action_run_times.triggered.connect(self.hotkey_run_times)
self.menu_run.addAction(self.action_run_times)
def hotkey_run_times(self):
try:
text, ok = QInputDialog.getText(self, '执行次数', f'当前任务包:{state.PATH_TASK}\n请输入执行次数:',
text=str(self.run_times))
if not ok:
return
if text == "":
return
if int(text) < 1:
self.run_times = 1
else:
self.run_times = int(text)
self.action_run_times.setText(f"设置:当前任务包 执行次数:{self.run_times}")
print(f"设置:当前任务包 执行次数:{self.run_times}")
# 创建 ConfigParser 对象
config_main = configparser.ConfigParser()
# 添加节和键-值对
config_main['seting'] = {
"run_times": str(self.run_times),
}
# 写入配置到 INI 文件
with open(os.path.join(state.PATH_TASK, "细节参数.ini"), 'w') as configfile:
config_main.write(configfile)
except:
pass
def show_xunlu(self, image_path="./datas/111.png"):
# 加载本地图片
pixmap = QPixmap(image_path)
# 设置图片到 lb_xunlu 控件
self.fromshow.lb_xunlu.setPixmap(pixmap)
# 设置宽度固定,高度自适应
self.fromshow.window_width = int(self.fromshow.window_height * pixmap.width() / pixmap.height())
self.fromshow.lb_xunlu.setFixedHeight(self.fromshow.window_height)
self.fromshow.lb_xunlu.setFixedWidth(self.fromshow.window_width)
self.fromshow.lb_xunlu.setScaledContents(True)
def show_yolov(self, image_path="./datas/111.png"):
# 加载本地图片
pixmap1 = QPixmap(image_path)
# 设置图片到 lb_yolov 控件
self.fromshow.lb_yolov.setPixmap(pixmap1)
# 根据窗口宽度调整 lb_yolov 的位置
if self.fromshow.width() >= self.fromshow.lb_yolov.width() + self.fromshow.lb_xunlu.width():
self.fromshow.lb_yolov.move(self.fromshow.lb_xunlu.width(), 0)
else:
self.fromshow.lb_yolov.move(0, self.fromshow.lb_xunlu.height())
# 打开预测窗口
def hotkey_isShow(self):
# 尝试创建悬浮窗口实例
if not hasattr(self, "fromshow"):
self.fromshow = FormShow(self) # 创建悬浮窗口实例
self.addDockWidget(Qt.TopDockWidgetArea, self.fromshow) # 添加悬浮窗口到主窗口
# 更新布局
def update_layout(self):
try:
# 获取主窗口的宽度
width = self.centralWidget().width()
# 计算每行可以容纳的组件数量
num_per_row = (width) // (self.column_step * ratio)
if num_per_row < 1:
num_per_row = 1
# 清空当前布局
for i in reversed(range(self.g_box.count())):
self.g_box.itemAt(i).widget().setParent(None)
# 重新添加组件到网格布局
for i, data in enumerate(self.datas):
self.row = int(i // num_per_row)
self.column = int(i % num_per_row)
self.g_box.addWidget(data["f_item"], self.row, self.column)
except:
pass
# 设置主窗口样式
def set_ui(self):
# 设置主题样式为 Flatwhite
from qt_material import apply_stylesheet
apply_stylesheet(app, theme='dark_pink.xml')
# 设置窗口样式表
self.setStyleSheet("""
QScrollBar::handle:horizontal {
background-color: #A50F2C; /* 设置滑块颜色 */
}
QScrollBar::handle:horizontal:hover {
background-color: #FF1744; /* 设置滑块颜色 */
}
QPushButton:hover {
background-color: #DFC472; /* 设置颜色 */
}
QPlainTextEdit {
padding: 0px;
margin: 0px;
}
QPushButton {
padding: 0px;
margin: 1px;
}
""" + "font-family: {}; font-size: {}pt;".format(font.family(), font.pointSize()))
# 主程序入口
if __name__ == '__main__':
# 获取屏幕宽度和高度
screen_width = ctypes.windll.user32.GetSystemMetrics(0)
screen_height = ctypes.windll.user32.GetSystemMetrics(1)
# 初始化QT应用
app = QApplication(sys.argv)
# 计算分辨率比例
ratio = screen_width / 2560
# 设置全局字体大小
base_font_size = 13
new_font_size = int(base_font_size * ratio)
font = QFont("Arial", new_font_size)
item_width = 320
item_height = 240
item_height_min = 60
# 创建主窗口实例
window_main = MainWindow()
# 显示主窗口
window_main.show()
# 监听消息不关闭
sys.exit(app.exec_())
13、 关于多任务说明
首先,我们可以在同一个任务包下面定义多个任务
同一个任务包下的所有任务的类型必须一致
如果是多个任务包,那么type类型必须不一致,不然就串了