diff --git a/1.xlsx b/1.xlsx deleted file mode 100644 index 5ebf04c..0000000 Binary files a/1.xlsx and /dev/null differ diff --git a/MRobot.iss b/MRobot.iss index dc26181..f2ac647 100644 --- a/MRobot.iss +++ b/MRobot.iss @@ -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" \ No newline at end of file +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" \ No newline at end of file diff --git a/MRobot_old.py b/MRobot_old.py deleted file mode 100644 index b35fc14..0000000 --- a/MRobot_old.py +++ /dev/null @@ -1,1714 +0,0 @@ -import sys -import os -import serial -import serial.tools.list_ports - -from PyQt5.QtCore import Qt, pyqtSignal, QThread -from PyQt5.QtGui import QTextCursor -from PyQt5.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QHBoxLayout, - QSizePolicy, - QFileDialog, QMessageBox, QStackedLayout -) - -from qfluentwidgets import ( - Theme, setTheme, FluentIcon, SwitchButton, BodyLabel, SubtitleLabel,TitleLabel, - StrongBodyLabel, HorizontalSeparator, InfoBar, MessageDialog, Dialog, - AvatarWidget, NavigationItemPosition, FluentWindow, NavigationAvatarWidget, - PushButton, TextEdit, LineEdit, ComboBox, ImageLabel, -) -from qfluentwidgets import FluentIcon as FIF -import requests -import shutil -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QHeaderView - -from qfluentwidgets import ( - TreeWidget, InfoBar, InfoBarPosition, -) -from qfluentwidgets import CheckBox -from qfluentwidgets import TreeWidget -from PyQt5.QtCore import QThread, pyqtSignal -from PyQt5.QtWidgets import QFileDialog -from qfluentwidgets import ProgressBar -import zipfile -import io -import jinja2 -from PyQt5.QtWidgets import QTreeWidgetItem as TreeItem -import yaml # 确保已安装 pyyaml -import requests -from PyQt5.QtCore import Qt, QTimer -from qfluentwidgets import ( - SettingCardGroup, SettingCard, ExpandSettingCard, HyperlinkButton, PushButton, - SubtitleLabel, StrongBodyLabel, BodyLabel, HorizontalSeparator, FluentIcon, InfoBar, InfoBarPosition -) -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout - -from qfluentwidgets import ( - SettingCardGroup, ExpandSettingCard, SubtitleLabel, BodyLabel, HorizontalSeparator, FluentIcon, InfoBar - -) - -import os -from jinja2 import Template -import yaml -import re -import textwrap -# 添加quote -from urllib.parse import quote -import re -from packaging.version import parse as vparse -__version__ = "1.0.1" - -# ===================== 页面基类 ===================== -class BaseInterface(QWidget): - def __init__(self, parent=None): - super().__init__(parent=parent) - -# ===================== 启动界面 ===================== -from qfluentwidgets import ImageLabel, TitleLabel, BodyLabel, ProgressBar, PushSettingCard, HyperlinkCard -from PyQt5.QtWidgets import QDialog, QVBoxLayout, QSpacerItem, QSizePolicy -from PyQt5.QtCore import Qt -# ...existing code... - -from qfluentwidgets import isDarkTheme # 加入主题判断 - -class SplashScreen(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - # 自动适配主题 - dark = isDarkTheme() - bg_color = "#23272e" if dark else "#f7fafd" - text_color = "#e9f6ff" if dark else "#2d7d9a" - sub_color = "#b0b8c1" if dark else "#6b7b8c" - border_color = "#3a3f4b" if dark else "#e0e6ef" - - self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog) - self.setModal(True) - self.setFixedSize(420, 260) - self.setStyleSheet(f""" - QDialog {{ - background: {bg_color}; - border-radius: 18px; - border: 1px solid {border_color}; - }} - """) - layout = QVBoxLayout(self) - layout.setContentsMargins(36, 36, 36, 36) - layout.setSpacing(18) - - layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) - - # Logo - self.logo = ImageLabel('img/MRobot.png') - self.logo.setFixedSize(220, 56) - self.logo.setAlignment(Qt.AlignCenter) - layout.addWidget(self.logo, alignment=Qt.AlignCenter) - - # 应用名 - self.title = TitleLabel("MRobot Toolbox") - self.title.setAlignment(Qt.AlignCenter) - self.title.setStyleSheet(f"font-size: 26px; font-weight: bold; color: {text_color};") - layout.addWidget(self.title) - - # 状态文本 - self.status = BodyLabel("正在启动...") - self.status.setAlignment(Qt.AlignCenter) - self.status.setStyleSheet(f"font-size: 15px; color: {sub_color};") - layout.addWidget(self.status) - - # 进度条 - self.progress = ProgressBar() - self.progress.setRange(0, 100) - self.progress.setValue(0) - self.progress.setFixedHeight(10) - layout.addWidget(self.progress) - - layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) - - def set_status(self, text, value=None): - self.status.setText(text) - if value is not None: - self.progress.setValue(value) - -# ===================== 首页界面 ===================== -class HomeInterface(BaseInterface): - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setObjectName("homeInterface") - - # 外层居中布局 - outer_layout = QVBoxLayout(self) - outer_layout.setContentsMargins(0, 0, 0, 0) - outer_layout.setSpacing(0) - outer_layout.addStretch() - - # 直接用布局和控件,无卡片 - content_layout = QVBoxLayout() - content_layout.setSpacing(24) - content_layout.setContentsMargins(48, 48, 48, 48) - - # Logo - logo = ImageLabel('img/MRobot.png') - logo.setFixedSize(320, 80) - logo.setAlignment(Qt.AlignCenter) - content_layout.addWidget(logo, alignment=Qt.AlignHCenter) - - content_layout.addSpacing(8) - content_layout.addStretch() - # 主标题 - title = SubtitleLabel("MRobot Toolbox") - title.setAlignment(Qt.AlignCenter) - title.setStyleSheet("font-size: 32px; font-weight: bold;") - content_layout.addWidget(title) - - # 副标题 - subtitle = BodyLabel("现代化,多功能机器人开发工具箱") - subtitle.setAlignment(Qt.AlignCenter) - subtitle.setStyleSheet("font-size: 18px; color: #4a6fa5;") - content_layout.addWidget(subtitle) - - # 欢迎语 - welcome = BodyLabel("欢迎使用 MRobot Toolbox!一站式支持代码生成、硬件管理、串口调试与零件库下载。") - welcome.setAlignment(Qt.AlignCenter) - welcome.setStyleSheet("font-size: 15px;") - content_layout.addWidget(welcome) - - content_layout.addSpacing(16) - content_layout.addStretch() - - # 直接加到主布局 - outer_layout.addLayout(content_layout) - outer_layout.addStretch() - - # 版权信息置底 - copyright_label = BodyLabel("© 2025 MRobot | Powered by QUT RM&RCer") - copyright_label.setAlignment(Qt.AlignCenter) - copyright_label.setStyleSheet("font-size: 13px;") - outer_layout.addWidget(copyright_label) - outer_layout.addSpacing(18) - -# ===================== 代码生成页面 ===================== -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): - # 判断是否开启FreeRTOS - 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 - - # 可扩展:添加更多参数获取方法 - def get_parameter(self, key, default=None): - return self.config.get(key, default) - - def get_all_with_prefix(self, prefix): - return {k: v for k, v in self.config.items() if k.startswith(prefix)} - -class DataInterface(BaseInterface): - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setObjectName("dataInterface") - - # 属性初始化 - self.project_path = "" - self.project_name = "" - self.ioc_file = "" - self.freertos_enabled = False # 新增属性 - - # 主布局 - self.stacked_layout = QStackedLayout(self) - self.setLayout(self.stacked_layout) - - # --- 页面1:工程路径选择 --- - self.select_widget = QWidget() - outer_layout = QVBoxLayout(self.select_widget) - outer_layout.setContentsMargins(0, 0, 0, 0) - outer_layout.addStretch() - - # 直接用布局和控件,无卡片 - content_layout = QVBoxLayout() - content_layout.setSpacing(28) - content_layout.setContentsMargins(48, 48, 48, 48) - - # 主标题 - 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) - - # 选择项目路径按钮 - 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) - - content_layout.addSpacing(10) - content_layout.addStretch() - - - outer_layout.addLayout(content_layout) - outer_layout.addStretch() - - self.stacked_layout.addWidget(self.select_widget) - - - # --- 页面2:主配置页面 --- - self.config_widget = QWidget() - main_layout = QVBoxLayout(self.config_widget) - main_layout.setContentsMargins(32, 32, 32, 32) - main_layout.setSpacing(18) - - # 顶部项目信息 - info_layout = QHBoxLayout() - self.back_btn = PushButton(FluentIcon.SKIP_BACK, "返回") - self.back_btn.setFixedWidth(90) - self.back_btn.clicked.connect(self.back_to_select) - info_layout.addWidget(self.back_btn) # 返回按钮放最左 - self.project_name_label = StrongBodyLabel() - self.project_path_label = BodyLabel() - self.ioc_file_label = BodyLabel() - self.freertos_label = BodyLabel() - info_layout.addWidget(self.project_name_label) - info_layout.addWidget(self.project_path_label) - info_layout.addWidget(self.ioc_file_label) - info_layout.addWidget(self.freertos_label) - info_layout.addStretch() - main_layout.addLayout(info_layout) - main_layout.addWidget(HorizontalSeparator()) - - # ======= 新增:左右分栏 ======= - content_hbox = QHBoxLayout() - content_hbox.setSpacing(24) - - # 左侧:文件树 - left_vbox = QVBoxLayout() - left_vbox.addWidget(SubtitleLabel("用户代码模块选择")) - left_vbox.addWidget(HorizontalSeparator()) - self.file_tree = TreeWidget() - self.file_tree.setHeaderLabels(["模块名"]) - self.file_tree.setSelectionMode(self.file_tree.ExtendedSelection) - self.file_tree.header().setSectionResizeMode(0, QHeaderView.Stretch) - self.file_tree.setCheckedColor("#0078d4", "#2d7d9a") - self.file_tree.setBorderRadius(8) - self.file_tree.setBorderVisible(True) - left_vbox.addWidget(self.file_tree, stretch=1) - content_hbox.addLayout(left_vbox, 2) - - # 右侧:操作按钮和说明 - right_vbox = QVBoxLayout() - right_vbox.setSpacing(18) - right_vbox.addWidget(SubtitleLabel("操作区")) - right_vbox.addWidget(HorizontalSeparator()) - - # 操作按钮分组 - btn_group = QVBoxLayout() - # 自动环境配置按钮 - self.env_btn = PushButton("自动环境配置") - self.env_btn.setFixedWidth(200) - self.env_btn.setToolTip("自动检测并配置常用开发环境(功能开发中)") - self.env_btn.clicked.connect(self.auto_env_config) - btn_group.addWidget(self.env_btn) - # FreeRTOS相关按钮 - self.freertos_task_btn = PushButton("自动生成FreeRTOS任务") - self.freertos_task_btn.setFixedWidth(200) - self.freertos_task_btn.setToolTip("自动在 freertos.c 中插入任务创建代码") - self.freertos_task_btn.clicked.connect(self.on_freertos_task_btn_clicked) - btn_group.addWidget(self.freertos_task_btn) - self.task_code_btn = PushButton("配置并生成任务代码") - self.task_code_btn.setFixedWidth(200) - self.task_code_btn.setToolTip("配置任务参数并一键生成任务代码文件") - self.task_code_btn.clicked.connect(self.on_task_code_btn_clicked) - btn_group.addWidget(self.task_code_btn) - self.generate_btn = PushButton(FluentIcon.CODE, "生成代码") - self.generate_btn.setFixedWidth(200) - self.generate_btn.setToolTip("将选中的用户模块代码复制到工程 User 目录") - self.generate_btn.clicked.connect(self.generate_code) - btn_group.addWidget(self.generate_btn) - btn_group.addSpacing(10) - right_vbox.addLayout(btn_group) - right_vbox.addStretch() - - content_hbox.addLayout(right_vbox, 1) - main_layout.addLayout(content_hbox, stretch=1) - self.stacked_layout.addWidget(self.config_widget) - self.file_tree.itemChanged.connect(self.on_tree_item_changed) - - def auto_env_config(self): - InfoBar.info( - title="敬请期待", - content="自动环境配置功能暂未实现,等待后续更新。", - parent=self, - duration=2000 - ) - - def choose_project_folder(self): - folder = QFileDialog.getExistingDirectory(self, "请选择代码项目文件夹") - if not folder: - return - ioc_files = [f for f in os.listdir(folder) if f.endswith('.ioc')] - if not ioc_files: - InfoBar.warning( - title="提示", - content="未找到.ioc文件,请确认项目文件夹。", - parent=self, - duration=2000 - ) - return - self.project_path = folder - self.project_name = os.path.basename(folder) - self.ioc_file = os.path.join(folder, ioc_files[0]) - self.show_config_page() - - def show_config_page(self): - # 更新项目信息 - self.project_name_label.setText(f"项目名称: {self.project_name}") - self.project_path_label.setText(f"项目路径: {self.project_path}") - # self.ioc_file_label.setText(f"IOC 文件: {self.ioc_file}") - try: - ioc = IocConfig(self.ioc_file) - self.freertos_enabled = ioc.is_freertos_enabled() # 记录状态 - freertos_status = "已启用" if self.freertos_enabled else "未启用" - self.freertos_label.setText(f"FreeRTOS: {freertos_status}") - # self.freertos_task_btn.setEnabled(self.freertos_enabled) - except Exception as e: - self.freertos_label.setText(f"IOC解析失败: {e}") - self.freertos_task_btn.hide() - self.freertos_enabled = False - self.show_user_code_files() - self.stacked_layout.setCurrentWidget(self.config_widget) - - def on_freertos_task_btn_clicked(self): - if not self.freertos_enabled: - InfoBar.warning( - title="未开启 FreeRTOS", - content="请先在 CubeMX 中开启 FreeRTOS!", - parent=self, - duration=2000 - ) - return - self.generate_freertos_task() - - def on_task_code_btn_clicked(self): - if not self.freertos_enabled: - InfoBar.warning( - title="未开启 FreeRTOS", - content="请先在 CubeMX 中开启 FreeRTOS!", - parent=self, - duration=2000 - ) - return - self.open_task_config_dialog() - - def back_to_select(self): - self.stacked_layout.setCurrentWidget(self.select_widget) - - def update_user_template(self): - url = "http://gitea.qutrobot.top/robofish/MRobot/archive/User_code.zip" - local_dir = "User_code" - try: - resp = requests.get(url, timeout=30) - resp.raise_for_status() - z = zipfile.ZipFile(io.BytesIO(resp.content)) - if os.path.exists(local_dir): - shutil.rmtree(local_dir) - for member in z.namelist(): - rel_path = os.path.relpath(member, z.namelist()[0]) - if rel_path == ".": - continue - target_path = os.path.join(local_dir, rel_path) - if member.endswith('/'): - os.makedirs(target_path, exist_ok=True) - else: - os.makedirs(os.path.dirname(target_path), exist_ok=True) - with open(target_path, "wb") as f: - f.write(z.read(member)) - InfoBar.success( - title="更新成功", - content="用户模板已更新到最新版本!", - parent=self, - duration=2000 - ) - except Exception as e: - InfoBar.error( - title="更新失败", - content=f"用户模板更新失败: {e}", - parent=self, - duration=3000 - ) - - def show_user_code_files(self): - self.file_tree.clear() - base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "User_code") - user_dir = os.path.join(self.project_path, "User") - sub_dirs = ["bsp", "component", "device", "module"] - - # 读取所有 describe.csv 和 dependencies.csv - describe_map = {} - dependencies_map = {} - for sub in sub_dirs: - dir_path = os.path.join(base_dir, sub) - if not os.path.isdir(dir_path): - continue - # describe - desc_path = os.path.join(dir_path, "describe.csv") - if os.path.exists(desc_path): - with open(desc_path, encoding="utf-8") as f: - for line in f: - if "," in line: - k, v = line.strip().split(",", 1) - describe_map[f"{sub}/{k.strip()}"] = v.strip() - # dependencies - dep_path = os.path.join(dir_path, "dependencies.csv") - if os.path.exists(dep_path): - with open(dep_path, encoding="utf-8") as f: - for line in f: - if "," in line: - a, b = line.strip().split(",", 1) - dependencies_map.setdefault(f"{sub}/{a.strip()}", []).append(b.strip()) - - self._describe_map = describe_map - self._dependencies_map = dependencies_map - - self.file_tree.setHeaderLabels(["模块名", "描述"]) - self.file_tree.setSelectionMode(self.file_tree.ExtendedSelection) - self.file_tree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) - self.file_tree.header().setSectionResizeMode(1, QHeaderView.Stretch) # 描述列自适应 - self.file_tree.setCheckedColor("#0078d4", "#2d7d9a") - self.file_tree.setBorderRadius(8) - self.file_tree.setBorderVisible(True) - - for sub in sub_dirs: - dir_path = os.path.join(base_dir, sub) - if not os.path.isdir(dir_path): - continue - group_item = TreeItem([sub, ""]) - self.file_tree.addTopLevelItem(group_item) - has_file = False - for root, _, files in os.walk(dir_path): - rel_root = os.path.relpath(root, base_dir) - for f in sorted(files): - if f.endswith(".c"): - mod_name = os.path.splitext(f)[0] - rel_c = os.path.join(rel_root, f) - key = f"{rel_root}/{mod_name}".replace("\\", "/") - desc = describe_map.get(key, "") - file_item = TreeItem([mod_name, desc]) - file_item.setFlags(file_item.flags() | Qt.ItemIsUserCheckable) - file_item.setData(0, Qt.UserRole, rel_c) - file_item.setData(0, Qt.UserRole + 1, key) # 存模块key - file_item.setToolTip(1, desc) - file_item.setTextAlignment(1, Qt.AlignLeft | Qt.AlignVCenter) - group_item.addChild(file_item) - dst_c = os.path.join(user_dir, rel_c) - if os.path.exists(dst_c): - file_item.setCheckState(0, Qt.Unchecked) - file_item.setText(0, f"{mod_name}(已存在)") - file_item.setForeground(0, Qt.gray) - else: - file_item.setCheckState(0, Qt.Unchecked) - group_item.addChild(file_item) - has_file = True - if not has_file: - empty_item = TreeItem(["(无 .c 文件)", ""]) - group_item.addChild(empty_item) - self.file_tree.expandAll() - - # 勾选依赖自动勾选 - def on_tree_item_changed(self, item, column): - if column != 0: - return - if item.childCount() > 0: - return # 只处理叶子 - if item.checkState(0) == Qt.Checked: - key = item.data(0, Qt.UserRole + 1) - deps = self._dependencies_map.get(key, []) - if deps: - checked = [] - root = self.file_tree.invisibleRootItem() - for i in range(root.childCount()): - group = root.child(i) - for j in range(group.childCount()): - child = group.child(j) - ckey = child.data(0, Qt.UserRole + 1) - if ckey in deps and child.checkState(0) != Qt.Checked: - child.setCheckState(0, Qt.Checked) - checked.append(ckey) - if checked: - descs = [self._describe_map.get(dep, dep) for dep in checked] - InfoBar.info( - title="依赖自动勾选", - content="已自动勾选依赖模块: " + ",".join(descs), - parent=self, - duration=2000 - ) - - - def get_checked_files(self): - files = [] - def _traverse(item): - for i in range(item.childCount()): - child = item.child(i) - if child.childCount() == 0 and child.checkState(0) == Qt.Checked: - files.append(child.data(0, Qt.UserRole)) - _traverse(child) - root = self.file_tree.invisibleRootItem() - for i in range(root.childCount()): - _traverse(root.child(i)) - return files - - def generate_code(self): - import shutil - base_dir = "User_code" - user_dir = os.path.join(self.project_path, "User") - copied = [] - files = self.get_checked_files() - skipped = [] - for rel_c in files: - rel_h = rel_c[:-2] + ".h" - src_c = os.path.join(base_dir, rel_c) - src_h = os.path.join(base_dir, rel_h) - dst_c = os.path.join(user_dir, rel_c) - dst_h = os.path.join(user_dir, rel_h) - # 如果目标文件已存在则跳过 - if os.path.exists(dst_c): - skipped.append(dst_c) - else: - os.makedirs(os.path.dirname(dst_c), exist_ok=True) - shutil.copy2(src_c, dst_c) - copied.append(dst_c) - if os.path.exists(src_h): - if os.path.exists(dst_h): - skipped.append(dst_h) - else: - os.makedirs(os.path.dirname(dst_h), exist_ok=True) - shutil.copy2(src_h, dst_h) - copied.append(dst_h) - msg = f"已拷贝 {len(copied)} 个文件到 User 目录" - if skipped: - msg += f"\n{len(skipped)} 个文件已存在,未覆盖" - InfoBar.success( - title="生成完成", - content=msg, - parent=self, - duration=2000 - ) - # 生成后刷新文件树,更新标记 - self.show_user_code_files() - - def generate_freertos_task(self): - import re - freertos_path = os.path.join(self.project_path, "Core", "Src", "freertos.c") - if not os.path.exists(freertos_path): - InfoBar.error( - title="未找到 freertos.c", - content="未找到 Core/Src/freertos.c 文件,请确认工程路径。", - parent=self, - duration=2500 - ) - return - with open(freertos_path, "r", encoding="utf-8") as f: - code = f.read() - - changed = False - error_msgs = [] - - # 1. 添加 #include "task/user_task.h" - include_line = '#include "task/user_task.h"' - if include_line not in code: - # 只插入到 USER CODE BEGIN Includes 区域 - include_pattern = r'(\/\* *USER CODE BEGIN Includes *\*\/\s*)' - if re.search(include_pattern, code): - code = re.sub( - include_pattern, - r'\1' + include_line + '\n', - code - ) - changed = True - else: - error_msgs.append("未找到 /* USER CODE BEGIN Includes */ 区域,无法插入 include。") - - # 2. 在 /* USER CODE BEGIN RTOS_THREADS */ 区域添加 osThreadNew(Task_Init, NULL, &attr_init); - rtos_threads_pattern = r'(\/\* *USER CODE BEGIN RTOS_THREADS *\*\/\s*)(.*?)(\/\* *USER CODE END RTOS_THREADS *\*\/)' - match = re.search(rtos_threads_pattern, code, re.DOTALL) - task_line = ' osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务\n' - if match: - threads_code = match.group(2) - if 'Task_Init' not in threads_code: - # 保留原有内容,追加新行 - new_threads_code = match.group(1) + threads_code + task_line + match.group(3) - code = code[:match.start()] + new_threads_code + code[match.end():] - changed = True - else: - error_msgs.append("未找到 /* USER CODE BEGIN RTOS_THREADS */ 区域,无法插入任务创建代码。") - - # 3. 清空 StartDefaultTask 的 USER CODE 区域,只保留 osThreadTerminate - sdt_pattern = r'(\/\* *USER CODE BEGIN StartDefaultTask *\*\/\s*)(.*?)(\/\* *USER CODE END StartDefaultTask *\*\/)' - match = re.search(sdt_pattern, code, re.DOTALL) - if match: - if 'osThreadTerminate(osThreadGetId());' not in match.group(2): - new_sdt_code = match.group(1) + ' osThreadTerminate(osThreadGetId());\n' + match.group(3) - code = code[:match.start()] + new_sdt_code + code[match.end():] - changed = True - else: - error_msgs.append("未找到 /* USER CODE BEGIN StartDefaultTask */ 区域,无法插入终止代码。") - - if changed: - with open(freertos_path, "w", encoding="utf-8") as f: - f.write(code) - InfoBar.success( - title="生成成功", - content="FreeRTOS任务代码已自动生成!", - parent=self, - duration=2000 - ) - elif error_msgs: - InfoBar.error( - title="生成失败", - content="\n".join(error_msgs), - parent=self, - duration=3000 - ) - else: - InfoBar.info( - title="无需修改", - content="FreeRTOS任务相关代码已存在,无需重复生成。", - parent=self, - duration=2000 - ) - - def open_task_config_dialog(self): - from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSpinBox, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView, QCheckBox - import yaml - import os - - class TaskConfigDialog(QDialog): - def __init__(self, parent=None, config_path=None): - super().__init__(parent) - self.setWindowTitle("任务配置") - self.resize(900, 420) - layout = QVBoxLayout(self) - self.table = QTableWidget(0, 6) - self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述", "频率控制"]) - self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) - self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) - self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) - self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch) - self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.Stretch) - self.table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeToContents) - self.table.setColumnWidth(4, 320) # 任务描述更宽 - layout.addWidget(self.table) - btn_layout = QHBoxLayout() - add_btn = QPushButton("添加任务") - del_btn = QPushButton("删除选中") - ok_btn = QPushButton("生成") - cancel_btn = QPushButton("取消") - btn_layout.addWidget(add_btn) - btn_layout.addWidget(del_btn) - btn_layout.addStretch() - btn_layout.addWidget(ok_btn) - btn_layout.addWidget(cancel_btn) - layout.addLayout(btn_layout) - add_btn.clicked.connect(self.add_row) - del_btn.clicked.connect(self.del_row) - ok_btn.clicked.connect(self.accept) - 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: - row = self.table.rowCount() - self.table.insertRow(row) - for col, key in enumerate(["name", "frequency", "delay", "stack", "description"]): - item = QTableWidgetItem(str(t.get(key, ""))) - item.setTextAlignment(Qt.AlignCenter) - self.table.setItem(row, col, item) - # 新增频率控制复选框 - freq_ctrl = QCheckBox() - freq_ctrl.setChecked(t.get("freq_control", True)) - self.table.setCellWidget(row, 5, freq_ctrl) - except Exception as e: - pass # 配置文件损坏时忽略 - - def add_row(self): - row = self.table.rowCount() - self.table.insertRow(row) - default_values = [ - f"Task{row+1}", "500", "0", "256", "不要偷懒,请写清楚每个任务的作用!(如果你看到任务上面是这句话,说明作者是个懒蛋)" - ] - for col, val in enumerate(default_values): - item = QTableWidgetItem(val) - item.setTextAlignment(Qt.AlignCenter) - self.table.setItem(row, col, item) - freq_ctrl = QCheckBox() - freq_ctrl.setChecked(True) - self.table.setCellWidget(row, 5, freq_ctrl) - - def del_row(self): - rows = set([i.row() for i in self.table.selectedItems()]) - for r in sorted(rows, reverse=True): - self.table.removeRow(r) - - def get_tasks(self): - tasks = [] - for row in range(self.table.rowCount()): - name = self.table.item(row, 0).text().strip() - freq = self.table.item(row, 1).text() - delay = int(self.table.item(row, 2).text()) - stack = int(self.table.item(row, 3).text()) - desc = self.table.item(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"] = int(freq) - tasks.append(task) - return tasks - - - - - - config_path = os.path.join(self.project_path, "User", "task", "config.yaml") - dlg = TaskConfigDialog(self, config_path=config_path) - if dlg.exec() == QDialog.Accepted: - try: - tasks = dlg.get_tasks() - except Exception as e: - InfoBar.error( - title="参数错误", - content=str(e), - parent=self, - duration=3000 - ) - return - if not tasks: - InfoBar.warning( - title="未配置任务", - content="请至少添加一个任务!", - parent=self, - duration=2000 - ) - return - try: - self.generate_task_code(tasks) - InfoBar.success( - title="生成成功", - content="任务代码已生成到 User/task 目录!", - parent=self, - duration=2000 - ) - except Exception as e: - InfoBar.error( - title="生成失败", - content=f"任务代码生成失败: {e}", - parent=self, - duration=3000 - ) - - def preserve_user_region(self, 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) - - 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") - output_dir = os.path.join(self.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"]: - pattern = re.compile( - rf"/\*\s*{region}\s*BEGIN\s*\*/(.*?)/\*\s*{region}\s*END\s*\*/", - re.DOTALL - ) - old_match = pattern.search(old_code) - if old_match and old_match.group(1).strip(): - new_user_task_h = self.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 = self.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 = self.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) - - # ...existing code... - - -# ===================== 串口终端界面 ===================== -class SerialReadThread(QThread): - data_received = pyqtSignal(str) - - def __init__(self, ser): - super().__init__() - self.ser = ser - self._running = True - - def run(self): - while self._running: - if self.ser and self.ser.is_open and self.ser.in_waiting: - try: - data = self.ser.readline().decode(errors='ignore') - self.data_received.emit(data) - except Exception: - pass - - def stop(self): - self._running = False - self.wait() - -class SerialTerminalInterface(BaseInterface): - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setObjectName("serialTerminalInterface") - main_layout = QVBoxLayout(self) - main_layout.setSpacing(12) - - # 顶部:串口设置区 - top_hbox = QHBoxLayout() - top_hbox.addWidget(BodyLabel("串口:")) - self.port_combo = ComboBox() - self.refresh_ports() - top_hbox.addWidget(self.port_combo) - top_hbox.addWidget(BodyLabel("波特率:")) - self.baud_combo = ComboBox() - self.baud_combo.addItems(['9600', '115200', '57600', '38400', '19200', '4800']) - top_hbox.addWidget(self.baud_combo) - self.connect_btn = PushButton("连接") - self.connect_btn.clicked.connect(self.toggle_connection) - top_hbox.addWidget(self.connect_btn) - self.refresh_btn = PushButton(FluentIcon.SYNC, "刷新") - self.refresh_btn.clicked.connect(self.refresh_ports) - top_hbox.addWidget(self.refresh_btn) - top_hbox.addStretch() - main_layout.addLayout(top_hbox) - - main_layout.addWidget(HorizontalSeparator()) - - # 中部:左侧预设命令,右侧显示区 - center_hbox = QHBoxLayout() - # 左侧:预设命令竖排 - preset_vbox = QVBoxLayout() - preset_vbox.addWidget(SubtitleLabel("快捷指令")) - #快捷指令居中 - preset_vbox.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.preset_commands = [ - ("线程监视器", "htop"), - ("陀螺仪校准", "cali_gyro"), - ("性能监视", "htop"), - ("重启", "reset"), - ("显示所有设备", "ls /dev"), - ("查询id", "id"), - ] - for label, cmd in self.preset_commands: - btn = PushButton(label) - btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - btn.clicked.connect(lambda _, c=cmd: self.send_preset_command(c)) - preset_vbox.addWidget(btn) - preset_vbox.addStretch() - main_layout.addLayout(center_hbox, stretch=1) - - - # 右侧:串口数据显示区 - self.text_edit = TextEdit() - self.text_edit.setReadOnly(True) - self.text_edit.setMinimumWidth(400) - center_hbox.addWidget(self.text_edit, 3) - center_hbox.addLayout(preset_vbox, 1) - - main_layout.addWidget(HorizontalSeparator()) - - # 底部:输入区 - bottom_hbox = QHBoxLayout() - self.input_line = LineEdit() - self.input_line.setPlaceholderText("输入内容,回车发送") - self.input_line.returnPressed.connect(self.send_data) - bottom_hbox.addWidget(self.input_line, 4) - send_btn = PushButton("发送") - send_btn.clicked.connect(self.send_data) - bottom_hbox.addWidget(send_btn, 1) - self.auto_enter_checkbox = CheckBox("自动回车 ") - self.auto_enter_checkbox.setChecked(True) - bottom_hbox.addWidget(self.auto_enter_checkbox) - bottom_hbox.addStretch() - main_layout.addLayout(bottom_hbox) - - self.ser = None - self.read_thread = None - - def send_preset_command(self, cmd): - self.input_line.setText(cmd) - self.send_data() - - def refresh_ports(self): - self.port_combo.clear() - ports = serial.tools.list_ports.comports() - for port in ports: - self.port_combo.addItem(port.device) - - def toggle_connection(self): - if self.ser and self.ser.is_open: - self.disconnect_serial() - else: - self.connect_serial() - - def connect_serial(self): - port = self.port_combo.currentText() - baud = int(self.baud_combo.currentText()) - try: - self.ser = serial.Serial(port, baud, timeout=0.1) - self.connect_btn.setText("断开") - self.text_edit.append(f"已连接到 {port} @ {baud}") - self.read_thread = SerialReadThread(self.ser) - self.read_thread.data_received.connect(self.display_data) - self.read_thread.start() - except Exception as e: - self.text_edit.append(f"连接失败: {e}") - - def disconnect_serial(self): - if self.read_thread: - self.read_thread.stop() - self.read_thread = None - if self.ser: - self.ser.close() - self.ser = None - self.connect_btn.setText("连接") - self.text_edit.append("已断开连接") - - def display_data(self, data): - self.text_edit.moveCursor(QTextCursor.End) - self.text_edit.insertPlainText(data) - self.text_edit.moveCursor(QTextCursor.End) - - def send_data(self): - if self.ser and self.ser.is_open: - text = self.input_line.text() - try: - if not text: - self.ser.write('\n'.encode()) - else: - for char in text: - self.ser.write(char.encode()) - # 判断是否自动回车 - if self.auto_enter_checkbox.isChecked(): - self.ser.write('\n'.encode()) - except Exception as e: - self.text_edit.append(f"发送失败: {e}") - self.input_line.clear() - -# ===================== 零件库页面 ===================== -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: - print(f"下载失败({resp.status_code}): {rel_path},第{retry+1}次尝试") - retry += 1 - except Exception as e: - print(f"下载异常: {rel_path},第{retry+1}次尝试,错误: {e}") - retry += 1 - else: - fail.append(rel_path) - self.progressChanged.emit(int((idx + 1) / total * 100)) - self.finished.emit(success, fail) -class PartLibraryInterface(BaseInterface): - SERVER_URL = "http://154.37.215.220:5000" - SECRET_KEY = "MRobot_Download" - LOCAL_LIB_DIR = "mech_lib" - - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setObjectName("partLibraryInterface") - layout = QVBoxLayout(self) - layout.setSpacing(16) - - layout.addWidget(SubtitleLabel("零件库(在线bate版)")) - layout.addWidget(HorizontalSeparator()) - layout.addWidget(BodyLabel("感谢重庆邮电大学整理的零件库,选择需要的文件下载到本地。(如无法使用或者下载失败,请尝试重新下载或检查网络连接)")) - - btn_layout = QHBoxLayout() - refresh_btn = PushButton(FluentIcon.SYNC, "刷新列表") - refresh_btn.clicked.connect(self.refresh_list) - btn_layout.addWidget(refresh_btn) - - # 新增:打开本地零件库按钮 - open_local_btn = PushButton(FluentIcon.FOLDER, "打开本地零件库") - open_local_btn.clicked.connect(self.open_local_lib) - btn_layout.addWidget(open_local_btn) - btn_layout.addStretch() - layout.addLayout(btn_layout) - - self.tree = TreeWidget(self) - - self.tree.setHeaderLabels(["名称", "类型"]) - self.tree.setSelectionMode(self.tree.ExtendedSelection) - self.tree.header().setSectionResizeMode(0, QHeaderView.Stretch) - self.tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents) - self.tree.setCheckedColor("#0078d4", "#2d7d9a") - self.tree.setBorderRadius(8) - self.tree.setBorderVisible(True) - layout.addWidget(self.tree, stretch=1) - - download_btn = PushButton(FluentIcon.DOWNLOAD, "下载选中文件") - download_btn.clicked.connect(self.download_selected_files) - layout.addWidget(download_btn) - - self.refresh_list(first=True) - - - - def refresh_list(self, first=False): - self.tree.clear() - try: - resp = requests.get( - f"{self.SERVER_URL}/list", - params={"key": self.SECRET_KEY}, - timeout=5 - ) - resp.raise_for_status() - tree = resp.json() - self.populate_tree(self.tree, tree, "") - if not first: - InfoBar.success( - title="刷新成功", - content="零件库已经是最新的!", - parent=self, - position=InfoBarPosition.TOP, - duration=2000 - ) - except Exception as e: - InfoBar.error( - title="刷新失败", - content=f"获取零件库失败: {e}", - parent=self, - position=InfoBarPosition.TOP, - duration=3000 - ) - - def populate_tree(self, parent, node, path_prefix): - from PyQt5.QtWidgets import QTreeWidgetItem - for dname, dnode in node.get("dirs", {}).items(): - item = QTreeWidgetItem([dname, "文件夹"]) - if isinstance(parent, TreeWidget): - parent.addTopLevelItem(item) - else: - parent.addChild(item) - self.populate_tree(item, dnode, os.path.join(path_prefix, dname)) - for fname in node.get("files", []): - item = QTreeWidgetItem([fname, "文件"]) - item.setFlags(item.flags() | Qt.ItemIsUserCheckable) - item.setCheckState(0, Qt.Unchecked) - item.setData(0, Qt.UserRole, os.path.join(path_prefix, fname)) - if isinstance(parent, TreeWidget): - parent.addTopLevelItem(item) - else: - parent.addChild(item) - - def get_checked_files(self): - files = [] - def _traverse(item): - for i in range(item.childCount()): - child = item.child(i) - if child.text(1) == "文件" and child.checkState(0) == Qt.Checked: - files.append(child.data(0, Qt.UserRole)) - _traverse(child) - root = self.tree.invisibleRootItem() - for i in range(root.childCount()): - _traverse(root.child(i)) - return files - - 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="正在下载选中文件,请稍候...", - parent=self - ) - self.progress_bar = ProgressBar() - self.progress_bar.setValue(0) - # 插入进度条到内容布局 - self.progress_dialog.textLayout.addWidget(self.progress_bar) - self.progress_dialog.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.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 - ) - # 添加“打开文件夹”按钮 - open_btn = PushButton("打开文件夹") - def open_folder(): - folder = os.path.abspath(self.LOCAL_LIB_DIR) - # 打开文件夹(macOS用open,Windows用explorer,Linux用xdg-open) - import platform, subprocess - if platform.system() == "Darwin": - subprocess.call(["open", folder]) - elif platform.system() == "Windows": - subprocess.call(["explorer", folder]) - else: - subprocess.call(["xdg-open", folder]) - dialog.close() - open_btn.clicked.connect(open_folder) - # 添加按钮到Dialog布局 - dialog.textLayout.addWidget(open_btn) - dialog.exec() - - def open_local_lib(self): - folder = os.path.abspath(self.LOCAL_LIB_DIR) - import platform, subprocess - if platform.system() == "Darwin": - subprocess.call(["open", folder]) - elif platform.system() == "Windows": - subprocess.call(["explorer", folder]) - else: - subprocess.call(["xdg-open", folder]) - -# ===================== 设置界面 ===================== -class SettingInterface(BaseInterface): - themeSwitchRequested = pyqtSignal() - - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setObjectName("settingInterface") - layout = QVBoxLayout() - self.setLayout(layout) - - # 标题 - layout.addSpacing(10) - layout.addWidget(SubtitleLabel("设置中心")) - layout.addSpacing(10) - layout.addWidget(HorizontalSeparator()) - - # 主题切换区域 - theme_title = StrongBodyLabel("外观设置") - theme_desc = BodyLabel("切换夜间/白天模式,适应不同环境。") - theme_desc.setWordWrap(True) - layout.addSpacing(10) - layout.addWidget(theme_title) - layout.addWidget(theme_desc) - - theme_box = QHBoxLayout() - self.theme_label = BodyLabel("夜间模式") - self.theme_switch = SwitchButton() - self.theme_switch.setChecked(Theme.DARK == Theme.DARK) - self.theme_switch.checkedChanged.connect(self.on_theme_switch) - theme_box.addWidget(self.theme_label) - theme_box.addWidget(self.theme_switch) - theme_box.addStretch() - layout.addLayout(theme_box) - - layout.addSpacing(15) - layout.addWidget(HorizontalSeparator()) - - # 其它设置区域(示例) - other_title = StrongBodyLabel("其它设置") - other_desc = BodyLabel("更多功能正在开发中,敬请期待。") - other_desc.setWordWrap(True) - layout.addSpacing(10) - layout.addWidget(other_title) - layout.addWidget(other_desc) - - # 版权信息 - layout.addStretch() - copyright_label = BodyLabel("© 2025 MRobot Toolbox") - copyright_label.setAlignment(Qt.AlignmentFlag.AlignHCenter) - layout.addWidget(copyright_label) - layout.addSpacing(10) - - def on_theme_switch(self, checked): - self.themeSwitchRequested.emit() - -# ===================== 帮助与关于界面 ===================== - -# ...existing code... - - -# 注意:PushSettingCard、HyperlinkCard 已由你的 SettingCard 文件定义 - -# ...existing code... - -from PyQt5.QtWidgets import QScrollArea, QWidget, QVBoxLayout, QMessageBox -from qfluentwidgets import ScrollArea, VBoxLayout -from qfluentwidgets import VBoxLayout -class HelpInterface(BaseInterface): - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setObjectName("helpInterface") - - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - self.setLayout(layout) - - content_widget = QWidget() - main_layout = VBoxLayout(content_widget) - main_layout.setContentsMargins(32, 32, 32, 32) - main_layout.setSpacing(18) - # 标题 - main_layout.addWidget(SubtitleLabel("帮助中心")) - main_layout.addWidget(HorizontalSeparator()) - - # 版本与更新 - version_card = PushSettingCard( - "检查更新", - FluentIcon.INFO, - f"当前版本:MRobot Toolbox v{__version__}", - "点击按钮检查是否有新版本。", - parent=self - ) - version_card.clicked.connect(self.check_update) - main_layout.addWidget(version_card) - - # FAQ分组 - faq_group = SettingCardGroup("常见问题", self) - faq_card1 = PushSettingCard( - "查看解决方法", - FluentIcon.HELP, - "启动报错/界面异常怎么办?", - "遇到启动问题请尝试重启、检查依赖,或加入交流群获取帮助。", - parent=self - ) - faq_card1.clicked.connect(lambda: self.show_info( - "启动报错/界面异常解决方法", - "1. 尝试重启软件。\n2. 检查Python和依赖库版本。\n3. 如仍有问题,请在GitHub提交Issue。" - )) - faq_group.addSettingCard(faq_card1) - - faq_card2 = PushSettingCard( - "查看解决方法", - FluentIcon.LIBRARY, - "零件库无法下载怎么办?", - "如遇网络问题或下载失败,请多次尝试或联系管理员。", - parent=self - ) - faq_card2.clicked.connect(lambda: self.show_info( - "零件库无法下载解决方法", - "1. 检查网络连接。\n2. 多次刷新或重启软件。\n3. 若仍无法下载,请加入QQ群:857466609 反馈。" - )) - faq_group.addSettingCard(faq_card2) - - # faq_card3 = PushSettingCard( - # "获取下载链接", - # FluentIcon.DOWNLOAD, - # "如何下载最新版?", - # "点击按钮获取最新版下载地址。", - # parent=self - # ) - # faq_card3.clicked.connect(lambda: self.show_info( - # "最新版下载地址", - # "GitHub发布页:https://github.com/goldenfishs/MRobot/releases\n如遇下载问题,请联系QQ群:857466609" - # )) - # faq_group.addSettingCard(faq_card3) - main_layout.addWidget(faq_group) - - # 联系方式 - contact_group = SettingCardGroup("联系方式", self) - contact_card = PushSettingCard( - "复制邮箱", - FluentIcon.MESSAGE, - "联系开发团队", - "点击按钮复制邮箱地址:support@mrobot.com", - parent=self - ) - contact_card.clicked.connect(lambda: self.copy_text("support@mrobot.com", "邮箱已复制:1683502971@qq.com")) - contact_group.addSettingCard(contact_card) - main_layout.addWidget(contact_group) - - main_layout.addStretch() - # 不使用滚动区,直接添加内容区 - layout.addWidget(content_widget) - - def copy_text(self, text, message): - clipboard = QApplication.clipboard() - clipboard.setText(text) - InfoBar.info( - title="已复制", - content=message, - parent=self, - position=InfoBarPosition.TOP, - duration=2000 - ) - - def check_update(self): - latest = check_update() - if latest: - self.show_info( - "发现新版本", - f"检测到新版本 {latest},请前往 GitHub 下载:\nhttps://github.com/goldenfishs/MRobot/releases" - ) - else: - InfoBar.info( - title="已是最新版", - content="当前已是最新版本。", - parent=self, - duration=2000 - ) - - def show_info(self, title, content): - dialog = Dialog( - title=title, - content=content, - parent=self - ) - dialog.exec() - -class AboutInterface(BaseInterface): - def __init__(self, parent=None): - super().__init__(parent=parent) - self.setObjectName("aboutInterface") - layout = QVBoxLayout() - self.setLayout(layout) - -# ===================== 主窗口与导航 ===================== -class MainWindow(FluentWindow): - themeChanged = pyqtSignal(Theme) - - def __init__(self): - super().__init__() - self.setWindowTitle("MR_ToolBox") - self.resize(800, 600) - self.setMinimumSize(640, 480) - self.setWindowFlag(Qt.Window) - # 记录当前主题 - self.current_theme = Theme.DARK - latest = check_update() - if latest: - InfoBar.info( - title="发现新版本", - content=f"检测到新版本 {latest},请前往 GitHub 下载更新。", - parent=self, - duration=5000 - ) - # 创建页面实例 - self.setting_page = SettingInterface(self) - self.setting_page.themeSwitchRequested.connect(self.toggle_theme) - - self.page_registry = [ - (HomeInterface(self), FluentIcon.HOME, "首页", NavigationItemPosition.TOP), - (DataInterface(self), FluentIcon.LIBRARY, "MRobot代码生成", NavigationItemPosition.SCROLL), - (SerialTerminalInterface(self), FluentIcon.COMMAND_PROMPT, "Mini_Shell", NavigationItemPosition.SCROLL), - (PartLibraryInterface(self), FluentIcon.DOWNLOAD, "零件库", NavigationItemPosition.SCROLL), # ← 加上这一行 - (self.setting_page, FluentIcon.SETTING, "设置", NavigationItemPosition.BOTTOM), - (HelpInterface(self), FluentIcon.HELP, "帮助", NavigationItemPosition.BOTTOM), - # (AboutInterface(self), FluentIcon.INFO, "关于", NavigationItemPosition.BOTTOM), - ] - self.initNavigation() - - def initNavigation(self): - for page, icon, name, position in self.page_registry: - self.addSubInterface(page, icon, name, position) - self.navigationInterface.addSeparator() - avatar = NavigationAvatarWidget('用户', ':/qfluentwidgets/images/avatar.png') - self.navigationInterface.addWidget( - routeKey='avatar', - widget=avatar, - onClick=self.show_user_info, # 这里改为 self.show_user_info - position=NavigationItemPosition.BOTTOM - ) - - def toggle_theme(self): - # 切换主题 - if self.current_theme == Theme.DARK: - self.current_theme = Theme.LIGHT - else: - self.current_theme = Theme.DARK - setTheme(self.current_theme) - # 同步设置界面按钮状态 - self.setting_page.theme_switch.setChecked(self.current_theme == Theme.DARK) - - def show_user_info(self): - dialog = Dialog( - title="MRobot", - content="账号:VIP内测版", - parent=self - ) - dialog.exec() - -def check_update(): - try: - 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") - print(f"本地版本: {__version__}, 最新版本: {latest}") # 调试用 - if vparse(latest) > vparse(__version__): - return latest - except Exception as e: - print(f"检查更新失败: {e}") - return None - -# ===================== 程序入口 ===================== -def main(): - from PyQt5.QtWidgets import QApplication # <-- 移到这里,所有平台都能用 - import platform - if platform.system() == "Windows": - try: - from PyQt5.QtCore import Qt - import ctypes - ctypes.windll.shcore.SetProcessDpiAwareness(1) - except Exception: - pass - QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) - QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) - - app = QApplication(sys.argv) - # 跟随系统主题 - setTheme(Theme.DARK) - splash = SplashScreen() - setTheme(Theme.DARK) - splash.show() - setTheme(Theme.DARK) - app.processEvents() - - # 步骤1:获取零件库 - splash.set_status("正在获取零件仓库...", 20) - try: - import requests - resp = requests.get("http://154.37.215.220:5000/list", params={"key": "MRobot_Download"}, timeout=5) - resp.raise_for_status() - except Exception: - pass - app.processEvents() - - # 步骤2:检查更新 - splash.set_status("正在检查软件更新...", 60) - latest = check_update() - app.processEvents() - - # 步骤3:加载主窗口 - splash.set_status("正在加载主界面...", 90) - window = MainWindow() - window.show() - setTheme(Theme.DARK) - splash.set_status("启动完成", 100) - from PyQt5.QtCore import QTimer - QTimer.singleShot(500, splash.close) - - # 有新版本弹窗 - if latest: - InfoBar.info( - title="发现新版本", - content=f"检测到新版本 {latest},请前往帮助页面下载新版。", - parent=window, - duration=5000 - ) - - sys.exit(app.exec_()) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/README.md b/README.md index bf02c41..021f777 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file +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" \ No newline at end of file diff --git a/ai.py b/ai.py deleted file mode 100644 index 4cc0dd6..0000000 --- a/ai.py +++ /dev/null @@ -1,21 +0,0 @@ -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 # 忽略解析异常 \ No newline at end of file diff --git a/app/__pycache__/__init__.cpython-39.pyc b/app/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..3462ec2 Binary files /dev/null and b/app/__pycache__/__init__.cpython-39.pyc differ diff --git a/app/__pycache__/about_interface.cpython-39.pyc b/app/__pycache__/about_interface.cpython-39.pyc new file mode 100644 index 0000000..8c795a0 Binary files /dev/null and b/app/__pycache__/about_interface.cpython-39.pyc differ diff --git a/app/__pycache__/ai_interface.cpython-39.pyc b/app/__pycache__/ai_interface.cpython-39.pyc new file mode 100644 index 0000000..cfeebb9 Binary files /dev/null and b/app/__pycache__/ai_interface.cpython-39.pyc differ diff --git a/app/__pycache__/data_interface.cpython-39.pyc b/app/__pycache__/data_interface.cpython-39.pyc new file mode 100644 index 0000000..41a0056 Binary files /dev/null and b/app/__pycache__/data_interface.cpython-39.pyc differ diff --git a/app/__pycache__/function_fit_interface.cpython-39.pyc b/app/__pycache__/function_fit_interface.cpython-39.pyc new file mode 100644 index 0000000..2842399 Binary files /dev/null and b/app/__pycache__/function_fit_interface.cpython-39.pyc differ diff --git a/app/__pycache__/home_interface.cpython-39.pyc b/app/__pycache__/home_interface.cpython-39.pyc new file mode 100644 index 0000000..1491394 Binary files /dev/null and b/app/__pycache__/home_interface.cpython-39.pyc differ diff --git a/app/__pycache__/main_window.cpython-39.pyc b/app/__pycache__/main_window.cpython-39.pyc new file mode 100644 index 0000000..b02a880 Binary files /dev/null and b/app/__pycache__/main_window.cpython-39.pyc differ diff --git a/app/__pycache__/mini_tool_interface.cpython-39.pyc b/app/__pycache__/mini_tool_interface.cpython-39.pyc new file mode 100644 index 0000000..f93141a Binary files /dev/null and b/app/__pycache__/mini_tool_interface.cpython-39.pyc differ diff --git a/app/__pycache__/part_library_interface.cpython-39.pyc b/app/__pycache__/part_library_interface.cpython-39.pyc new file mode 100644 index 0000000..3674bf6 Binary files /dev/null and b/app/__pycache__/part_library_interface.cpython-39.pyc differ diff --git a/app/__pycache__/serial_terminal_interface.cpython-39.pyc b/app/__pycache__/serial_terminal_interface.cpython-39.pyc new file mode 100644 index 0000000..f184744 Binary files /dev/null and b/app/__pycache__/serial_terminal_interface.cpython-39.pyc differ diff --git a/app/about_interface.py b/app/about_interface.py index a8a6792..463d0b2 100644 --- a/app/about_interface.py +++ b/app/about_interface.py @@ -1,7 +1,9 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMessageBox -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QUrl +from PyQt5.QtGui import QDesktopServices + from qfluentwidgets import PrimaryPushSettingCard, FluentIcon -from qfluentwidgets import InfoBar, InfoBarPosition +from qfluentwidgets import InfoBar, InfoBarPosition, SubtitleLabel from .function_fit_interface import FunctionFitInterface from app.tools.check_update import check_update @@ -15,11 +17,18 @@ class AboutInterface(QWidget): 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="关于", + title="更新", content=f"MRobot_Toolbox 当前版本:{__version__}", ) card.clicked.connect(self.on_check_update_clicked) @@ -29,9 +38,11 @@ class AboutInterface(QWidget): try: latest = check_update(__version__) if latest: + # 直接用浏览器打开下载链接 + QDesktopServices.openUrl(QUrl("https://github.com/goldenfishs/MRobot/releases/latest")) InfoBar.success( title="发现新版本", - content=f"检测到新版本:{latest},请前往官网或仓库下载更新。", + content=f"检测到新版本:{latest},已为你打开下载页面。", parent=self, position=InfoBarPosition.TOP, duration=5000 diff --git a/app/data_interface.py b/app/data_interface.py index b697e55..ba82ec9 100644 --- a/app/data_interface.py +++ b/app/data_interface.py @@ -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: @@ -253,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() @@ -287,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"] @@ -407,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() @@ -690,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) diff --git a/app/function_fit_interface.py b/app/function_fit_interface.py index 368a460..6bab448 100644 --- a/app/function_fit_interface.py +++ b/app/function_fit_interface.py @@ -1,7 +1,8 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QTableWidgetItem, QApplication from PyQt5.QtCore import Qt from qfluentwidgets import TitleLabel, BodyLabel, TableWidget, PushButton, SubtitleLabel, SpinBox, ComboBox, InfoBar,InfoBarPosition, FluentIcon -import pandas as pd +from openpyxl import load_workbook, Workbook + import numpy as np from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure @@ -131,24 +132,30 @@ class FunctionFitInterface(QWidget): 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) - self.dataTable.setRowCount(0) # 清空原有数据 - for row_data in df.values.tolist(): + 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): - item = QTableWidgetItem(str(value)) + 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): data = [] @@ -184,9 +191,9 @@ class FunctionFitInterface(QWidget): self.figure.clear() ax = self.figure.add_subplot(111) - ax.scatter(x, y, color='blue', label='原始数据') - ax.plot(x_fit, y_fit, color='red', label=f'拟合: {degree}阶') - ax.set_title('函数图像') + 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() diff --git a/app/home_interface.py b/app/home_interface.py index ee130cb..4ba30be 100644 --- a/app/home_interface.py +++ b/app/home_interface.py @@ -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) # 居中对齐 diff --git a/app/main_window.py b/app/main_window.py index 8e84f73..2f40726 100644 --- a/app/main_window.py +++ b/app/main_window.py @@ -106,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) diff --git a/app/part_library_interface.py b/app/part_library_interface.py index 1279d8c..4393f16 100644 --- a/app/part_library_interface.py +++ b/app/part_library_interface.py @@ -10,12 +10,13 @@ from urllib.parse import quote 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版)")) @@ -164,7 +165,7 @@ class PartLibraryInterface(QWidget): position=InfoBarPosition.TOP, duration=2000 ) - + def on_download_finished(self, success, fail): self.info_bar.close() msg = f"成功下载:{len(success)} 个文件,失败:{len(fail)} 个文件" diff --git a/app/tools/__pycache__/check_update.cpython-39.pyc b/app/tools/__pycache__/check_update.cpython-39.pyc new file mode 100644 index 0000000..7477353 Binary files /dev/null and b/app/tools/__pycache__/check_update.cpython-39.pyc differ diff --git a/app/tools/__pycache__/part_download.cpython-39.pyc b/app/tools/__pycache__/part_download.cpython-39.pyc new file mode 100644 index 0000000..7e11e9c Binary files /dev/null and b/app/tools/__pycache__/part_download.cpython-39.pyc differ diff --git a/app/tools/code_utils.py b/app/tools/code_utils.py new file mode 100644 index 0000000..a048d3f --- /dev/null +++ b/app/tools/code_utils.py @@ -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) \ No newline at end of file diff --git a/app/tools/ioc_config.py b/app/tools/ioc_config.py new file mode 100644 index 0000000..f27b72a --- /dev/null +++ b/app/tools/ioc_config.py @@ -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 \ No newline at end of file diff --git a/app/tools/task_code_generator.py b/app/tools/task_code_generator.py new file mode 100644 index 0000000..c9610a1 --- /dev/null +++ b/app/tools/task_code_generator.py @@ -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) \ No newline at end of file diff --git a/app/tools/task_config.py b/app/tools/task_config.py deleted file mode 100644 index fec9f35..0000000 --- a/app/tools/task_config.py +++ /dev/null @@ -1,114 +0,0 @@ -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 \ No newline at end of file diff --git a/User_code/.DS_Store b/assets/User_code/.DS_Store similarity index 100% rename from User_code/.DS_Store rename to assets/User_code/.DS_Store diff --git a/User_code/bsp/.DS_Store b/assets/User_code/bsp/.DS_Store similarity index 100% rename from User_code/bsp/.DS_Store rename to assets/User_code/bsp/.DS_Store diff --git a/User_code/bsp/.gitkeep b/assets/User_code/bsp/.gitkeep similarity index 100% rename from User_code/bsp/.gitkeep rename to assets/User_code/bsp/.gitkeep diff --git a/User_code/bsp/bsp.h b/assets/User_code/bsp/bsp.h similarity index 100% rename from User_code/bsp/bsp.h rename to assets/User_code/bsp/bsp.h diff --git a/User_code/bsp/buzzer_gpio.c b/assets/User_code/bsp/buzzer_gpio.c similarity index 100% rename from User_code/bsp/buzzer_gpio.c rename to assets/User_code/bsp/buzzer_gpio.c diff --git a/User_code/bsp/buzzer_gpio.h b/assets/User_code/bsp/buzzer_gpio.h similarity index 100% rename from User_code/bsp/buzzer_gpio.h rename to assets/User_code/bsp/buzzer_gpio.h diff --git a/User_code/bsp/can.c b/assets/User_code/bsp/can.c similarity index 100% rename from User_code/bsp/can.c rename to assets/User_code/bsp/can.c diff --git a/User_code/bsp/can.h b/assets/User_code/bsp/can.h similarity index 100% rename from User_code/bsp/can.h rename to assets/User_code/bsp/can.h diff --git a/User_code/bsp/delay.c b/assets/User_code/bsp/delay.c similarity index 100% rename from User_code/bsp/delay.c rename to assets/User_code/bsp/delay.c diff --git a/User_code/bsp/delay.h b/assets/User_code/bsp/delay.h similarity index 100% rename from User_code/bsp/delay.h rename to assets/User_code/bsp/delay.h diff --git a/User_code/bsp/dependencies.csv b/assets/User_code/bsp/dependencies.csv similarity index 100% rename from User_code/bsp/dependencies.csv rename to assets/User_code/bsp/dependencies.csv diff --git a/User_code/bsp/describe.csv b/assets/User_code/bsp/describe.csv similarity index 100% rename from User_code/bsp/describe.csv rename to assets/User_code/bsp/describe.csv diff --git a/User_code/bsp/gpio_exti.c b/assets/User_code/bsp/gpio_exti.c similarity index 100% rename from User_code/bsp/gpio_exti.c rename to assets/User_code/bsp/gpio_exti.c diff --git a/User_code/bsp/gpio_exti.h b/assets/User_code/bsp/gpio_exti.h similarity index 100% rename from User_code/bsp/gpio_exti.h rename to assets/User_code/bsp/gpio_exti.h diff --git a/User_code/bsp/i2c.c b/assets/User_code/bsp/i2c.c similarity index 100% rename from User_code/bsp/i2c.c rename to assets/User_code/bsp/i2c.c diff --git a/User_code/bsp/i2c.h b/assets/User_code/bsp/i2c.h similarity index 100% rename from User_code/bsp/i2c.h rename to assets/User_code/bsp/i2c.h diff --git a/User_code/bsp/led_gpio.c b/assets/User_code/bsp/led_gpio.c similarity index 100% rename from User_code/bsp/led_gpio.c rename to assets/User_code/bsp/led_gpio.c diff --git a/User_code/bsp/led_gpio.h b/assets/User_code/bsp/led_gpio.h similarity index 100% rename from User_code/bsp/led_gpio.h rename to assets/User_code/bsp/led_gpio.h diff --git a/User_code/bsp/servo_pwm.c b/assets/User_code/bsp/servo_pwm.c similarity index 100% rename from User_code/bsp/servo_pwm.c rename to assets/User_code/bsp/servo_pwm.c diff --git a/User_code/bsp/servo_pwm.h b/assets/User_code/bsp/servo_pwm.h similarity index 100% rename from User_code/bsp/servo_pwm.h rename to assets/User_code/bsp/servo_pwm.h diff --git a/User_code/bsp/spi.c b/assets/User_code/bsp/spi.c similarity index 100% rename from User_code/bsp/spi.c rename to assets/User_code/bsp/spi.c diff --git a/User_code/bsp/spi.h b/assets/User_code/bsp/spi.h similarity index 100% rename from User_code/bsp/spi.h rename to assets/User_code/bsp/spi.h diff --git a/User_code/bsp/uart.c b/assets/User_code/bsp/uart.c similarity index 100% rename from User_code/bsp/uart.c rename to assets/User_code/bsp/uart.c diff --git a/User_code/bsp/uart.h b/assets/User_code/bsp/uart.h similarity index 100% rename from User_code/bsp/uart.h rename to assets/User_code/bsp/uart.h diff --git a/User_code/component/.gitkeep b/assets/User_code/component/.gitkeep similarity index 100% rename from User_code/component/.gitkeep rename to assets/User_code/component/.gitkeep diff --git a/User_code/component/crc16_rm.c b/assets/User_code/component/crc16_rm.c similarity index 100% rename from User_code/component/crc16_rm.c rename to assets/User_code/component/crc16_rm.c diff --git a/User_code/component/crc16_rm.h b/assets/User_code/component/crc16_rm.h similarity index 100% rename from User_code/component/crc16_rm.h rename to assets/User_code/component/crc16_rm.h diff --git a/User_code/component/crc8_rm.c b/assets/User_code/component/crc8_rm.c similarity index 100% rename from User_code/component/crc8_rm.c rename to assets/User_code/component/crc8_rm.c diff --git a/User_code/component/crc8_rm.h b/assets/User_code/component/crc8_rm.h similarity index 100% rename from User_code/component/crc8_rm.h rename to assets/User_code/component/crc8_rm.h diff --git a/User_code/component/dependencies.csv b/assets/User_code/component/dependencies.csv similarity index 100% rename from User_code/component/dependencies.csv rename to assets/User_code/component/dependencies.csv diff --git a/User_code/component/describe.csv b/assets/User_code/component/describe.csv similarity index 100% rename from User_code/component/describe.csv rename to assets/User_code/component/describe.csv diff --git a/User_code/component/filter.c b/assets/User_code/component/filter.c similarity index 100% rename from User_code/component/filter.c rename to assets/User_code/component/filter.c diff --git a/User_code/component/filter.h b/assets/User_code/component/filter.h similarity index 100% rename from User_code/component/filter.h rename to assets/User_code/component/filter.h diff --git a/User_code/component/pid.c b/assets/User_code/component/pid.c similarity index 100% rename from User_code/component/pid.c rename to assets/User_code/component/pid.c diff --git a/User_code/component/pid.h b/assets/User_code/component/pid.h similarity index 100% rename from User_code/component/pid.h rename to assets/User_code/component/pid.h diff --git a/User_code/component/user_math.c b/assets/User_code/component/user_math.c similarity index 100% rename from User_code/component/user_math.c rename to assets/User_code/component/user_math.c diff --git a/User_code/component/user_math.h b/assets/User_code/component/user_math.h similarity index 100% rename from User_code/component/user_math.h rename to assets/User_code/component/user_math.h diff --git a/User_code/device/.DS_Store b/assets/User_code/device/.DS_Store similarity index 100% rename from User_code/device/.DS_Store rename to assets/User_code/device/.DS_Store diff --git a/User_code/device/.gitkeep b/assets/User_code/device/.gitkeep similarity index 100% rename from User_code/device/.gitkeep rename to assets/User_code/device/.gitkeep diff --git a/User_code/device/bmp280_i2c.c b/assets/User_code/device/bmp280_i2c.c similarity index 100% rename from User_code/device/bmp280_i2c.c rename to assets/User_code/device/bmp280_i2c.c diff --git a/User_code/device/bmp280_i2c.h b/assets/User_code/device/bmp280_i2c.h similarity index 100% rename from User_code/device/bmp280_i2c.h rename to assets/User_code/device/bmp280_i2c.h diff --git a/User_code/device/dependencies.csv b/assets/User_code/device/dependencies.csv similarity index 100% rename from User_code/device/dependencies.csv rename to assets/User_code/device/dependencies.csv diff --git a/User_code/device/describe.csv b/assets/User_code/device/describe.csv similarity index 100% rename from User_code/device/describe.csv rename to assets/User_code/device/describe.csv diff --git a/User_code/device/device.h b/assets/User_code/device/device.h similarity index 100% rename from User_code/device/device.h rename to assets/User_code/device/device.h diff --git a/User_code/device/key_gpio.c b/assets/User_code/device/key_gpio.c similarity index 100% rename from User_code/device/key_gpio.c rename to assets/User_code/device/key_gpio.c diff --git a/User_code/device/key_gpio.h b/assets/User_code/device/key_gpio.h similarity index 100% rename from User_code/device/key_gpio.h rename to assets/User_code/device/key_gpio.h diff --git a/User_code/device/oled_i2c.c b/assets/User_code/device/oled_i2c.c similarity index 100% rename from User_code/device/oled_i2c.c rename to assets/User_code/device/oled_i2c.c diff --git a/User_code/device/oled_i2c.h b/assets/User_code/device/oled_i2c.h similarity index 100% rename from User_code/device/oled_i2c.h rename to assets/User_code/device/oled_i2c.h diff --git a/User_code/device/pc_uart.c b/assets/User_code/device/pc_uart.c similarity index 100% rename from User_code/device/pc_uart.c rename to assets/User_code/device/pc_uart.c diff --git a/User_code/device/pc_uart.h b/assets/User_code/device/pc_uart.h similarity index 100% rename from User_code/device/pc_uart.h rename to assets/User_code/device/pc_uart.h diff --git a/User_code/device/servo.c b/assets/User_code/device/servo.c similarity index 100% rename from User_code/device/servo.c rename to assets/User_code/device/servo.c diff --git a/User_code/device/servo.h b/assets/User_code/device/servo.h similarity index 100% rename from User_code/device/servo.h rename to assets/User_code/device/servo.h diff --git a/User_code/module/.gitkeep b/assets/User_code/module/.gitkeep similarity index 100% rename from User_code/module/.gitkeep rename to assets/User_code/module/.gitkeep diff --git a/User_code/module/config.c b/assets/User_code/module/config.c similarity index 100% rename from User_code/module/config.c rename to assets/User_code/module/config.c diff --git a/User_code/module/config.h b/assets/User_code/module/config.h similarity index 100% rename from User_code/module/config.h rename to assets/User_code/module/config.h diff --git a/User_code/module/dependencies.csv b/assets/User_code/module/dependencies.csv similarity index 100% rename from User_code/module/dependencies.csv rename to assets/User_code/module/dependencies.csv diff --git a/User_code/module/describe.csv b/assets/User_code/module/describe.csv similarity index 100% rename from User_code/module/describe.csv rename to assets/User_code/module/describe.csv diff --git a/User_code/task/.gitkeep b/assets/User_code/task/.gitkeep similarity index 100% rename from User_code/task/.gitkeep rename to assets/User_code/task/.gitkeep diff --git a/User_code/task/init.c.template b/assets/User_code/task/init.c.template similarity index 100% rename from User_code/task/init.c.template rename to assets/User_code/task/init.c.template diff --git a/User_code/task/task.c.template b/assets/User_code/task/task.c.template similarity index 100% rename from User_code/task/task.c.template rename to assets/User_code/task/task.c.template diff --git a/User_code/task/user_task.c.template b/assets/User_code/task/user_task.c.template similarity index 100% rename from User_code/task/user_task.c.template rename to assets/User_code/task/user_task.c.template diff --git a/User_code/task/user_task.h.template b/assets/User_code/task/user_task.h.template similarity index 100% rename from User_code/task/user_task.h.template rename to assets/User_code/task/user_task.h.template diff --git a/mech_lib/.DS_Store b/assets/mech_lib/.DS_Store similarity index 85% rename from mech_lib/.DS_Store rename to assets/mech_lib/.DS_Store index 557d4b2..68586da 100644 Binary files a/mech_lib/.DS_Store and b/assets/mech_lib/.DS_Store differ diff --git a/mech_lib/README.md b/assets/mech_lib/README.md similarity index 100% rename from mech_lib/README.md rename to assets/mech_lib/README.md diff --git a/fluent.py b/fluent.py deleted file mode 100644 index fde56e4..0000000 --- a/fluent.py +++ /dev/null @@ -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) diff --git a/img/.DS_Store b/img/.DS_Store deleted file mode 100644 index 77445c9..0000000 Binary files a/img/.DS_Store and /dev/null differ diff --git a/img/M.ico b/img/M.ico deleted file mode 100644 index b7df782..0000000 Binary files a/img/M.ico and /dev/null differ diff --git a/img/M.png b/img/M.png deleted file mode 100644 index 300d879..0000000 Binary files a/img/M.png and /dev/null differ diff --git a/img/M2.ico b/img/M2.ico deleted file mode 100644 index 400b26d..0000000 Binary files a/img/M2.ico and /dev/null differ diff --git a/img/MR.ico b/img/MR.ico deleted file mode 100644 index ebad3d5..0000000 Binary files a/img/MR.ico and /dev/null differ diff --git a/img/MR.png b/img/MR.png deleted file mode 100644 index c546f6f..0000000 Binary files a/img/MR.png and /dev/null differ diff --git a/img/MRobot.ico b/img/MRobot.ico deleted file mode 100644 index 004d771..0000000 Binary files a/img/MRobot.ico and /dev/null differ diff --git a/img/MRobot.png b/img/MRobot.png deleted file mode 100644 index 4524089..0000000 Binary files a/img/MRobot.png and /dev/null differ diff --git a/img/m1.png b/img/m1.png deleted file mode 100644 index 4c8bccb..0000000 Binary files a/img/m1.png and /dev/null differ diff --git a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT deleted file mode 100644 index 623f7b0..0000000 Binary files a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Magnet.SLDPRT and /dev/null differ diff --git a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT deleted file mode 100644 index 56f91e7..0000000 Binary files a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Prop Endcap.SLDPRT and /dev/null differ diff --git a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT b/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT deleted file mode 100644 index 3390fef..0000000 Binary files a/mech_lib/2.电机&舵机/5065BrushlessMotor/N5065 Windings.SLDPRT and /dev/null differ diff --git a/mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT b/mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT deleted file mode 100644 index 2fa0207..0000000 Binary files a/mech_lib/4.大疆电池/TB47_外购件_01.SLDPRT and /dev/null differ diff --git a/mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT b/mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT deleted file mode 100644 index a9244ec..0000000 Binary files a/mech_lib/4.大疆电池/TB47电池架_外购件_01.SLDPRT and /dev/null differ diff --git a/mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT b/mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT deleted file mode 100644 index beb9919..0000000 Binary files a/mech_lib/4.大疆电池/TB48电池架_外购件_01.SLDPRT and /dev/null differ diff --git a/mr_tool_img/MRobot.png b/mr_tool_img/MRobot.png deleted file mode 100644 index 4524089..0000000 Binary files a/mr_tool_img/MRobot.png and /dev/null differ