Compare commits

...

2 Commits

Author SHA1 Message Date
d08753d14d 1.0.4发布 2025-08-17 04:18:17 +08:00
a922bb34f9 exe无法使用 2025-08-13 04:38:36 +08:00
11 changed files with 225 additions and 163 deletions

View File

@ -8,7 +8,7 @@ from qfluentwidgets import InfoBar, InfoBarPosition, SubtitleLabel
from .function_fit_interface import FunctionFitInterface
from app.tools.check_update import check_update
__version__ = "1.0.3"
__version__ = "1.0.4"
class AboutInterface(QWidget):
def __init__(self, parent=None):

View File

@ -1,91 +1,205 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy, QTreeWidget, QTreeWidgetItem, QStackedWidget
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from qfluentwidgets import TitleLabel, BodyLabel, PushButton, TreeWidget, FluentIcon, InfoBar
from app.tools.analyzing_ioc import analyzing_ioc
from app.code_page.bsp_interface import bsp
from app.data_interface import DataInterface
from app.code_page.bsp_interface import bsp
from app.code_page.component_interface import component
from app.code_page.device_interface import device
import os
import csv
import sys
import importlib
def resource_path(relative_path):
"""获取资源文件的绝对路径,兼容打包和开发环境"""
try:
if hasattr(sys, '_MEIPASS'):
base_path = sys._MEIPASS
return os.path.join(base_path, relative_path)
current_dir = os.path.dirname(os.path.abspath(__file__))
while current_dir != os.path.dirname(current_dir):
if os.path.basename(current_dir) == 'MRobot':
break
current_dir = os.path.dirname(current_dir)
return os.path.join(current_dir, relative_path)
except Exception as e:
print(f"资源路径获取失败: {e}")
current_dir = os.path.dirname(os.path.abspath(__file__))
return os.path.join(current_dir, "..", relative_path)
class CodeGenerateThread(QThread):
finished = pyqtSignal(str, str) # (状态, 信息)
def __init__(self, project_path, page_cache, component_manager, stack):
super().__init__()
self.project_path = project_path
self.page_cache = page_cache
self.component_manager = component_manager
self.stack = stack
def run(self):
try:
csv_path = resource_path("assets/User_code/config.csv")
csv_path = os.path.abspath(csv_path)
if not os.path.exists(csv_path):
self.finished.emit("error", f"未找到配置文件: {csv_path}")
return
all_class_names = []
try:
with open(csv_path, newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
row = [cell.strip() for cell in row if cell.strip()]
if not row:
continue
main_title = row[0]
for sub in row[1:]:
class_name = f"{main_title}_{sub}".replace("-", "_")
all_class_names.append(class_name)
except Exception as e:
self.finished.emit("error", f"读取配置文件时出错: {str(e)}")
return
try:
test_file = os.path.join(self.project_path, "test_write_permission.tmp")
with open(test_file, 'w') as f:
f.write("test")
os.remove(test_file)
except Exception as e:
self.finished.emit("error", f"无法写入项目目录,请检查权限: {str(e)}")
return
try:
from app.code_page.bsp_interface import get_bsp_page
from app.code_page.component_interface import get_component_page, ComponentManager
from app.code_page.device_interface import get_device_page
except Exception as e:
self.finished.emit("error", f"模块导入失败: {str(e)}")
return
if not self.component_manager:
self.component_manager = ComponentManager()
bsp_pages = []
component_pages = []
device_pages = []
for class_name in all_class_names:
page = None
try:
if class_name in self.page_cache:
page = self.page_cache[class_name]
else:
if class_name.startswith('bsp_'):
periph_name = class_name[len('bsp_'):]
page = get_bsp_page(periph_name, self.project_path)
elif class_name.startswith('component_'):
comp_name = class_name[len('component_'):]
page = get_component_page(comp_name, self.project_path, self.component_manager)
if page and hasattr(page, 'component_name'):
self.component_manager.register_component(page.component_name, page)
elif class_name.startswith('device_'):
device_name = class_name[len('device_'):]
page = get_device_page(device_name, self.project_path)
if page:
self.page_cache[class_name] = page
self.stack.addWidget(page)
except Exception:
continue
if page:
if hasattr(page, '_generate_bsp_code_internal') and page not in bsp_pages:
bsp_pages.append(page)
elif hasattr(page, '_generate_component_code_internal') and page not in component_pages:
component_pages.append(page)
elif hasattr(page, '_generate_device_code_internal') and page not in device_pages:
device_pages.append(page)
try:
bsp_result = bsp.generate_bsp(self.project_path, bsp_pages) if bsp_pages else ""
except Exception:
bsp_result = ""
try:
component_result = component.generate_component(self.project_path, component_pages) if component_pages else ""
except Exception:
component_result = ""
try:
device_result = device.generate_device(self.project_path, device_pages) if device_pages else ""
except Exception:
device_result = ""
if not bsp_result and not component_result and not device_result:
self.finished.emit("warning", "未生成任何代码,请检查是否启用了相关模块!")
return
results = []
if bsp_result:
results.append(f"BSP: {bsp_result}")
if component_result:
results.append(f"Component: {component_result}")
if device_result:
results.append(f"Device: {device_result}")
combined_result = "\n".join(results)
self.finished.emit("success", combined_result)
except Exception as e:
self.finished.emit("error", f"代码生成过程中出现错误: {str(e)}")
class CodeGenerateInterface(QWidget):
def __init__(self, project_path, parent=None):
super().__init__(parent)
self.setObjectName("CodeGenerateInterface")
self.project_path = project_path
# 初始化页面缓存
self.page_cache = {}
self.component_manager = None
self._init_ui()
def _init_ui(self):
main_layout = QVBoxLayout(self)
main_layout.setAlignment(Qt.AlignTop)
main_layout.setContentsMargins(10, 10, 10, 10)
top_layout = self._create_top_layout()
main_layout.addLayout(top_layout)
content_layout = QHBoxLayout()
content_layout.setContentsMargins(0, 10, 0, 0)
main_layout.addLayout(content_layout)
# 左侧树形列表使用qfluentwidgets的TreeWidget
self.tree = TreeWidget()
self.tree.setHeaderHidden(True)
self.tree.setMaximumWidth(250)
self.tree.setBorderRadius(8)
self.tree.setBorderVisible(True)
content_layout.addWidget(self.tree)
# 右侧内容区
self.stack = QStackedWidget()
content_layout.addWidget(self.stack)
self._load_csv_and_build_tree()
self.tree.itemClicked.connect(self.on_tree_item_clicked)
def _create_top_layout(self):
"""创建顶部横向布局"""
top_layout = QHBoxLayout()
top_layout.setAlignment(Qt.AlignTop)
# 项目名称标签
project_name = os.path.basename(self.project_path)
name_label = BodyLabel(f"项目名称: {project_name}")
name_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
top_layout.addWidget(name_label)
# FreeRTOS状态标签
freertos_label = BodyLabel(f"FreeRTOS: {self._get_freertos_status()}")
freertos_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
top_layout.addWidget(freertos_label)
# 自动生成FreeRTOS任务按钮
auto_task_btn = PushButton(FluentIcon.SEND, "自动生成FreeRTOS任务")
auto_task_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
auto_task_btn.clicked.connect(self.on_freertos_task_btn_clicked)
top_layout.addWidget(auto_task_btn, alignment=Qt.AlignRight)
# 配置并生成FreeRTOS任务按钮
freertos_task_btn = PushButton(FluentIcon.SETTING, "配置并生成FreeRTOS任务")
freertos_task_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
freertos_task_btn.clicked.connect(self.on_task_code_btn_clicked)
top_layout.addWidget(freertos_task_btn, alignment=Qt.AlignRight)
# 生成代码按钮
generate_btn = PushButton(FluentIcon.PROJECTOR,"生成代码")
generate_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
generate_btn.clicked.connect(self.generate_code)
top_layout.addWidget(generate_btn, alignment=Qt.AlignRight)
self.generate_btn = PushButton(FluentIcon.PROJECTOR,"生成代码")
self.generate_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.generate_btn.clicked.connect(self.generate_code)
top_layout.addWidget(self.generate_btn, alignment=Qt.AlignRight)
return top_layout
def on_task_code_btn_clicked(self):
# 检查是否开启 FreeRTOS
ioc_files = [f for f in os.listdir(self.project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(self.project_path, ioc_files[0])
@ -105,12 +219,9 @@ class CodeGenerateInterface(QWidget):
duration=3000
)
return
# 直接弹出任务配置对话框并生成代码
dlg = DataInterface()
dlg.project_path = self.project_path
result = dlg.open_task_config_dialog()
# 生成任务成功后弹出 InfoBar 提示
if getattr(dlg, "task_generate_success", False):
InfoBar.success(
title="任务生成成功",
@ -120,7 +231,6 @@ class CodeGenerateInterface(QWidget):
)
def on_freertos_task_btn_clicked(self):
# 检查是否开启 FreeRTOS
ioc_files = [f for f in os.listdir(self.project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(self.project_path, ioc_files[0])
@ -140,8 +250,6 @@ class CodeGenerateInterface(QWidget):
duration=3000
)
return
# 自动生成FreeRTOS任务代码
from app.data_interface import DataInterface
di = DataInterface()
di.project_path = self.project_path
@ -153,84 +261,7 @@ class CodeGenerateInterface(QWidget):
duration=2000
)
def generate_code(self):
"""生成所有代码,包括未加载页面"""
try:
# 先收集所有页面名从CSV配置文件读取
script_dir = os.path.dirname(os.path.abspath(__file__))
csv_path = os.path.join(script_dir, "../assets/User_code/config.csv")
csv_path = os.path.abspath(csv_path)
all_class_names = []
if os.path.exists(csv_path):
with open(csv_path, newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
row = [cell.strip() for cell in row if cell.strip()]
if not row:
continue
main_title = row[0]
for sub in row[1:]:
class_name = f"{main_title}_{sub}".replace("-", "_")
all_class_names.append(class_name)
# 创建所有页面对象(无论是否点击过)
bsp_pages = []
component_pages = []
device_pages = []
for class_name in all_class_names:
widget = self._get_or_create_page(class_name)
if widget:
if hasattr(widget, '_generate_bsp_code_internal') and widget not in bsp_pages:
bsp_pages.append(widget)
elif hasattr(widget, '_generate_component_code_internal') and widget not in component_pages:
component_pages.append(widget)
elif hasattr(widget, '_generate_device_code_internal') and widget not in device_pages:
device_pages.append(widget)
# 确保导入成功
from app.code_page.bsp_interface import bsp
from app.code_page.component_interface import component
from app.code_page.device_interface import device
# 生成 BSP 代码
bsp_result = bsp.generate_bsp(self.project_path, bsp_pages)
# 生成 Component 代码
component_result = component.generate_component(self.project_path, component_pages)
# 生成 Device 代码
device_result = device.generate_device(self.project_path, device_pages)
# 合并结果信息
combined_result = f"BSP代码生成:\n{bsp_result}\n\nComponent代码生成:\n{component_result}\n\nDevice代码生成:\n{device_result}"
InfoBar.success(
title="代码生成结果",
content=combined_result,
parent=self,
duration=5000
)
except ImportError as e:
InfoBar.error(
title="导入错误",
content=f"模块导入失败: {str(e)}",
parent=self,
duration=3000
)
except Exception as e:
InfoBar.error(
title="生成失败",
content=f"代码生成过程中出现错误: {str(e)}",
parent=self,
duration=3000
)
def _get_freertos_status(self):
"""获取FreeRTOS状态"""
ioc_files = [f for f in os.listdir(self.project_path) if f.endswith('.ioc')]
if ioc_files:
ioc_path = os.path.join(self.project_path, ioc_files[0])
@ -238,8 +269,7 @@ class CodeGenerateInterface(QWidget):
return "未找到.ioc文件"
def _load_csv_and_build_tree(self):
script_dir = os.path.dirname(os.path.abspath(__file__))
csv_path = os.path.join(script_dir, "../assets/User_code/config.csv")
csv_path = resource_path("assets/User_code/config.csv")
csv_path = os.path.abspath(csv_path)
print(f"加载CSV路径: {csv_path}")
if not os.path.exists(csv_path):
@ -269,37 +299,43 @@ class CodeGenerateInterface(QWidget):
if widget:
self.stack.setCurrentWidget(widget)
def generate_code(self):
self.generate_btn.setEnabled(False)
self.thread = CodeGenerateThread(self.project_path, self.page_cache, self.component_manager, self.stack)
self.thread.finished.connect(self.on_code_generate_finished)
self.thread.start()
def on_code_generate_finished(self, status, msg):
self.generate_btn.setEnabled(True)
if status == "success":
InfoBar.success(title="代码生成成功", content=msg, parent=self, duration=5000)
elif status == "warning":
InfoBar.warning(title="无代码生成", content=msg, parent=self, duration=3000)
else:
InfoBar.error(title="生成失败", content=msg, parent=self, duration=3000)
def _get_or_create_page(self, class_name):
"""获取或创建页面"""
if class_name in self.page_cache:
return self.page_cache[class_name]
# 如果是第一次创建组件页面,初始化组件管理器
if not hasattr(self, 'component_manager'):
from app.code_page.component_interface import ComponentManager
self.component_manager = ComponentManager()
try:
if class_name.startswith('bsp_'):
# BSP页面
from app.code_page.bsp_interface import get_bsp_page
# 提取外设名,如 bsp_error_detect -> error_detect
periph_name = class_name[len('bsp_'):] # 移除 .replace("_", " ")
from app.code_page.component_interface import get_component_page, ComponentManager
from app.code_page.device_interface import get_device_page
page = None
if class_name.startswith('bsp_'):
periph_name = class_name[len('bsp_'):]
page = get_bsp_page(periph_name, self.project_path)
elif class_name.startswith('component_'):
from app.code_page.component_interface import get_component_page
comp_name = class_name[len('component_'):] # 移除 .replace("_", " ")
comp_name = class_name[len('component_'):]
if not self.component_manager:
self.component_manager = ComponentManager()
page = get_component_page(comp_name, self.project_path, self.component_manager)
if page and hasattr(page, 'component_name'):
self.component_manager.register_component(page.component_name, page)
elif class_name.startswith('device_'):
# Device页面
from app.code_page.device_interface import get_device_page
device_name = class_name[len('device_'):] # 移除 device_ 前缀
device_name = class_name[len('device_'):]
page = get_device_page(device_name, self.project_path)
else:
print(f"未知的页面类型: {class_name}")
return None
if page:
self.page_cache[class_name] = page
self.stack.addWidget(page)
return page

View File

@ -6,6 +6,7 @@ import os
import shutil
import yaml
import re
import sys
def load_device_config(config_path):
"""加载设备配置"""
@ -274,16 +275,25 @@ class DeviceSimple(QWidget):
def _get_device_template_dir(self):
"""获取设备模板目录"""
current_dir = os.path.dirname(os.path.abspath(__file__))
# 向上找到 MRobot 根目录
while os.path.basename(current_dir) != 'MRobot' and current_dir != '/':
current_dir = os.path.dirname(current_dir)
if os.path.basename(current_dir) == 'MRobot':
return os.path.join(current_dir, "assets/User_code/device")
try:
if getattr(sys, 'frozen', False):
# 打包环境
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, "assets", "User_code", "device")
else:
# 如果找不到,使用相对路径作为备选
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "assets/User_code/device")
return os.path.join(os.path.dirname(sys.executable), "assets", "User_code", "device")
else:
# 开发环境
current_dir = os.path.dirname(os.path.abspath(__file__))
while current_dir != os.path.dirname(current_dir):
if os.path.basename(current_dir) == 'MRobot':
break
current_dir = os.path.dirname(current_dir)
return os.path.join(current_dir, "assets", "User_code", "device")
except Exception as e:
print(f"获取设备模板目录失败: {e}")
# 备用方案
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "assets", "User_code", "device")
def _save_config(self):
"""保存配置"""

View File

@ -63,19 +63,35 @@ class CodeGenerator:
@staticmethod
def get_template_dir():
"""获取模板目录路径,兼容打包环境"""
try:
if getattr(sys, 'frozen', False):
# 打包后的环境
if hasattr(sys, '_MEIPASS'):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(sys.executable)
template_dir = os.path.join(base_path, "assets", "User_code", "bsp")
else:
# 开发环境
current_dir = os.path.dirname(os.path.abspath(__file__))
while os.path.basename(current_dir) != 'MRobot' and current_dir != '/':
while current_dir != os.path.dirname(current_dir):
if os.path.basename(current_dir) == 'MRobot':
break
current_dir = os.path.dirname(current_dir)
template_dir = os.path.join(current_dir, "assets", "User_code", "bsp")
print(f"模板目录路径: {template_dir}")
if not os.path.exists(template_dir):
print(f"警告:模板目录不存在: {template_dir}")
# 尝试备用路径
alt_template_dir = os.path.join(os.path.dirname(__file__), "..", "..", "assets", "User_code", "bsp")
alt_template_dir = os.path.abspath(alt_template_dir)
if os.path.exists(alt_template_dir):
print(f"使用备用模板目录: {alt_template_dir}")
return alt_template_dir
return template_dir
except Exception as e:
print(f"获取模板目录失败: {e}")
# 最后的备用方案
return os.path.join(os.path.dirname(__file__), "..", "..", "assets", "User_code", "bsp")