Compare commits

...

4 Commits

Author SHA1 Message Date
7951dae760 修改版本 2025-07-26 01:24:50 +08:00
501a9ddff4 重构完代码结构了 2025-07-26 01:22:52 +08:00
47e0b8419f 优化了ai个零件库 2025-07-25 16:39:32 +08:00
9fc6b4577a 继续改 2025-07-25 02:54:03 +08:00
104 changed files with 881 additions and 1996 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -8,10 +8,11 @@ OutputBaseFilename=MRobotInstaller
[Files]
Source: "dist\MRobot.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "img\*"; DestDir: "{app}\img"; Flags: ignoreversion recursesubdirs
Source: "User_code\*"; DestDir: "{app}\User_code"; Flags: ignoreversion recursesubdirs
Source: "mech_lib\*"; DestDir: "{app}\mech_lib"; Flags: ignoreversion recursesubdirs
Source: "assets\logo\*"; DestDir: "{app}\assets\logo"; Flags: ignoreversion recursesubdirs
Source: "assets\User_code\*"; DestDir: "{app}\assets\User_code"; Flags: ignoreversion recursesubdirs
Source: "assets\mech_lib\*"; DestDir: "{app}\assets\mech_lib"; Flags: ignoreversion recursesubdirs
Source: "assets\logo\M.ico"; DestDir: "{app}\assets\logo"; Flags: ignoreversion
[Icons]
Name: "{group}\MRobot"; Filename: "{app}\MRobot.exe"; IconFilename: "{app}\img\M.ico"
Name: "{userdesktop}\MRobot"; Filename: "{app}\MRobot.exe"; IconFilename: "{app}\img\M.ico"
Name: "{group}\MRobot"; Filename: "{app}\MRobot.exe"; IconFilename: "{app}\assets\logo\M.ico"
Name: "{userdesktop}\MRobot"; Filename: "{app}\MRobot.exe"; IconFilename: "{app}\assets\logo\M.ico"

1719
MRobot.py

File diff suppressed because it is too large Load Diff

View File

@ -122,4 +122,11 @@ pyinstaller --noconfirm --onefile --windowed --add-data "img;img" --add-data "Us
pyinstaller MRobot.py
pyinstaller --noconfirm --onefile --windowed --icon=img/M.ico --add-data "img;img" --add-data "User_code;User_code" --add-data "mech_lib;mech_lib" MRobot.py
pyinstaller --noconfirm --onefile --windowed --icon=img/M.ico --add-data "img;img" --add-data "User_code;User_code" --add-data "mech_lib;mech_lib" MRobot.py
pyinstaller --onefile --windowed --icon=assets/logo/M.ico --add-data "assets/logo:assets/logo" --add-data "assets/User_code:assets/User_code" --add-data "assets/mech_lib:assets/mech_lib" --collect-all pandas MRobot.py
pyinstaller --onefile --windowed --icon=assets/logo/M.ico --add-data "assets/logo:assets/logo" --add-data "assets/User_code:assets/User_code" --add-data "assets/mech_lib:assets/mech_lib" MRobot.py
python3 -m pyinstaller MRobot.py --onefile --windowed --add-data "assets:assets" --add-data "app:app" --add-data "app/tools:app/tools"

22
app.py
View File

@ -1,22 +0,0 @@
import os
import sys
# 将当前工作目录设置为程序所在的目录,确保无论从哪里执行,其工作目录都正确设置为程序本身的位置,避免路径错误。
os.chdir(os.path.dirname(sys.executable) if getattr(sys, 'frozen', False)else os.path.dirname(os.path.abspath(__file__)))
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from app.main_window import MainWindow
# 启用 DPI 缩放
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # 启用高 DPI 缩放
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) # 使用高 DPI 图标
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setAttribute(Qt.AA_DontCreateNativeWidgetSiblings) # 避免创建原生窗口小部件的兄弟窗口
w = MainWindow()
sys.exit(app.exec_()) # 启动应用程序并进入主事件循环
# 注意:在 PyQt5 中exec_() 是一个阻塞调用,直到应用程序退出。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

65
app/about_interface.py Normal file
View File

@ -0,0 +1,65 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMessageBox
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QDesktopServices
from qfluentwidgets import PrimaryPushSettingCard, FluentIcon
from qfluentwidgets import InfoBar, InfoBarPosition, SubtitleLabel
from .function_fit_interface import FunctionFitInterface
from app.tools.check_update import check_update
__version__ = "1.0.2"
class AboutInterface(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("aboutInterface")
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignTop)
layout.setContentsMargins(20, 30, 20, 20) # 添加边距
title = SubtitleLabel("MRobot 帮助页面", self)
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
# 添加空间隔
layout.addSpacing(10)
card = PrimaryPushSettingCard(
text="检查更新",
icon=FluentIcon.DOWNLOAD,
title="更新",
content=f"MRobot_Toolbox 当前版本:{__version__}",
)
card.clicked.connect(self.on_check_update_clicked)
layout.addWidget(card)
def on_check_update_clicked(self):
try:
latest = check_update(__version__)
if latest:
# 直接用浏览器打开下载链接
QDesktopServices.openUrl(QUrl("https://github.com/goldenfishs/MRobot/releases/latest"))
InfoBar.success(
title="发现新版本",
content=f"检测到新版本:{latest},已为你打开下载页面。",
parent=self,
position=InfoBarPosition.TOP,
duration=5000
)
elif latest is None:
InfoBar.info(
title="已是最新版本",
content="当前已是最新版本,无需更新。",
parent=self,
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
View 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

View File

@ -6,8 +6,10 @@ import os
import requests
import zipfile
import io
import re
import shutil
import yaml
import textwrap
from jinja2 import Template
class IocConfig:
@ -65,19 +67,16 @@ class DataInterface(QWidget):
# 主标题
title = TitleLabel("MRobot 代码生成")
title.setAlignment(Qt.AlignCenter)
title.setStyleSheet("font-size: 36px; font-weight: bold; color: #2d7d9a;")
content_layout.addWidget(title)
# 副标题
subtitle = BodyLabel("请选择您的由CUBEMX生成的工程路径.ico所在的目录然后开启代码之旅")
subtitle.setAlignment(Qt.AlignCenter)
subtitle.setStyleSheet("font-size: 16px; color: #4a6fa5;")
content_layout.addWidget(subtitle)
# 简要说明
desc = BodyLabel("支持自动配置和生成任务自主选择模块代码倒入自动识别cubemx配置")
desc.setAlignment(Qt.AlignCenter)
desc.setStyleSheet("font-size: 14px; color: #6b7b8c;")
content_layout.addWidget(desc)
content_layout.addSpacing(18)
@ -85,14 +84,12 @@ class DataInterface(QWidget):
# 选择项目路径按钮
self.choose_btn = PushButton(FluentIcon.FOLDER, "选择项目路径")
self.choose_btn.setFixedWidth(200)
self.choose_btn.setStyleSheet("font-size: 17px;")
self.choose_btn.clicked.connect(self.choose_project_folder)
content_layout.addWidget(self.choose_btn, alignment=Qt.AlignmentFlag.AlignCenter)
# 更新代码库按钮
self.update_template_btn = PushButton(FluentIcon.SYNC, "更新代码库")
self.update_template_btn.setFixedWidth(200)
self.update_template_btn.setStyleSheet("font-size: 17px;")
self.update_template_btn.clicked.connect(self.update_user_template)
content_layout.addWidget(self.update_template_btn, alignment=Qt.AlignmentFlag.AlignCenter)
@ -258,7 +255,7 @@ class DataInterface(QWidget):
def update_user_template(self):
url = "http://gitea.qutrobot.top/robofish/MRobot/archive/User_code.zip"
local_dir = "User_code"
local_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../assets/User_code")
try:
resp = requests.get(url, timeout=30)
resp.raise_for_status()
@ -292,7 +289,7 @@ class DataInterface(QWidget):
def show_user_code_files(self):
self.file_tree.clear()
base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../User_code")
base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../assets/User_code")
user_dir = os.path.join(self.project_path, "User")
sub_dirs = ["bsp", "component", "device", "module"]
@ -412,7 +409,7 @@ class DataInterface(QWidget):
def generate_code(self):
import shutil
base_dir = "User_code"
base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../assets/User_code")
user_dir = os.path.join(self.project_path, "User")
copied = []
files = self.get_checked_files()
@ -695,7 +692,7 @@ class DataInterface(QWidget):
def generate_task_code(self, task_list):
base_dir = os.path.dirname(os.path.abspath(__file__))
template_dir = os.path.join(base_dir, "User_code", "task")
template_dir = os.path.join(base_dir, "../assets/User_code/task")
output_dir = os.path.join(self.project_path, "User", "task")
os.makedirs(output_dir, exist_ok=True)

View File

@ -1,8 +1,19 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QFileDialog, QLabel
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QTableWidgetItem, QApplication
from PyQt5.QtCore import Qt
from qfluentwidgets import TitleLabel, BodyLabel
import pandas as pd
import io
from qfluentwidgets import TitleLabel, BodyLabel, TableWidget, PushButton, SubtitleLabel, SpinBox, ComboBox, InfoBar,InfoBarPosition, FluentIcon
from openpyxl import load_workbook, Workbook
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWebEngineWidgets import QWebEngineView
import plotly.graph_objs as go
import plotly.io as pio
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'Source Han Sans', 'STHeiti', 'Heiti TC']
matplotlib.rcParams['axes.unicode_minus'] = False
class FunctionFitInterface(QWidget):
def __init__(self, parent=None):
@ -10,71 +21,153 @@ class FunctionFitInterface(QWidget):
self.setObjectName("functionFitInterface")
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(32, 32, 32, 32)
main_layout.setSpacing(24)
# 左侧:数据输入区
left_layout = QVBoxLayout()
left_layout.setSpacing(16)
left_layout.addWidget(TitleLabel("数据输入/导入"))
self.dataEdit = QTextEdit()
self.dataEdit.setPlaceholderText("输入数据每行格式x,y")
left_layout.addWidget(self.dataEdit)
self.dataTable = TableWidget(self)
self.dataTable.setColumnCount(2)
self.dataTable.setHorizontalHeaderLabels(["x", "y"])
self.dataTable.setColumnWidth(0, 125)
self.dataTable.setColumnWidth(1, 125)
left_layout.addWidget(self.dataTable)
btn_layout = QHBoxLayout()
import_btn = QPushButton("导入 Excel")
add_row_btn = PushButton("添加一行")
add_row_btn.clicked.connect(self.add_row)
del_row_btn = PushButton("删除选中行") # 新增按钮
del_row_btn.clicked.connect(self.delete_selected_row) # 绑定槽函数
btn_layout.addWidget(add_row_btn)
btn_layout.addWidget(del_row_btn) # 添加到布局
left_layout.addLayout(btn_layout)
btn_layout = QHBoxLayout()
import_btn = PushButton("导入 Excel")
import_btn.clicked.connect(self.import_excel)
export_btn = QPushButton("导出 Excel")
export_btn = PushButton("导出 Excel")
export_btn.clicked.connect(self.export_excel)
btn_layout.addWidget(import_btn)
btn_layout.addWidget(export_btn)
left_layout.addLayout(btn_layout)
fit_btn = QPushButton("拟合并绘图")
fit_btn.clicked.connect(self.fit_and_plot)
left_layout.addWidget(fit_btn)
self.dataTable.setMinimumWidth(280)
self.dataTable.setMaximumWidth(280)
main_layout.addLayout(left_layout, 1)
self.add_row()
# 右侧:图像展示区
right_layout = QVBoxLayout()
right_layout.setSpacing(16)
right_layout.addWidget(TitleLabel("函数拟合图像"))
right_layout.setSpacing(12)
right_layout.addWidget(SubtitleLabel("函数图像预览"))
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
self.figure, self.ax = plt.subplots()
self.figure = Figure(figsize=(5, 4))
self.canvas = FigureCanvas(self.figure)
right_layout.addWidget(self.canvas, stretch=1)
self.resultLabel = BodyLabel("")
self.resultLabel.setWordWrap(True) # 自动换行
right_layout.addWidget(self.resultLabel)
# 拟合阶数和输出语言选择(合并到同一行)
options_layout = QHBoxLayout()
self.spinBox = SpinBox()
self.spinBox.setRange(1, 10)
self.spinBox.setValue(2)
options_layout.addWidget(SubtitleLabel("拟合阶数"))
options_layout.addWidget(self.spinBox)
self.langBox = ComboBox()
self.langBox.addItems(["C/C++", "Python"])
options_layout.addWidget(SubtitleLabel("输出语言"))
options_layout.addWidget(self.langBox)
right_layout.addLayout(options_layout)
# 代码显示和复制按钮
self.codeLabel = BodyLabel("")
self.codeLabel.setWordWrap(True) # 自动换行
right_layout.addWidget(self.codeLabel)
btn_layout = QHBoxLayout() # 新增一行布局
fit_btn = PushButton(FluentIcon.UNIT,"拟合并绘图")
fit_btn.clicked.connect(self.fit_and_plot)
btn_layout.addWidget(fit_btn)
copy_btn = PushButton(FluentIcon.COPY, "复制代码")
copy_btn.clicked.connect(self.copy_code)
btn_layout.addWidget(copy_btn)
right_layout.addLayout(btn_layout)
main_layout.addLayout(right_layout, 2)
# 默认显示空图像
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.set_xlabel('x')
ax.set_ylabel('y')
self.canvas.draw()
def add_row(self):
row = self.dataTable.rowCount()
self.dataTable.insertRow(row)
# 可选:初始化为空字符串
self.dataTable.setItem(row, 0, QTableWidgetItem(""))
self.dataTable.setItem(row, 1, QTableWidgetItem(""))
def delete_selected_row(self):
selected = self.dataTable.selectedItems()
if selected:
rows = set(item.row() for item in selected)
for row in sorted(rows, reverse=True):
self.dataTable.removeRow(row)
def import_excel(self):
path, _ = QFileDialog.getOpenFileName(self, "导入 Excel", "", "Excel Files (*.xlsx *.xls)")
path, _ = QFileDialog.getOpenFileName(self, "导入 Excel", "", "Excel Files (*.xlsx)")
if path:
df = pd.read_excel(path)
text = "\n".join(f"{row[0]},{row[1]}" for row in df.values)
self.dataEdit.setText(text)
wb = load_workbook(path)
ws = wb.active
self.dataTable.setRowCount(0)
for row_data in ws.iter_rows(min_row=2, values_only=True): # 跳过表头
row = self.dataTable.rowCount()
self.dataTable.insertRow(row)
for col, value in enumerate(row_data[:2]):
item = QTableWidgetItem(str(value) if value is not None else "")
self.dataTable.setItem(row, col, item)
def export_excel(self):
path, _ = QFileDialog.getSaveFileName(self, "导出 Excel", "", "Excel Files (*.xlsx)")
if path:
data = self.parse_data()
if data is not None:
df = pd.DataFrame(data, columns=["x", "y"])
df.to_excel(path, index=False)
wb = Workbook()
ws = wb.active
ws.append(["x", "y"])
for row in data:
ws.append(row)
wb.save(path)
def parse_data(self):
lines = self.dataEdit.toPlainText().strip().split('\n')
data = []
for line in lines:
row_count = self.dataTable.rowCount()
for row in range(row_count):
try:
x, y = map(float, line.split(','))
x_item = self.dataTable.item(row, 0)
y_item = self.dataTable.item(row, 1)
if x_item is None or y_item is None:
continue
x = float(x_item.text())
y = float(y_item.text())
data.append([x, y])
except Exception:
continue
@ -84,18 +177,84 @@ class FunctionFitInterface(QWidget):
data = self.parse_data()
if not data:
self.resultLabel.setText("数据格式错误或为空")
self.codeLabel.setText("")
return
import numpy as np
import matplotlib.pyplot as plt
x = np.array([d[0] for d in data])
y = np.array([d[1] for d in data])
# 简单线性拟合
coeffs = np.polyfit(x, y, 1)
y_fit = np.polyval(coeffs, x)
self.ax.clear()
self.ax.scatter(x, y, label="原始数据")
self.ax.plot(x, y_fit, color='r', label=f"拟合: y={coeffs[0]:.3f}x+{coeffs[1]:.3f}")
self.ax.legend()
degree = self.spinBox.value()
coeffs = np.polyfit(x, y, degree)
# 用更密集的横坐标画拟合曲线
x_fit = np.linspace(x.min(), x.max(), 100)
y_fit = np.polyval(coeffs, x_fit)
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.scatter(x, y, color='blue', label='raw data')
ax.plot(x_fit, y_fit, color='red', label=f'Fitted curve')
ax.set_title('graph of a function')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend()
self.canvas.draw()
self.resultLabel.setText(f"拟合公式: y = {coeffs[0]:.3f}x + {coeffs[1]:.3f}")
formula = self.poly_formula(coeffs)
self.resultLabel.setText(f"拟合公式: {formula}")
lang = self.langBox.currentText()
code = self.generate_code(coeffs, lang)
self.codeLabel.setText(code)
def poly_formula(self, coeffs):
terms = []
degree = len(coeffs) - 1
for i, c in enumerate(coeffs):
power = degree - i
if abs(c) < 1e-8:
continue
if power == 0:
terms.append(f"{c:.6g}")
elif power == 1:
terms.append(f"{c:.6g}*x")
else:
terms.append(f"{c:.6g}*x^{power}")
return " + ".join(terms)
def generate_code(self, coeffs, lang):
degree = len(coeffs) - 1
if lang == "C/C++":
code = "double poly(double x) {\n return "
elif lang == "Python":
code = "def poly(x):\n return "
else:
code = ""
terms = []
for i, c in enumerate(coeffs):
power = degree - i
if abs(c) < 1e-8:
continue
if power == 0:
terms.append(f"{c:.6g}")
elif power == 1:
terms.append(f"{c:.6g}*x")
else:
terms.append(f"{c:.6g}*pow(x,{power})" if lang == "C/C++" else f"{c:.6g}*x**{power}")
code += " + ".join(terms)
code += ";\n}" if lang == "C/C++" else ""
return code
def copy_code(self):
clipboard = QApplication.clipboard()
clipboard.setText(self.codeLabel.text())
# 弹出提示
InfoBar.success(
title='复制成功',
content="代码已复制到剪贴板!",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)

View File

@ -1,6 +1,15 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from PyQt5.QtCore import Qt
from qfluentwidgets import SubtitleLabel, BodyLabel, HorizontalSeparator, ImageLabel, FluentLabelBase, TitleLabel
import sys
import os
def resource_path(relative_path):
"""获取资源文件的绝对路径,兼容打包和开发环境"""
if hasattr(sys, '_MEIPASS'):
# PyInstaller 打包后的临时目录
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
class HomeInterface(QWidget):
def __init__(self, parent=None):
@ -19,7 +28,7 @@ class HomeInterface(QWidget):
content_layout.setContentsMargins(48, 48, 48, 48)
# Logo
logo = ImageLabel('img/MRobot.png')
logo = ImageLabel(resource_path('assets/logo/MRobot.png'))
logo.scaledToHeight(80)
content_layout.addWidget(logo, alignment=Qt.AlignHCenter) # 居中对齐

View File

@ -10,9 +10,10 @@ with redirect_stdout(None):
from .home_interface import HomeInterface
from .serial_terminal_interface import SerialTerminalInterface
from .function_fit_interface import FunctionFitInterface
from .part_library_interface import PartLibraryInterface
from .data_interface import DataInterface
from .mini_tool_interface import MiniToolInterface
from .about_interface import AboutInterface
import base64
@ -47,17 +48,20 @@ class MainWindow(FluentWindow):
def initInterface(self):
self.homeInterface = HomeInterface(self)
self.serialTerminalInterface = SerialTerminalInterface(self)
self.functionFitInterface = FunctionFitInterface(self)
self.partLibraryInterface = PartLibraryInterface(self)
self.dataInterface = DataInterface(self)
self.miniToolInterface = MiniToolInterface(self)
def initNavigation(self):
self.addSubInterface(self.homeInterface, FIF.HOME, self.tr('主页'))
self.addSubInterface(self.dataInterface, FIF.CODE, self.tr('代码生成'))
self.addSubInterface(self.serialTerminalInterface, FIF.COMMAND_PROMPT,self.tr('串口助手'))
self.addSubInterface(self.functionFitInterface, FIF.ROBOT, self.tr('函数拟合'))
self.addSubInterface(self.partLibraryInterface, FIF.DOWNLOAD, self.tr('零件库'))
self.addSubInterface(self.dataInterface, FIF.DOWNLOAD, self.tr('代码生成'))
self.addSubInterface(self.miniToolInterface, FIF.LIBRARY, self.tr('迷你工具箱'))
self.addSubInterface(AboutInterface(self), FIF.INFO, self.tr('关于'), position=NavigationItemPosition.BOTTOM)
# self.navigationInterface.addWidget(
# 'startGameButton',
# NavigationBarPushButton(FIF.PLAY, '启动游戏', isSelectable=False),
@ -102,7 +106,7 @@ class MainWindow(FluentWindow):
# main_window.py 只需修改关闭事件
def closeEvent(self, e):
if self.themeListener and self.themeListener.isRunning():
self.themeListener.terminate()
self.themeListener.deleteLater()
# if self.themeListener and self.themeListener.isRunning():
# self.themeListener.terminate()
# self.themeListener.deleteLater()
super().closeEvent(e)

104
app/mini_tool_interface.py Normal file
View File

@ -0,0 +1,104 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QStackedWidget, QSizePolicy
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):
super().__init__(parent)
self.setObjectName("minitoolInterface")
self.vBoxLayout = QVBoxLayout(self)
self.vBoxLayout.setAlignment(Qt.AlignTop)
self.vBoxLayout.setContentsMargins(10, 0, 10, 10) # 设置外边距
# 顶部标签栏,横向拉伸
self.tabBar = TabBar(self)
self.tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.vBoxLayout.addWidget(self.tabBar) # 移除 Qt.AlignLeft
self.stackedWidget = QStackedWidget(self)
self.vBoxLayout.addWidget(self.stackedWidget) # 加入布局
# 初始主页面
self.mainPage = QWidget(self)
mainLayout = QVBoxLayout(self.mainPage)
mainLayout.setAlignment(Qt.AlignTop) # 卡片靠顶部
self.card = PushSettingCard(
text="▶ 启动",
icon=FluentIcon.UNIT,
title="曲线拟合工具",
content="简单的曲线拟合工具,支持多种函数类型",
)
mainLayout.addWidget(self.card)
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", "工具箱主页")
self.setLayout(self.vBoxLayout)
# 信号连接
self.stackedWidget.currentChanged.connect(self.onCurrentIndexChanged)
# self.tabBar.tabAddRequested.connect(self.onAddNewTab)
self.tabBar.tabCloseRequested.connect(self.onCloseTab)
self.card.clicked.connect(self.open_fit_tab)
def addSubInterface(self, widget: QWidget, objectName: str, text: str):
widget.setObjectName(objectName)
self.stackedWidget.addWidget(widget)
self.tabBar.addTab(
routeKey=objectName,
text=text,
onClick=lambda: self.stackedWidget.setCurrentWidget(widget)
)
def onCurrentIndexChanged(self, index):
widget = self.stackedWidget.widget(index)
self.tabBar.setCurrentTab(widget.objectName())
def onAddNewTab(self):
pass # 可自定义添加新标签页逻辑
def onCloseTab(self, index: int):
item = self.tabBar.tabItem(index)
widget = self.findChild(QWidget, item.routeKey())
self.stackedWidget.removeWidget(widget)
self.tabBar.removeTab(index)
widget.deleteLater()
def open_fit_tab(self):
# 检查是否已存在标签页,避免重复添加
for i in range(self.stackedWidget.count()):
widget = self.stackedWidget.widget(i)
if widget.objectName() == "fitPage":
self.stackedWidget.setCurrentWidget(widget)
self.tabBar.setCurrentTab("fitPage")
return
fit_page = FunctionFitInterface(self)
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")

View File

@ -1,60 +1,22 @@
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"
LOCAL_LIB_DIR = "mech_lib"
LOCAL_LIB_DIR = "assets/mech_lib"
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("partLibraryInterface")
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20) # 添加边距
layout.setSpacing(16)
layout.addWidget(SubtitleLabel("零件库在线bate版"))
@ -151,41 +113,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
dialog = Dialog(
title="温馨提示",
content="请先勾选需要下载的文件。",
parent=self
)
dialog.yesButton.setText("知道啦")
dialog.cancelButton.hide()
dialog.exec()
return
self.progress_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()
# 创建进度环
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 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.progress_dialog.close()
msg = f"成功下载: {len(success)} 个文件\n失败: {len(fail)} 个文件"
dialog = Dialog(
title="下载结果",
content=msg,
parent=self
)
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 +181,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)

Binary file not shown.

Binary file not shown.

14
app/tools/check_update.py Normal file
View File

@ -0,0 +1,14 @@
import requests
from packaging.version import parse as vparse
def check_update(local_version, repo="goldenfishs/MRobot"):
url = f"https://api.github.com/repos/{repo}/releases/latest"
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
else:
return None
else:
raise RuntimeError("GitHub API 请求失败")

20
app/tools/code_utils.py Normal file
View File

@ -0,0 +1,20 @@
import re
def preserve_user_region(new_code, old_code, region_name):
"""
替换 new_code region_name 区域为 old_code 中的内容如果有
region_name: 'USER INCLUDE'
"""
pattern = re.compile(
rf"/\*\s*{region_name}\s*BEGIN\s*\*/(.*?)/\*\s*{region_name}\s*END\s*\*/",
re.DOTALL
)
old_match = pattern.search(old_code or "")
if not old_match:
return new_code # 旧文件没有该区域,直接返回新代码
old_content = old_match.group(1)
def repl(m):
return m.group(0).replace(m.group(1), old_content)
# 替换新代码中的该区域
return pattern.sub(repl, new_code, count=1)

25
app/tools/ioc_config.py Normal file
View File

@ -0,0 +1,25 @@
class IocConfig:
def __init__(self, ioc_path):
self.ioc_path = ioc_path
self.config = {}
self._parse()
def _parse(self):
with open(self.ioc_path, encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
if '=' in line:
key, value = line.split('=', 1)
self.config[key.strip()] = value.strip()
def is_freertos_enabled(self):
ip_keys = [k for k in self.config if k.startswith('Mcu.IP')]
for k in ip_keys:
if self.config[k] == 'FREERTOS':
return True
for k in self.config:
if k.startswith('FREERTOS.'):
return True
return False

View 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)

View File

@ -0,0 +1,109 @@
import os
import yaml
import textwrap
from jinja2 import Template
from .code_utils import preserve_user_region
def generate_task_code(task_list, project_path):
# base_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
template_dir = os.path.join(project_root, "User_code", "task")
output_dir = os.path.join(project_path, "User", "task")
os.makedirs(output_dir, exist_ok=True)
user_task_h_tpl = os.path.join(template_dir, "user_task.h.template")
user_task_c_tpl = os.path.join(template_dir, "user_task.c.template")
init_c_tpl = os.path.join(template_dir, "init.c.template")
task_c_tpl = os.path.join(template_dir, "task.c.template")
freq_tasks = [t for t in task_list if t.get("freq_control", True)]
def render_template(path, context):
with open(path, encoding="utf-8") as f:
tpl = Template(f.read())
return tpl.render(**context)
context_h = {
"thread_definitions": "\n".join([f" osThreadId_t {t['name']};" for t in task_list]),
"freq_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
"stack_definitions": "\n".join([f" UBaseType_t {t['name']};" for t in task_list]),
"last_up_time_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
"task_frequency_definitions": "\n".join([f"#define {t['name'].upper()}_FREQ ({t['frequency']})" for t in freq_tasks]),
"task_init_delay_definitions": "\n".join([f"#define {t['name'].upper()}_INIT_DELAY ({t['delay']})" for t in task_list]),
"task_attr_declarations": "\n".join([f"extern const osThreadAttr_t attr_{t['name']};" for t in task_list]),
"task_function_declarations": "\n".join([f"void {t['function']}(void *argument);" for t in task_list]),
}
# ----------- 生成 user_task.h -----------
user_task_h_path = os.path.join(output_dir, "user_task.h")
new_user_task_h = render_template(user_task_h_tpl, context_h)
if os.path.exists(user_task_h_path):
with open(user_task_h_path, "r", encoding="utf-8") as f:
old_code = f.read()
for region in ["USER INCLUDE", "USER MESSAGE", "USER CONFIG"]:
new_user_task_h = preserve_user_region(new_user_task_h, old_code, region)
with open(user_task_h_path, "w", encoding="utf-8") as f:
f.write(new_user_task_h)
# ----------- 生成 user_task.c -----------
context_c = {
"task_attr_definitions": "\n".join([
f"const osThreadAttr_t attr_{t['name']} = {{\n"
f" .name = \"{t['name']}\",\n"
f" .priority = osPriorityNormal,\n"
f" .stack_size = {t['stack']} * 4,\n"
f"}};"
for t in task_list
])
}
user_task_c = render_template(user_task_c_tpl, context_c)
with open(os.path.join(output_dir, "user_task.c"), "w", encoding="utf-8") as f:
f.write(user_task_c)
# ----------- 生成 init.c -----------
thread_creation_code = "\n".join([
f" task_runtime.thread.{t['name']} = osThreadNew({t['function']}, NULL, &attr_{t['name']});"
for t in task_list
])
context_init = {
"thread_creation_code": thread_creation_code,
}
init_c = render_template(init_c_tpl, context_init)
init_c_path = os.path.join(output_dir, "init.c")
if os.path.exists(init_c_path):
with open(init_c_path, "r", encoding="utf-8") as f:
old_code = f.read()
for region in ["USER INCLUDE", "USER CODE", "USER CODE INIT"]:
init_c = preserve_user_region(init_c, old_code, region)
with open(init_c_path, "w", encoding="utf-8") as f:
f.write(init_c)
# ----------- 生成 task.c -----------
for t in task_list:
desc = t.get("description", "")
desc_wrapped = "\n ".join(textwrap.wrap(desc, 20))
context_task = {
"task_name": t["name"],
"task_function": t["function"],
"task_frequency": f"{t['name'].upper()}_FREQ" if t.get("freq_control", True) else None,
"task_delay": f"{t['name'].upper()}_INIT_DELAY",
"task_description": desc_wrapped,
"freq_control": t.get("freq_control", True)
}
with open(task_c_tpl, encoding="utf-8") as f:
tpl = Template(f.read())
code = tpl.render(**context_task)
task_c_path = os.path.join(output_dir, f"{t['name']}.c")
if os.path.exists(task_c_path):
with open(task_c_path, "r", encoding="utf-8") as f:
old_code = f.read()
for region in ["USER INCLUDE", "USER STRUCT", "USER CODE", "USER CODE INIT"]:
code = preserve_user_region(code, old_code, region)
with open(task_c_path, "w", encoding="utf-8") as f:
f.write(code)
# ----------- 保存任务配置到 config.yaml -----------
config_yaml_path = os.path.join(output_dir, "config.yaml")
with open(config_yaml_path, "w", encoding="utf-8") as f:
yaml.safe_dump(task_list, f, allow_unicode=True)

Binary file not shown.

136
fluent.py
View File

@ -1,136 +0,0 @@
import os
import sys
# 将当前工作目录设置为程序所在的目录,确保无论从哪里执行,其工作目录都正确设置为程序本身的位置,避免路径错误。
os.chdir(os.path.dirname(sys.executable) if getattr(sys, 'frozen', False)else os.path.dirname(os.path.abspath(__file__)))
import pyuac
if not pyuac.isUserAdmin():
try:
pyuac.runAsAdmin(False)
sys.exit(0)
except Exception:
sys.exit(1)
import atexit
import base64
def first_run():
# if not cfg.get_value(base64.b64decode("YXV0b191cGRhdGU=").decode("utf-8")):
# log.error("首次使用请先打开图形界面 March7th Launcher.exe")
input("按回车键关闭窗口. . .")
sys.exit(0)
def run_main_actions():
while True:
version.start()
game.start()
reward.start_specific("dispatch")
Daily.start()
reward.start()
game.stop(True)
def run_sub_task(action):
game.start()
sub_tasks = {
"daily": lambda: (Daily.run(), reward.start()),
"power": Power.run,
"fight": Fight.start,
"universe": Universe.start,
"forgottenhall": lambda: challenge.start("memoryofchaos"),
"purefiction": lambda: challenge.start("purefiction"),
"apocalyptic": lambda: challenge.start("apocalyptic"),
"redemption": Redemption.start
}
task = sub_tasks.get(action)
if task:
task()
game.stop(False)
def run_sub_task_gui(action):
gui_tasks = {
"universe_gui": Universe.gui,
"fight_gui": Fight.gui
}
task = gui_tasks.get(action)
if task and not task():
input("按回车键关闭窗口. . .")
sys.exit(0)
def run_sub_task_update(action):
update_tasks = {
"universe_update": Universe.update,
"fight_update": Fight.update
}
task = update_tasks.get(action)
if task:
task()
input("按回车键关闭窗口. . .")
sys.exit(0)
def run_notify_action():
notif.notify(cfg.notify_template['TestMessage'], "./assets/app/images/March7th.jpg")
input("按回车键关闭窗口. . .")
sys.exit(0)
def main(action=None):
first_run()
# 完整运行
if action is None or action == "main":
run_main_actions()
# 子任务
elif action in ["daily", "power", "fight", "universe", "forgottenhall", "purefiction", "apocalyptic", "redemption"]:
run_sub_task(action)
# 子任务 原生图形界面
elif action in ["universe_gui", "fight_gui"]:
run_sub_task_gui(action)
# 子任务 更新项目
elif action in ["universe_update", "fight_update"]:
run_sub_task_update(action)
elif action in ["screenshot", "plot"]:
tool.start(action)
elif action == "game":
game.start()
elif action == "notify":
run_notify_action()
else:
log.error(f"未知任务: {action}")
input("按回车键关闭窗口. . .")
sys.exit(1)
# 程序结束时的处理器
def exit_handler():
"""注册程序退出时的处理函数用于清理OCR资源."""
ocr.exit_ocr()
if __name__ == "__main__":
try:
atexit.register(exit_handler)
main(sys.argv[1]) if len(sys.argv) > 1 else main()
except KeyboardInterrupt:
log.error("发生错误: 手动强制停止")
input("按回车键关闭窗口. . .")
sys.exit(1)
except Exception as e:
log.error(cfg.notify_template['ErrorOccurred'].format(error=e))
notif.notify(cfg.notify_template['ErrorOccurred'].format(error=e))
input("按回车键关闭窗口. . .")
sys.exit(1)

BIN
img/M.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

BIN
img/M.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Some files were not shown because too many files have changed in this diff Show More