mirror of
https://github.com/goldenfishs/MRobot.git
synced 2025-07-27 08:49:01 +08:00
优化了ai个零件库
This commit is contained in:
parent
9fc6b4577a
commit
47e0b8419f
@ -8,7 +8,6 @@ from PyQt5.QtWidgets import QApplication
|
||||
from app.main_window import MainWindow
|
||||
|
||||
|
||||
|
||||
# 启用 DPI 缩放
|
||||
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
||||
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # 启用高 DPI 缩放
|
||||
|
21
ai.py
Normal file
21
ai.py
Normal file
@ -0,0 +1,21 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
url = "http://154.37.215.220:11434/api/generate"
|
||||
payload = {
|
||||
"model": "qwen3:0.6b",
|
||||
"prompt": "你好,介绍一下你自己"
|
||||
}
|
||||
response = requests.post(url, json=payload, stream=True)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
try:
|
||||
data = json.loads(line.decode('utf-8'))
|
||||
# 只输出 response 字段内容
|
||||
print(data.get("response", ""), end="", flush=True)
|
||||
# 如果 done 为 True,则换行
|
||||
if data.get("done", False):
|
||||
print()
|
||||
except Exception as e:
|
||||
pass # 忽略解析异常
|
@ -26,6 +26,7 @@ class AboutInterface(QWidget):
|
||||
layout.addWidget(card)
|
||||
|
||||
def on_check_update_clicked(self):
|
||||
try:
|
||||
latest = check_update(__version__)
|
||||
if latest:
|
||||
InfoBar.success(
|
||||
@ -35,7 +36,7 @@ class AboutInterface(QWidget):
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=5000
|
||||
)
|
||||
else:
|
||||
elif latest is None:
|
||||
InfoBar.info(
|
||||
title="已是最新版本",
|
||||
content="当前已是最新版本,无需更新。",
|
||||
@ -43,3 +44,11 @@ class AboutInterface(QWidget):
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=3000
|
||||
)
|
||||
except Exception:
|
||||
InfoBar.error(
|
||||
title="检查更新失败",
|
||||
content="无法获取最新版本,请检查网络连接。",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=4000
|
||||
)
|
182
app/ai_interface.py
Normal file
182
app/ai_interface.py
Normal file
@ -0,0 +1,182 @@
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
||||
|
||||
from qfluentwidgets import TextEdit, LineEdit, PushButton, TitleLabel, SubtitleLabel, FluentIcon, InfoBar, InfoBarPosition
|
||||
import requests
|
||||
import json
|
||||
|
||||
class AIWorker(QThread):
|
||||
response_signal = pyqtSignal(str)
|
||||
done_signal = pyqtSignal()
|
||||
error_signal = pyqtSignal(str) # 新增
|
||||
|
||||
def __init__(self, prompt, parent=None):
|
||||
super().__init__(parent)
|
||||
self.prompt = prompt
|
||||
|
||||
def run(self):
|
||||
url = "http://154.37.215.220:11434/api/generate"
|
||||
payload = {
|
||||
"model": "qwen3:0.6b",
|
||||
"prompt": self.prompt
|
||||
}
|
||||
try:
|
||||
response = requests.post(url, json=payload, stream=True, timeout=60)
|
||||
got_response = False
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
got_response = True
|
||||
try:
|
||||
data = json.loads(line.decode('utf-8'))
|
||||
self.response_signal.emit(data.get("response", ""))
|
||||
if data.get("done", False):
|
||||
self.done_signal.emit()
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if not got_response:
|
||||
self.error_signal.emit("服务器繁忙,请稍后再试。")
|
||||
self.done_signal.emit()
|
||||
except requests.ConnectionError:
|
||||
self.error_signal.emit("网络连接失败,请检查网络设置。")
|
||||
self.done_signal.emit()
|
||||
except Exception as e:
|
||||
self.error_signal.emit(f"[错误]: {str(e)}")
|
||||
self.done_signal.emit()
|
||||
|
||||
|
||||
class AIInterface(QWidget):
|
||||
MAX_HISTORY = 20 # 新增最大对话条数
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName("aiPage")
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(20, 20, 20, 20)
|
||||
self.layout.setSpacing(10)
|
||||
|
||||
self.title = SubtitleLabel("MRobot AI小助手", self)
|
||||
self.title.setAlignment(Qt.AlignCenter)
|
||||
self.layout.addWidget(self.title)
|
||||
|
||||
self.chat_display = TextEdit(self)
|
||||
self.chat_display.setReadOnly(True)
|
||||
|
||||
self.layout.addWidget(self.chat_display, stretch=1)
|
||||
|
||||
input_layout = QHBoxLayout()
|
||||
self.input_box = LineEdit(self)
|
||||
self.input_box.setPlaceholderText("请输入你的问题...")
|
||||
input_layout.addWidget(self.input_box, stretch=1)
|
||||
|
||||
# self.send_btn = PushButton("发送", self)
|
||||
self.send_btn = PushButton("发送", icon=FluentIcon.SEND, parent=self)
|
||||
|
||||
self.send_btn.setFixedWidth(80)
|
||||
input_layout.addWidget(self.send_btn)
|
||||
|
||||
self.layout.addLayout(input_layout)
|
||||
|
||||
self.send_btn.clicked.connect(self.send_message)
|
||||
self.input_box.returnPressed.connect(self.send_message)
|
||||
|
||||
self.worker = None
|
||||
self.is_waiting = False
|
||||
self.history = []
|
||||
self.chat_display.setText(
|
||||
"<b>MRobot:</b> 欢迎使用MRobot AI小助手!"
|
||||
)
|
||||
|
||||
def send_message(self):
|
||||
if self.is_waiting:
|
||||
return
|
||||
prompt = self.input_box.text().strip()
|
||||
if not prompt:
|
||||
return
|
||||
if len(prompt) > 1000:
|
||||
InfoBar.warning(
|
||||
title='警告',
|
||||
content="每条发送内容不能超过1000字,请精简后再发送。",
|
||||
orient=Qt.Horizontal,
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.BOTTOM,
|
||||
duration=-1,
|
||||
parent=self
|
||||
)
|
||||
return
|
||||
if len(self.history) >= self.MAX_HISTORY:
|
||||
InfoBar.warning(
|
||||
title='警告',
|
||||
content="对话条数已达上限,请清理历史或重新开始。",
|
||||
orient=Qt.Horizontal,
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.BOTTOM,
|
||||
duration=-1,
|
||||
parent=self
|
||||
)
|
||||
return
|
||||
self.append_chat("你", prompt)
|
||||
self.input_box.clear()
|
||||
self.append_chat("MRobot", "", new_line=False)
|
||||
self.is_waiting = True
|
||||
|
||||
# 只在首次对话时加入身份提示
|
||||
if not self.history:
|
||||
system_prompt = (
|
||||
"你是MRobot,是QUT青岛理工大学机器人战队的AI机器人。"
|
||||
"请以此身份与用户进行交流。"
|
||||
)
|
||||
else:
|
||||
system_prompt = ""
|
||||
|
||||
self.history.append({"role": "user", "content": prompt})
|
||||
context = system_prompt + "\n" if system_prompt else ""
|
||||
for msg in self.history:
|
||||
if msg["role"] == "user":
|
||||
context += f"你: {msg['content']}\n"
|
||||
else:
|
||||
context += f"AI: {msg['content']}\n"
|
||||
|
||||
self.worker = AIWorker(context)
|
||||
self.worker.response_signal.connect(self.stream_response)
|
||||
self.worker.done_signal.connect(self.finish_response)
|
||||
self.worker.error_signal.connect(self.show_error) # 新增
|
||||
self.worker.start()
|
||||
|
||||
|
||||
def append_chat(self, sender, message, new_line=True):
|
||||
if new_line:
|
||||
self.chat_display.append(f"<b>{sender}:</b> {message}")
|
||||
else:
|
||||
self.chat_display.append(f"<b>{sender}:</b> ")
|
||||
self.chat_display.moveCursor(self.chat_display.textCursor().End)
|
||||
# 新增:保存AI回复到历史
|
||||
if sender == "AI" and message:
|
||||
self.history.append({"role": "ai", "content": message})
|
||||
|
||||
def stream_response(self, text):
|
||||
cursor = self.chat_display.textCursor()
|
||||
cursor.movePosition(cursor.End)
|
||||
cursor.insertText(text)
|
||||
self.chat_display.setTextCursor(cursor)
|
||||
# 新增:流式保存AI回复
|
||||
if self.history and self.history[-1]["role"] == "ai":
|
||||
self.history[-1]["content"] += text
|
||||
elif text:
|
||||
self.history.append({"role": "ai", "content": text})
|
||||
|
||||
def finish_response(self):
|
||||
self.chat_display.append("") # 换行
|
||||
self.is_waiting = False
|
||||
|
||||
def show_error(self, msg): # 新增
|
||||
InfoBar.error(
|
||||
title='失败',
|
||||
content=msg,
|
||||
orient=Qt.Vertical,
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=-1,
|
||||
parent=self
|
||||
)
|
||||
self.is_waiting = False
|
@ -3,6 +3,7 @@ from PyQt5.QtCore import Qt
|
||||
from qfluentwidgets import PushSettingCard, FluentIcon, TabBar
|
||||
|
||||
from .function_fit_interface import FunctionFitInterface
|
||||
from .ai_interface import AIInterface
|
||||
|
||||
class MiniToolInterface(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
@ -26,14 +27,22 @@ class MiniToolInterface(QWidget):
|
||||
mainLayout = QVBoxLayout(self.mainPage)
|
||||
mainLayout.setAlignment(Qt.AlignTop) # 卡片靠顶部
|
||||
self.card = PushSettingCard(
|
||||
text="▶启动",
|
||||
text="▶ 启动",
|
||||
icon=FluentIcon.UNIT,
|
||||
title="曲线拟合工具",
|
||||
content="简单的曲线拟合工具,支持多种函数类型",
|
||||
)
|
||||
mainLayout.addWidget(self.card)
|
||||
self.mainPage.setLayout(mainLayout)
|
||||
|
||||
self.mainPage.setLayout(mainLayout)
|
||||
self.aiCard = PushSettingCard(
|
||||
text="▶ 启动",
|
||||
icon=FluentIcon.ROBOT,
|
||||
title="MRobot AI助手",
|
||||
content="与 MRobot 进行图一乐交流, 使用开源模型qwen3:0.6b。",
|
||||
)
|
||||
mainLayout.addWidget(self.aiCard)
|
||||
self.aiCard.clicked.connect(self.open_ai_tab)
|
||||
# 添加主页面到堆叠窗口
|
||||
self.addSubInterface(self.mainPage, "mainPage", "工具箱主页")
|
||||
|
||||
@ -80,3 +89,16 @@ class MiniToolInterface(QWidget):
|
||||
self.addSubInterface(fit_page, "fitPage", "曲线拟合")
|
||||
self.stackedWidget.setCurrentWidget(fit_page)
|
||||
self.tabBar.setCurrentTab("fitPage")
|
||||
|
||||
def open_ai_tab(self):
|
||||
# 检查是否已存在标签页,避免重复添加
|
||||
for i in range(self.stackedWidget.count()):
|
||||
widget = self.stackedWidget.widget(i)
|
||||
if widget.objectName() == "aiPage":
|
||||
self.stackedWidget.setCurrentWidget(widget)
|
||||
self.tabBar.setCurrentTab("aiPage")
|
||||
return
|
||||
ai_page = AIInterface(self)
|
||||
self.addSubInterface(ai_page, "aiPage", "AI问答")
|
||||
self.stackedWidget.setCurrentWidget(ai_page)
|
||||
self.tabBar.setCurrentTab("aiPage")
|
||||
|
@ -1,51 +1,12 @@
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget
|
||||
from qfluentwidgets import SubtitleLabel, BodyLabel, HorizontalSeparator, PushButton, TreeWidget, ProgressBar, Dialog, InfoBar, InfoBarPosition, FluentIcon
|
||||
from qfluentwidgets import SubtitleLabel, BodyLabel, HorizontalSeparator, PushButton, TreeWidget, ProgressBar, Dialog, InfoBar, InfoBarPosition, FluentIcon, ProgressRing, Dialog
|
||||
import requests
|
||||
import shutil
|
||||
import os
|
||||
from .tools.part_download import DownloadThread # 新增导入
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
class DownloadThread(QThread):
|
||||
progressChanged = pyqtSignal(int)
|
||||
finished = pyqtSignal(list, list) # success, fail
|
||||
|
||||
def __init__(self, files, server_url, secret_key, local_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.files = files
|
||||
self.server_url = server_url
|
||||
self.secret_key = secret_key
|
||||
self.local_dir = local_dir
|
||||
|
||||
def run(self):
|
||||
success, fail = [], []
|
||||
total = len(self.files)
|
||||
max_retry = 3
|
||||
for idx, rel_path in enumerate(self.files):
|
||||
retry = 0
|
||||
while retry < max_retry:
|
||||
try:
|
||||
rel_path_unix = rel_path.replace("\\", "/")
|
||||
encoded_path = quote(rel_path_unix)
|
||||
url = f"{self.server_url}/download/{encoded_path}"
|
||||
params = {"key": self.secret_key}
|
||||
resp = requests.get(url, params=params, stream=True, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
local_path = os.path.join(self.local_dir, rel_path)
|
||||
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
||||
with open(local_path, "wb") as f:
|
||||
shutil.copyfileobj(resp.raw, f)
|
||||
success.append(rel_path)
|
||||
break
|
||||
else:
|
||||
retry += 1
|
||||
except Exception:
|
||||
retry += 1
|
||||
else:
|
||||
fail.append(rel_path)
|
||||
self.progressChanged.emit(int((idx + 1) / total * 100))
|
||||
self.finished.emit(success, fail)
|
||||
|
||||
class PartLibraryInterface(QWidget):
|
||||
SERVER_URL = "http://154.37.215.220:5000"
|
||||
SECRET_KEY = "MRobot_Download"
|
||||
@ -151,41 +112,64 @@ class PartLibraryInterface(QWidget):
|
||||
def download_selected_files(self):
|
||||
files = self.get_checked_files()
|
||||
if not files:
|
||||
InfoBar.info(
|
||||
title="提示",
|
||||
content="请先勾选要下载的文件。",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000
|
||||
)
|
||||
return
|
||||
|
||||
self.progress_dialog = Dialog(
|
||||
title="正在下载",
|
||||
content="正在下载选中文件,请稍候...",
|
||||
dialog = Dialog(
|
||||
title="温馨提示",
|
||||
content="请先勾选需要下载的文件。",
|
||||
parent=self
|
||||
)
|
||||
self.progress_bar = ProgressBar()
|
||||
self.progress_bar.setValue(0)
|
||||
self.progress_dialog.textLayout.addWidget(self.progress_bar)
|
||||
self.progress_dialog.show()
|
||||
dialog.yesButton.setText("知道啦")
|
||||
dialog.cancelButton.hide()
|
||||
dialog.exec()
|
||||
return
|
||||
|
||||
# 创建进度环
|
||||
self.progress_ring = ProgressRing()
|
||||
self.progress_ring.setRange(0, 100)
|
||||
self.progress_ring.setValue(0)
|
||||
self.progress_ring.setTextVisible(True)
|
||||
self.progress_ring.setFixedSize(32, 32)
|
||||
self.progress_ring.setStrokeWidth(4)
|
||||
|
||||
# 展示消息条(关闭按钮即中断下载)
|
||||
self.info_bar = InfoBar(
|
||||
icon=FluentIcon.DOWNLOAD,
|
||||
title="正在下载",
|
||||
content="正在下载选中文件...",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=-1 # 不自动消失
|
||||
)
|
||||
self.info_bar.addWidget(self.progress_ring)
|
||||
self.info_bar.closeButton.clicked.connect(self.stop_download) # 关闭即中断下载
|
||||
self.info_bar.show()
|
||||
|
||||
# 启动下载线程
|
||||
self.download_thread = DownloadThread(
|
||||
files, self.SERVER_URL, self.SECRET_KEY, self.LOCAL_LIB_DIR
|
||||
)
|
||||
self.download_thread.progressChanged.connect(self.progress_bar.setValue)
|
||||
self.download_thread.progressChanged.connect(self.progress_ring.setValue)
|
||||
self.download_thread.finished.connect(self.on_download_finished)
|
||||
self.download_thread.finished.connect(self.download_thread.deleteLater)
|
||||
self.download_thread.start()
|
||||
|
||||
def on_download_finished(self, success, fail):
|
||||
self.progress_dialog.close()
|
||||
msg = f"成功下载: {len(success)} 个文件\n失败: {len(fail)} 个文件"
|
||||
dialog = Dialog(
|
||||
title="下载结果",
|
||||
content=msg,
|
||||
parent=self
|
||||
def stop_download(self):
|
||||
if hasattr(self, "download_thread") and self.download_thread.isRunning():
|
||||
self.download_thread.terminate()
|
||||
self.download_thread.wait()
|
||||
self.info_bar.close()
|
||||
InfoBar.warning(
|
||||
title="下载已中断",
|
||||
content="已手动中断下载任务。",
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=2000
|
||||
)
|
||||
|
||||
def on_download_finished(self, success, fail):
|
||||
self.info_bar.close()
|
||||
msg = f"成功下载:{len(success)} 个文件,失败:{len(fail)} 个文件"
|
||||
|
||||
# 创建“打开文件夹”按钮
|
||||
open_btn = PushButton("打开文件夹")
|
||||
def open_folder():
|
||||
folder = os.path.abspath(self.LOCAL_LIB_DIR)
|
||||
@ -196,10 +180,18 @@ class PartLibraryInterface(QWidget):
|
||||
subprocess.call(["explorer", folder])
|
||||
else:
|
||||
subprocess.call(["xdg-open", folder])
|
||||
dialog.close()
|
||||
|
||||
# 展示成功消息条,自动消失
|
||||
self.result_bar = InfoBar.success(
|
||||
title="下载完成",
|
||||
content=msg,
|
||||
parent=self,
|
||||
position=InfoBarPosition.TOP,
|
||||
duration=4000 # 4秒后自动消失
|
||||
)
|
||||
self.result_bar.addWidget(open_btn)
|
||||
open_btn.clicked.connect(open_folder)
|
||||
dialog.textLayout.addWidget(open_btn)
|
||||
dialog.exec()
|
||||
self.result_bar.show()
|
||||
|
||||
def open_local_lib(self):
|
||||
folder = os.path.abspath(self.LOCAL_LIB_DIR)
|
||||
|
@ -2,19 +2,13 @@ import requests
|
||||
from packaging.version import parse as vparse
|
||||
|
||||
def check_update(local_version, repo="goldenfishs/MRobot"):
|
||||
"""
|
||||
检查 GitHub 上是否有新版本
|
||||
:param local_version: 当前版本号字符串,如 "1.0.2"
|
||||
:param repo: 仓库名,格式 "用户名/仓库名"
|
||||
:return: 最新版本号字符串(如果有新版本),否则 None
|
||||
"""
|
||||
url = f"https://api.github.com/repos/{repo}/releases/latest"
|
||||
try:
|
||||
resp = requests.get(url, timeout=5)
|
||||
if resp.status_code == 200:
|
||||
latest = resp.json()["tag_name"].lstrip("v")
|
||||
if vparse(latest) > vparse(local_version):
|
||||
return latest
|
||||
except Exception as e:
|
||||
print(f"检查更新失败: {e}")
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
raise RuntimeError("GitHub API 请求失败")
|
45
app/tools/part_download.py
Normal file
45
app/tools/part_download.py
Normal file
@ -0,0 +1,45 @@
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
import requests
|
||||
import shutil
|
||||
import os
|
||||
from urllib.parse import quote
|
||||
|
||||
class DownloadThread(QThread):
|
||||
progressChanged = pyqtSignal(int)
|
||||
finished = pyqtSignal(list, list) # success, fail
|
||||
|
||||
def __init__(self, files, server_url, secret_key, local_dir, parent=None):
|
||||
super().__init__(parent)
|
||||
self.files = files
|
||||
self.server_url = server_url
|
||||
self.secret_key = secret_key
|
||||
self.local_dir = local_dir
|
||||
|
||||
def run(self):
|
||||
success, fail = [], []
|
||||
total = len(self.files)
|
||||
max_retry = 3
|
||||
for idx, rel_path in enumerate(self.files):
|
||||
retry = 0
|
||||
while retry < max_retry:
|
||||
try:
|
||||
rel_path_unix = rel_path.replace("\\", "/")
|
||||
encoded_path = quote(rel_path_unix)
|
||||
url = f"{self.server_url}/download/{encoded_path}"
|
||||
params = {"key": self.secret_key}
|
||||
resp = requests.get(url, params=params, stream=True, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
local_path = os.path.join(self.local_dir, rel_path)
|
||||
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
||||
with open(local_path, "wb") as f:
|
||||
shutil.copyfileobj(resp.raw, f)
|
||||
success.append(rel_path)
|
||||
break
|
||||
else:
|
||||
retry += 1
|
||||
except Exception:
|
||||
retry += 1
|
||||
else:
|
||||
fail.append(rel_path)
|
||||
self.progressChanged.emit(int((idx + 1) / total * 100))
|
||||
self.finished.emit(success, fail)
|
114
app/tools/task_config.py
Normal file
114
app/tools/task_config.py
Normal file
@ -0,0 +1,114 @@
|
||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout
|
||||
from qfluentwidgets import TitleLabel, BodyLabel, TableWidget, PushButton, SubtitleLabel, SpinBox, InfoBar, InfoBarPosition, LineEdit, CheckBox
|
||||
from PyQt5.QtCore import Qt
|
||||
import yaml
|
||||
import os
|
||||
|
||||
class TaskConfigDialog(QDialog):
|
||||
def __init__(self, parent=None, config_path=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("任务配置")
|
||||
self.resize(900, 480)
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(32, 32, 32, 32)
|
||||
layout.setSpacing(18)
|
||||
|
||||
layout.addWidget(TitleLabel("FreeRTOS 任务配置"))
|
||||
layout.addWidget(BodyLabel("请添加并配置您的任务参数,支持频率控制与描述。"))
|
||||
|
||||
self.table = TableWidget(self)
|
||||
self.table.setColumnCount(6)
|
||||
self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述", "频率控制"])
|
||||
self.table.horizontalHeader().setSectionResizeMode(0, self.table.horizontalHeader().Stretch)
|
||||
self.table.horizontalHeader().setSectionResizeMode(1, self.table.horizontalHeader().ResizeToContents)
|
||||
self.table.horizontalHeader().setSectionResizeMode(2, self.table.horizontalHeader().ResizeToContents)
|
||||
self.table.horizontalHeader().setSectionResizeMode(3, self.table.horizontalHeader().ResizeToContents)
|
||||
self.table.horizontalHeader().setSectionResizeMode(4, self.table.horizontalHeader().Stretch)
|
||||
self.table.horizontalHeader().setSectionResizeMode(5, self.table.horizontalHeader().ResizeToContents)
|
||||
self.table.setMinimumHeight(260)
|
||||
layout.addWidget(self.table)
|
||||
|
||||
btn_layout = QHBoxLayout()
|
||||
self.add_btn = PushButton("添加任务")
|
||||
self.del_btn = PushButton("删除选中")
|
||||
self.ok_btn = PushButton("生成")
|
||||
self.cancel_btn = PushButton("取消")
|
||||
btn_layout.addWidget(self.add_btn)
|
||||
btn_layout.addWidget(self.del_btn)
|
||||
btn_layout.addStretch()
|
||||
btn_layout.addWidget(self.ok_btn)
|
||||
btn_layout.addWidget(self.cancel_btn)
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
self.add_btn.clicked.connect(self.add_row)
|
||||
self.del_btn.clicked.connect(self.del_row)
|
||||
self.ok_btn.clicked.connect(self.accept)
|
||||
self.cancel_btn.clicked.connect(self.reject)
|
||||
|
||||
# 自动读取配置文件
|
||||
if config_path and os.path.exists(config_path):
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
tasks = yaml.safe_load(f)
|
||||
if tasks:
|
||||
for t in tasks:
|
||||
self.add_row(t)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def add_row(self, task=None):
|
||||
row = self.table.rowCount()
|
||||
self.table.insertRow(row)
|
||||
name = LineEdit(task.get("name", f"Task{row+1}") if task else f"Task{row+1}")
|
||||
freq = SpinBox()
|
||||
freq.setRange(1, 10000)
|
||||
freq.setValue(task.get("frequency", 500) if task else 500)
|
||||
delay = SpinBox()
|
||||
delay.setRange(0, 10000)
|
||||
delay.setValue(task.get("delay", 0) if task else 0)
|
||||
stack = SpinBox()
|
||||
stack.setRange(128, 8192)
|
||||
stack.setSingleStep(128)
|
||||
stack.setValue(task.get("stack", 256) if task else 256)
|
||||
desc = LineEdit(task.get("description", "") if task else "请填写任务描述")
|
||||
freq_ctrl = CheckBox("启用")
|
||||
freq_ctrl.setChecked(task.get("freq_control", True) if task else True)
|
||||
|
||||
self.table.setCellWidget(row, 0, name)
|
||||
self.table.setCellWidget(row, 1, freq)
|
||||
self.table.setCellWidget(row, 2, delay)
|
||||
self.table.setCellWidget(row, 3, stack)
|
||||
self.table.setCellWidget(row, 4, desc)
|
||||
self.table.setCellWidget(row, 5, freq_ctrl)
|
||||
|
||||
def del_row(self):
|
||||
selected = self.table.selectedItems()
|
||||
if selected:
|
||||
rows = set(item.row() for item in selected)
|
||||
for row in sorted(rows, reverse=True):
|
||||
self.table.removeRow(row)
|
||||
|
||||
def get_tasks(self):
|
||||
tasks = []
|
||||
for row in range(self.table.rowCount()):
|
||||
name = self.table.cellWidget(row, 0).text().strip()
|
||||
freq = self.table.cellWidget(row, 1).value()
|
||||
delay = self.table.cellWidget(row, 2).value()
|
||||
stack = self.table.cellWidget(row, 3).value()
|
||||
desc = self.table.cellWidget(row, 4).text().strip()
|
||||
freq_ctrl = self.table.cellWidget(row, 5).isChecked()
|
||||
# 校验 stack 必须为 128*2^n
|
||||
if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0:
|
||||
raise ValueError(f"第{row+1}行任务“{name}”的堆栈大小必须为128、256、512、1024等(128*2^n)")
|
||||
task = {
|
||||
"name": name,
|
||||
"function": f"Task_{name}",
|
||||
"delay": delay,
|
||||
"stack": stack,
|
||||
"description": desc,
|
||||
"freq_control": freq_ctrl
|
||||
}
|
||||
if freq_ctrl:
|
||||
task["frequency"] = freq
|
||||
tasks.append(task)
|
||||
return tasks
|
BIN
mech_lib/.DS_Store
vendored
Normal file
BIN
mech_lib/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT
Normal file
BIN
mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT
Normal file
Binary file not shown.
BIN
mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT
Normal file
BIN
mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT
Normal file
Binary file not shown.
BIN
mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT
Normal file
BIN
mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT
Normal file
Binary file not shown.
BIN
mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT
Normal file
BIN
mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT
Normal file
Binary file not shown.
BIN
mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT
Normal file
BIN
mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT
Normal file
Binary file not shown.
BIN
mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT
Normal file
BIN
mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user