feat: 增加伴生包加载器、manifest配置,Excel处理优化
This commit is contained in:
+51
-2
@@ -1,4 +1,53 @@
|
||||
import re
|
||||
from .ui.mil_tool import FrameMILTool
|
||||
from .core.companion_loader import load_companions
|
||||
from .core.manifest import COMPANION_PACKAGES
|
||||
|
||||
def create_mil_tool(parent=None):
|
||||
return FrameMILTool(parent)
|
||||
# 工具名称
|
||||
NMAE = "MIL Tool"
|
||||
|
||||
# 工具版本
|
||||
__MAJOR_VER: int = 0 # 主版本号
|
||||
__MINOR_VER: int = 0 # 次版本号
|
||||
__MICRO_VER: int = 4 # 修订版本号
|
||||
|
||||
VERSION:str = f"{__MAJOR_VER}.{__MINOR_VER}.{__MICRO_VER}"
|
||||
|
||||
# 工具描述
|
||||
DESCRIPITION = """
|
||||
主要用于生成Simlink Test Case。
|
||||
"""
|
||||
|
||||
SVG = """
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 512 512"><path fill="#1de9b6" d="M495.855 367.604c-28.98-73.177-53.037-148.231-80.725-221.893c-12.23-31.362-24.198-62.986-40.868-92.33c-6.62-10.535-12.956-22.119-23.814-28.86c-2.739-1.74-5.828-2.479-8.88-2.42c-5.084.096-10.14 2.37-14.025 5.65c-14.571 11.543-23.209 28.334-32.896 43.793c-17.114 28.698-35.156 58.748-63.773 77.396c-13.44 9.485-31.039 10.514-43.995 20.686c-17.72 13.36-29.767 32.31-44.298 48.737c-3.31 3.956-8.436 5.53-12.916 7.67C86.315 243.71 42.945 261.35 0 279.916c36.367 28.132 75.115 53.157 112.208 80.321c10.172-2.018 20.383-6.196 30.877-4.339c16.63 5.207 26.377 21.15 34.006 35.721c15.5 31.765 26.7 65.307 39.253 98.283c20.988-1.493 39.878-12.31 56.104-25.025c31.24-25.307 55.034-58.001 81.23-88.192c12.552-13.199 25.347-28.92 44.197-32.593c19.05-4.601 39.454 2.22 53.581 15.338c19.818 17.719 37.376 38.203 60.544 51.765c-3.108-15.338-10.575-29.101-16.145-43.592M171.844 316.04c-18.284 10.817-37.699 19.717-56.71 29.162c-29.828-19.98-58.889-41.15-88.192-61.856c39.515-17.315 79.635-33.4 119.675-49.545c18.97 14.813 38.547 28.88 57.517 43.693c-8.92 14.248-19.657 27.487-32.29 38.546m40.665-49.646c-18.85-14.167-37.94-27.951-56.508-42.482c11.907-16.226 24.945-31.906 40.565-44.701c10.333-6.62 22.966-8.456 33.703-14.43c25.469-12.714 42.845-36.044 59.434-58.324c-21.271 55.216-43.975 110.654-77.194 159.937"/></svg>
|
||||
"""
|
||||
|
||||
# 伴生包清单从 manifest 导入(唯一事实源),此处保留引用以兼容旧调用方
|
||||
# 加新伴生包:改 mil/core/manifest.py 的 COMPANION_PACKAGES,不要改这里
|
||||
|
||||
def create_plugin(workspace, parent=None):
|
||||
# 先挂伴生包,再构造 UI——mil 包内任何 import openpyxl 都将命中 build/ 下的 pyd
|
||||
load_companions(COMPANION_PACKAGES)
|
||||
return FrameMILTool(workspace, parent)
|
||||
|
||||
def create_plugin_svg(file:str):
|
||||
with open(file,'w', encoding='utf-8') as f:
|
||||
f.write(SVG.strip())
|
||||
f.close()
|
||||
|
||||
def read_plugin_name():
|
||||
return NMAE
|
||||
|
||||
def read_plugin_version():
|
||||
return VERSION
|
||||
|
||||
def read_plugin_description():
|
||||
return re.sub(r'^[\r\n]+',"",DESCRIPITION)
|
||||
|
||||
__all__ = [
|
||||
"create_plugin",
|
||||
"create_plugin_svg",
|
||||
"read_plugin_name",
|
||||
"read_plugin_version",
|
||||
"read_plugin_description",
|
||||
]
|
||||
+11
-14
@@ -9,7 +9,7 @@ from typing import Any
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
class Config():
|
||||
"""MIL SDK 全局配置
|
||||
|
||||
承载测试工程相关的全局开关与路径信息,是 UI 层与 core 层之间的
|
||||
@@ -23,6 +23,7 @@ class Config:
|
||||
CurrProject: 当前工程名称
|
||||
ItemConfigs: 各模块/用例项的细粒度配置
|
||||
"""
|
||||
path:str
|
||||
DataPath:str = ""
|
||||
FilePath:str = ""
|
||||
AddTimeEn:bool = True
|
||||
@@ -31,6 +32,8 @@ class Config:
|
||||
# dict 是可变类型,必须用 default_factory 显式实例化,避免多个 Config 共享同一对象
|
||||
ItemConfigs: dict = field(default_factory=dict)
|
||||
|
||||
def check_path(self):
|
||||
return os.path.exists(self.path)
|
||||
|
||||
def to_dict(self):
|
||||
"""将 Config 实例序列化为 dict,便于写入 JSON 文件。"""
|
||||
@@ -40,10 +43,10 @@ class Config:
|
||||
"AddTimeEn":self.AddTimeEn,
|
||||
"GeratePath":self.GeratePath,
|
||||
"CurrProject":self.CurrProject,
|
||||
"ItemConfigs":self.ItemConfigs
|
||||
"ItemConfigs":self.ItemConfigs,
|
||||
}
|
||||
|
||||
def load_config(self, config_path: str) -> "Config":
|
||||
def load_config(self) -> "Config":
|
||||
"""从 JSON 文件读取配置并填充到当前实例的各个字段。
|
||||
|
||||
解析规则:
|
||||
@@ -63,11 +66,11 @@ class Config:
|
||||
"""
|
||||
try:
|
||||
# 以 UTF-8 读取 JSON,避免 Windows 默认编码带来的乱码问题
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
with open(self.path, 'r', encoding='utf-8') as f:
|
||||
raw = json.load(f)
|
||||
except FileNotFoundError:
|
||||
# 显式记录并重抛,遵循"错误必须显式处理,禁止静默失败"
|
||||
logger.error(f"配置文件{config_path}未找到")
|
||||
logger.error(f"配置文件{self.path}未找到")
|
||||
raise
|
||||
|
||||
if not isinstance(raw, dict):
|
||||
@@ -81,7 +84,7 @@ class Config:
|
||||
setattr(self, key, value)
|
||||
return self
|
||||
|
||||
def save_config(self,config_path:str):
|
||||
def save_config(self):
|
||||
"""将当前配置以 JSON 格式写入磁盘。
|
||||
|
||||
Args:
|
||||
@@ -91,15 +94,10 @@ class Config:
|
||||
Exception: 写入失败时记录日志并原样抛出异常。
|
||||
"""
|
||||
try:
|
||||
with open(config_path,'w',encoding='utf-8') as f:
|
||||
with open(self.path,'w',encoding='utf-8') as f:
|
||||
json.dump(self.to_dict(), f, indent=4, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"配置文件{config_path}写入失败{e.args}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
logger.error(f"配置文件{self.path}写入失败{e.args}")
|
||||
|
||||
@dataclass
|
||||
class DataLog:
|
||||
@@ -111,7 +109,6 @@ class DataLog:
|
||||
"""
|
||||
time: float = 0.0
|
||||
value: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class SignalData:
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
"""伴生包加载器:把纯 Python 伴生包从产物目录挂到 sys.modules。
|
||||
|
||||
设计目标:宿主 exe 一次打包后,mil 的伴生依赖(openpyxl / et_xmlfile / ...)
|
||||
可通过"覆盖 build/<pkg>/ 下的 .pyd 或 mil_runtime/<pkg>/ 下的源码"增量更新,
|
||||
宿主不需要重打。
|
||||
|
||||
加载顺序(命中即停):
|
||||
1. sys.modules 已存在(不动)
|
||||
2. mil_runtime/<pkg>/ 下的纯 Python 源码(数据化部署)
|
||||
3. build/<pkg>/ 下的预编译 .pyd(本轮主路径)
|
||||
4. 都不命中 → 跳过并 warning,由调用方决定是否兜底
|
||||
|
||||
约定:
|
||||
- mil 包的父目录下存在两个候选目录:mil_runtime/(源码)和 build/(.pyd)。
|
||||
- 解析顺序可通过环境变量 MIL_RUNTIME_DIR / MIL_BUILD_DIR 覆盖。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_RUNTIME_DIRNAME = "mil_runtime"
|
||||
DEFAULT_BUILD_DIRNAME = "build"
|
||||
|
||||
|
||||
def _mil_root() -> Path:
|
||||
"""获取 mil 包的父目录(即 mil_runtime/ 与 build/ 所在的工程根)。
|
||||
|
||||
不通过 `import mil` 解析位置,避免触发 mil 包完整初始化
|
||||
(宿主侧必装 PySide6,开发侧不一定有)。
|
||||
"""
|
||||
# 本文件位于 <root>/mil/core/companion_loader.py,往上两级就是 <root>
|
||||
return Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
|
||||
def _load_from_source(runtime_dir: Path, pkg: str) -> ModuleType | None:
|
||||
"""尝试从 mil_runtime/<pkg>/ 加载纯 Python 源码包。"""
|
||||
pkg_dir = runtime_dir / pkg
|
||||
if not (pkg_dir / "__init__.py").is_file():
|
||||
return None
|
||||
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
pkg,
|
||||
pkg_dir / "__init__.py",
|
||||
submodule_search_locations=[str(pkg_dir)],
|
||||
)
|
||||
if spec is None or spec.loader is None:
|
||||
logger.warning(f"无法为 {pkg} 构造 spec(runtime 源码),跳过")
|
||||
return None
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[pkg] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def _load_from_pyd(build_dir: Path, pkg: str) -> ModuleType | None:
|
||||
"""尝试从 build/<pkg>/<pkg>.cp311-*.pyd 加载预编译包。"""
|
||||
candidates = list(build_dir.glob(f"{pkg}.*.pyd"))
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
pyd_path = candidates[0]
|
||||
spec = importlib.util.spec_from_file_location(pkg, pyd_path)
|
||||
if spec is None or spec.loader is None:
|
||||
logger.warning(f"无法为 {pkg} 构造 spec(pyd={pyd_path}),跳过")
|
||||
return None
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[pkg] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def load_companions(packages: list[str]) -> list[str]:
|
||||
"""按上述顺序把每个伴生包挂到 sys.modules,返回成功挂载的包名列表。"""
|
||||
root = _mil_root()
|
||||
runtime_dir = Path(os.environ.get("MIL_RUNTIME_DIR", root / DEFAULT_RUNTIME_DIRNAME))
|
||||
build_dir = Path(os.environ.get("MIL_BUILD_DIR", root / DEFAULT_BUILD_DIRNAME))
|
||||
|
||||
loaded: list[str] = []
|
||||
for pkg in packages:
|
||||
if pkg in sys.modules:
|
||||
logger.debug(f"伴生包 {pkg} 已在 sys.modules,跳过")
|
||||
loaded.append(pkg)
|
||||
continue
|
||||
|
||||
mod = (
|
||||
_load_from_source(runtime_dir, pkg)
|
||||
or _load_from_pyd(build_dir, pkg)
|
||||
)
|
||||
if mod is not None:
|
||||
loaded.append(pkg)
|
||||
logger.info(f"伴生包已挂载: {pkg}")
|
||||
else:
|
||||
logger.warning(
|
||||
f"伴生包未找到: {pkg}(runtime={runtime_dir} / build={build_dir} 均无)"
|
||||
)
|
||||
|
||||
return loaded
|
||||
@@ -0,0 +1,41 @@
|
||||
"""插件运行期依赖清单。
|
||||
|
||||
这是唯一的事实源:
|
||||
- build_pyd.py 读 COMPANION_PACKAGES_PYD 决定要编哪些伴生包;
|
||||
- build_pyd.py 编译 mil 时用 --include-package 把 COMPANION_PACKAGES_INLINE 编进 mil.pyd;
|
||||
- tools/export_runtime.py 读 COMPANION_C_EXTENSIONS 决定要拷哪些 C 扩展。
|
||||
|
||||
加新伴生包时只动这里,其它脚本自动跟随。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
# 需 Nuitka 编译成独立 .pyd 的伴生包(业务代码需保护)
|
||||
COMPANION_PACKAGES_PYD: list[str] = []
|
||||
|
||||
# 编译 mil 时用 --include-package 编进 mil.pyd 的纯 Python 包
|
||||
# 这些包的 .py 代码编进 mil.pyd,运行时内部 hard-import,不查 sys.path
|
||||
COMPANION_PACKAGES_INLINE: list[str] = [
|
||||
"openpyxl",
|
||||
"et_xmlfile",
|
||||
]
|
||||
|
||||
# 编译 mil 时用 --include-module 编进 mil.pyd 的 stdlib 子模块
|
||||
# Nuitka 默认不编译 stdlib(--nofollow-stdlib),所以必须显式 include。
|
||||
# 这些是 openpyxl/et_xmlfile 间接依赖的纯 Python stdlib,编进 mil.pyd 后
|
||||
# 运行时 hard-import,不查 sys.path。只有 C 扩展(_elementtree)留给运行时。
|
||||
COMPANION_STDLIB_INLINE: list[str] = [
|
||||
"xml.etree",
|
||||
"xml.etree.ElementTree",
|
||||
"xml.etree.ElementPath",
|
||||
"xml.etree.ElementInclude",
|
||||
]
|
||||
|
||||
# 兼容旧接口:所有伴生包
|
||||
COMPANION_PACKAGES: list[str] = COMPANION_PACKAGES_PYD + COMPANION_PACKAGES_INLINE
|
||||
|
||||
# 插件依赖的 C 扩展模块(Nuitka --module 编不进去,运行时从插件目录加载)
|
||||
# key = 所属 stdlib 包名, value = C 扩展模块名列表
|
||||
COMPANION_C_EXTENSIONS: dict[str, list[str]] = {
|
||||
"xml": ["_elementtree"], # xml.etree.ElementTree 内部 from _elementtree import *
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
from PySide6.QtGui import *
|
||||
from PySide6.QtCore import *
|
||||
from PySide6.QtWidgets import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from .mil_project_ui import Ui_FrameMILProject
|
||||
from mil.core import Config
|
||||
|
||||
class MILProject(QFrame, Ui_FrameMILProject):
|
||||
update_item_name_signal = Signal()
|
||||
update_item_data_path_signal = Signal(str,str)
|
||||
update_item_file_path_signal = Signal(str,str)
|
||||
|
||||
def __init__(self, config, name = None,parent = None):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.initUI(config,name)
|
||||
|
||||
def initUI(self,config:Config, name:str):
|
||||
self.lineEditDataPath.setReadOnly(True)
|
||||
self.lineEditFilePath.setReadOnly(True)
|
||||
|
||||
self.name = name
|
||||
self.config = config
|
||||
self.ItemConfigs = config.ItemConfigs
|
||||
|
||||
if self.name is not None and self.name in self.ItemConfigs.keys():
|
||||
self.lineEditName.setText(name)
|
||||
self.lineEditDataPath.setText(self.ItemConfigs[self.name]["DataPath"])
|
||||
self.lineEditFilePath.setText(self.ItemConfigs[self.name]["FilePath"])
|
||||
else:
|
||||
self.pushButtonDataPath.setEnabled(False)
|
||||
self.pushButtonFilePath.setEnabled(False)
|
||||
self.lineEditName.setPlaceholderText("必填项!!!")
|
||||
self.lineEditDataPath.setPlaceholderText("点击【选择数据文件】按钮,选择路径!")
|
||||
self.lineEditFilePath.setPlaceholderText("点击【选择测试用例】按钮,选择路径!")
|
||||
|
||||
self.lineEditName.editingFinished.connect(self.on_name_editing_finished_event)
|
||||
self.lineEditDataPath.editingFinished.connect(self.on_datapath_editing_finished_event)
|
||||
self.lineEditFilePath.editingFinished.connect(self.on_filepath_editing_finished_event)
|
||||
|
||||
self.pushButtonDataPath.clicked.connect(self.on_data_path_event)
|
||||
self.pushButtonFilePath.clicked.connect(self.on_file_path_event)
|
||||
|
||||
def on_name_editing_finished_event(self):
|
||||
name = self.lineEditName.text()
|
||||
|
||||
if name.replace(" ", "") == "":
|
||||
self.lineEditName.setText(self.name)
|
||||
logger.warning("项目名称是必填项!!!")
|
||||
return
|
||||
if name == self.name:
|
||||
return
|
||||
|
||||
if self.name is None:
|
||||
self.name = name
|
||||
self.ItemConfigs[name] = dict()
|
||||
self.ItemConfigs[name]["DataPath"] = ""
|
||||
self.ItemConfigs[name]["FilePath"] = ""
|
||||
else:
|
||||
self.ItemConfigs[name] = self.ItemConfigs.pop(self.name)
|
||||
if self.name == self.config.CurrProject:
|
||||
self.config.CurrProject = name
|
||||
self.name = name
|
||||
|
||||
self.config.save_config()
|
||||
self.update_item_name_signal.emit()
|
||||
self.pushButtonDataPath.setEnabled(True)
|
||||
self.pushButtonFilePath.setEnabled(True)
|
||||
|
||||
def on_datapath_editing_finished_event(self):
|
||||
name = self.lineEditName.text()
|
||||
data_path = self.lineEditDataPath.text()
|
||||
|
||||
if data_path.replace(" ","") == "":
|
||||
logger.warning("数据文件路径无效!")
|
||||
return
|
||||
|
||||
self.ItemConfigs[name]["DataPath"] = data_path
|
||||
self.config.save_config()
|
||||
|
||||
self.update_item_data_path_signal.emit(name,data_path)
|
||||
|
||||
def on_filepath_editing_finished_event(self):
|
||||
name = self.lineEditName.text()
|
||||
file_path = self.lineEditFilePath.text()
|
||||
|
||||
if file_path.replace(" ","") == "":
|
||||
logger.warning("测试用例路径无效!")
|
||||
return
|
||||
|
||||
self.ItemConfigs[name]["FilePath"] = file_path
|
||||
self.config.save_config()
|
||||
|
||||
self.update_item_file_path_signal.emit(name,file_path)
|
||||
|
||||
def on_data_path_event(self):
|
||||
dataPath = self.lineEditDataPath.text()
|
||||
if os.path.exists(dataPath):
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", dataPath,"Excel工作簿(*.xlsx)")
|
||||
else:
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", "./","Excel工作簿(*.xlsx)")
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
self.lineEditDataPath.setText(path)
|
||||
self.on_datapath_editing_finished_event()
|
||||
|
||||
def on_file_path_event(self):
|
||||
filePath = self.lineEditFilePath.text()
|
||||
if os.path.exists(filePath):
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", filePath,"Excel工作簿(*.xlsx)")
|
||||
else:
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", "./","Excel工作簿(*.xlsx)")
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
self.lineEditFilePath.setText(path)
|
||||
self.on_filepath_editing_finished_event()
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FrameProject</class>
|
||||
<widget class="QFrame" name="FrameProject">
|
||||
<class>FrameMILProject</class>
|
||||
<widget class="QFrame" name="FrameMILProject">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
|
||||
+23
-23
@@ -18,26 +18,26 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
||||
from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel,
|
||||
QLineEdit, QPushButton, QSizePolicy, QWidget)
|
||||
|
||||
class Ui_FrameProject(object):
|
||||
def setupUi(self, FrameProject):
|
||||
if not FrameProject.objectName():
|
||||
FrameProject.setObjectName(u"FrameProject")
|
||||
FrameProject.resize(740, 95)
|
||||
self.gridLayout_4 = QGridLayout(FrameProject)
|
||||
class Ui_FrameMILProject(object):
|
||||
def setupUi(self, FrameMILProject):
|
||||
if not FrameMILProject.objectName():
|
||||
FrameMILProject.setObjectName(u"FrameMILProject")
|
||||
FrameMILProject.resize(740, 95)
|
||||
self.gridLayout_4 = QGridLayout(FrameMILProject)
|
||||
self.gridLayout_4.setObjectName(u"gridLayout_4")
|
||||
self.gridLayout = QGridLayout()
|
||||
self.gridLayout.setObjectName(u"gridLayout")
|
||||
self.label = QLabel(FrameProject)
|
||||
self.label = QLabel(FrameMILProject)
|
||||
self.label.setObjectName(u"label")
|
||||
|
||||
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
|
||||
|
||||
self.label_2 = QLabel(FrameProject)
|
||||
self.label_2 = QLabel(FrameMILProject)
|
||||
self.label_2.setObjectName(u"label_2")
|
||||
|
||||
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
|
||||
|
||||
self.label_3 = QLabel(FrameProject)
|
||||
self.label_3 = QLabel(FrameMILProject)
|
||||
self.label_3.setObjectName(u"label_3")
|
||||
|
||||
self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
|
||||
@@ -47,7 +47,7 @@ class Ui_FrameProject(object):
|
||||
|
||||
self.gridLayout_2 = QGridLayout()
|
||||
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||
self.lineEditName = QLineEdit(FrameProject)
|
||||
self.lineEditName = QLineEdit(FrameMILProject)
|
||||
self.lineEditName.setObjectName(u"lineEditName")
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -57,14 +57,14 @@ class Ui_FrameProject(object):
|
||||
|
||||
self.gridLayout_2.addWidget(self.lineEditName, 0, 0, 1, 1)
|
||||
|
||||
self.lineEditDataPath = QLineEdit(FrameProject)
|
||||
self.lineEditDataPath = QLineEdit(FrameMILProject)
|
||||
self.lineEditDataPath.setObjectName(u"lineEditDataPath")
|
||||
sizePolicy.setHeightForWidth(self.lineEditDataPath.sizePolicy().hasHeightForWidth())
|
||||
self.lineEditDataPath.setSizePolicy(sizePolicy)
|
||||
|
||||
self.gridLayout_2.addWidget(self.lineEditDataPath, 1, 0, 1, 1)
|
||||
|
||||
self.lineEditFilePath = QLineEdit(FrameProject)
|
||||
self.lineEditFilePath = QLineEdit(FrameMILProject)
|
||||
self.lineEditFilePath.setObjectName(u"lineEditFilePath")
|
||||
sizePolicy.setHeightForWidth(self.lineEditFilePath.sizePolicy().hasHeightForWidth())
|
||||
self.lineEditFilePath.setSizePolicy(sizePolicy)
|
||||
@@ -76,7 +76,7 @@ class Ui_FrameProject(object):
|
||||
|
||||
self.gridLayout_3 = QGridLayout()
|
||||
self.gridLayout_3.setObjectName(u"gridLayout_3")
|
||||
self.pushButtonDataPath = QPushButton(FrameProject)
|
||||
self.pushButtonDataPath = QPushButton(FrameMILProject)
|
||||
self.pushButtonDataPath.setObjectName(u"pushButtonDataPath")
|
||||
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
|
||||
sizePolicy1.setHorizontalStretch(0)
|
||||
@@ -86,7 +86,7 @@ class Ui_FrameProject(object):
|
||||
|
||||
self.gridLayout_3.addWidget(self.pushButtonDataPath, 0, 0, 1, 1)
|
||||
|
||||
self.pushButtonFilePath = QPushButton(FrameProject)
|
||||
self.pushButtonFilePath = QPushButton(FrameMILProject)
|
||||
self.pushButtonFilePath.setObjectName(u"pushButtonFilePath")
|
||||
sizePolicy1.setHeightForWidth(self.pushButtonFilePath.sizePolicy().hasHeightForWidth())
|
||||
self.pushButtonFilePath.setSizePolicy(sizePolicy1)
|
||||
@@ -97,18 +97,18 @@ class Ui_FrameProject(object):
|
||||
self.gridLayout_4.addLayout(self.gridLayout_3, 0, 2, 1, 1)
|
||||
|
||||
|
||||
self.retranslateUi(FrameProject)
|
||||
self.retranslateUi(FrameMILProject)
|
||||
|
||||
QMetaObject.connectSlotsByName(FrameProject)
|
||||
QMetaObject.connectSlotsByName(FrameMILProject)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, FrameProject):
|
||||
FrameProject.setWindowTitle(QCoreApplication.translate("FrameProject", u"Frame", None))
|
||||
self.label.setText(QCoreApplication.translate("FrameProject", u"\u6a21\u578b\u9879\u76ee\uff1a", None))
|
||||
self.label_2.setText(QCoreApplication.translate("FrameProject", u"\u6570\u636e\u6587\u4ef6\uff1a", None))
|
||||
self.label_3.setText(QCoreApplication.translate("FrameProject", u"\u6d4b\u8bd5\u7528\u4f8b\uff1a", None))
|
||||
def retranslateUi(self, FrameMILProject):
|
||||
FrameMILProject.setWindowTitle(QCoreApplication.translate("FrameMILProject", u"Frame", None))
|
||||
self.label.setText(QCoreApplication.translate("FrameMILProject", u"\u6a21\u578b\u9879\u76ee\uff1a", None))
|
||||
self.label_2.setText(QCoreApplication.translate("FrameMILProject", u"\u6570\u636e\u6587\u4ef6\uff1a", None))
|
||||
self.label_3.setText(QCoreApplication.translate("FrameMILProject", u"\u6d4b\u8bd5\u7528\u4f8b\uff1a", None))
|
||||
self.lineEditName.setText("")
|
||||
self.pushButtonDataPath.setText(QCoreApplication.translate("FrameProject", u"\u9009\u62e9\u6570\u636e\u6587\u4ef6", None))
|
||||
self.pushButtonFilePath.setText(QCoreApplication.translate("FrameProject", u"\u9009\u62e9\u6d4b\u8bd5\u7528\u4f8b", None))
|
||||
self.pushButtonDataPath.setText(QCoreApplication.translate("FrameMILProject", u"\u9009\u62e9\u6570\u636e\u6587\u4ef6", None))
|
||||
self.pushButtonFilePath.setText(QCoreApplication.translate("FrameMILProject", u"\u9009\u62e9\u6d4b\u8bd5\u7528\u4f8b", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
+174
-8
@@ -1,8 +1,13 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
from .mil_tool_ui import Ui_FrameMILTool
|
||||
from PySide6.QtWidgets import QFrame
|
||||
from PySide6.QtGui import *
|
||||
from PySide6.QtCore import *
|
||||
from PySide6.QtWidgets import *
|
||||
|
||||
from .mil_project import MILProject
|
||||
|
||||
from mil.core import Config
|
||||
|
||||
@@ -11,16 +16,177 @@ logger = logging.getLogger(__name__)
|
||||
CONF = "mil.json"
|
||||
|
||||
class FrameMILTool(QFrame,Ui_FrameMILTool):
|
||||
def __init__(self,parent=None):
|
||||
def __init__(self,workspace:Path, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.initUI()
|
||||
self.initUI(workspace)
|
||||
|
||||
def initUI(self):
|
||||
self.config = Config()
|
||||
if os.path.exists(CONF):
|
||||
self.config.load_config(CONF)
|
||||
|
||||
def initUI(self, workspace:Path):
|
||||
self.workspace = workspace
|
||||
if not self.workspace.exists():
|
||||
os.mkdir(self.workspace)
|
||||
self.conf = self.workspace.joinpath(CONF)
|
||||
|
||||
self.config = Config(self.conf)
|
||||
if self.config.check_path():
|
||||
self.config.load_config()
|
||||
self.checkBox.setChecked(self.config.AddTimeEn)
|
||||
if self.config.GeratePath:
|
||||
self.radioButtonData.setChecked(True)
|
||||
else:
|
||||
self.radioButtonFile.setChecked(True)
|
||||
else:
|
||||
self.config.save_config(CONF)
|
||||
self.config.save_config()
|
||||
self.update_config()
|
||||
|
||||
for key in self.config.ItemConfigs.keys():
|
||||
self.add_pro_event(key)
|
||||
|
||||
self.menu = QMenu(self.listWidget)
|
||||
self.new_pro_acction = QAction("新建项目", self)
|
||||
self.new_pro_acction.triggered.connect(self.on_new_pro_event)
|
||||
|
||||
self.del_pro_acction = QAction("删除项目", self)
|
||||
self.del_pro_acction.triggered.connect(self.on_del_pro_event)
|
||||
self.menu.addAction(self.new_pro_acction)
|
||||
self.menu.addAction(self.del_pro_acction)
|
||||
|
||||
self.DataPath.setReadOnly(True)
|
||||
self.FilePath.setReadOnly(True)
|
||||
|
||||
self.DataButton.clicked.connect(self.on_load_data_path)
|
||||
self.FileButton.clicked.connect(self.on_load_file_path)
|
||||
|
||||
self.checkBox.toggled.connect(self.on_select_add_time_en)
|
||||
self.radioButtonData.toggled.connect(self.on_select_gerate_path)
|
||||
self.comboBox.currentTextChanged.connect(self.on_text_changed_event)
|
||||
self.listWidget.customContextMenuRequested.connect(self.on_ContextMenuRequested)
|
||||
|
||||
def update_config(self):
|
||||
self.comboBox.clear()
|
||||
if self.config.ItemConfigs.keys().__len__() == 0:
|
||||
return
|
||||
|
||||
self.lock_event = True
|
||||
for key in self.config.ItemConfigs.keys():
|
||||
self.comboBox.addItem(key)
|
||||
self.lock_event = False
|
||||
|
||||
|
||||
if self.config.CurrProject == "":
|
||||
self.config.CurrProject = self.comboBox.currentText()
|
||||
self.config.save_config()
|
||||
|
||||
self.DataPath.setText(self.config.ItemConfigs[self.comboBox.currentText()]["DataPath"])
|
||||
self.FilePath.setText(self.config.ItemConfigs[self.comboBox.currentText()]["FilePath"])
|
||||
else:
|
||||
self.comboBox.setCurrentText(self.config.CurrProject)
|
||||
self.DataPath.setText(self.config.ItemConfigs[self.config.CurrProject]["DataPath"])
|
||||
self.FilePath.setText(self.config.ItemConfigs[self.config.CurrProject]["FilePath"])
|
||||
|
||||
def on_update_data_path_event(self,name,data_path):
|
||||
if name == self.comboBox.currentText():
|
||||
self.DataPath.setText(data_path)
|
||||
|
||||
def on_update_file_path_event(self,name,file_path):
|
||||
if name == self.comboBox.currentText():
|
||||
self.FilePath.setText(file_path)
|
||||
|
||||
def on_select_gerate_path(self,checked):
|
||||
self.config.GeratePath = checked
|
||||
self.config.save_config()
|
||||
|
||||
def on_select_add_time_en(self,checked):
|
||||
self.config.AddTimeEn = checked
|
||||
self.config.save_config()
|
||||
|
||||
def on_ContextMenuRequested(self,point:QPoint):
|
||||
self.menu.exec_(QCursor.pos())
|
||||
|
||||
def on_text_changed_event(self,text:str):
|
||||
if text == "" or self.lock_event:
|
||||
return
|
||||
|
||||
self.config.CurrProject= text
|
||||
self.config.save_config()
|
||||
|
||||
self.DataPath.setText(self.config.ItemConfigs[self.config.CurrProject]["DataPath"])
|
||||
self.FilePath.setText(self.config.ItemConfigs[self.config.CurrProject]["FilePath"])
|
||||
pass
|
||||
|
||||
def on_new_pro_event(self):
|
||||
item = QListWidgetItem()
|
||||
mil_project = MILProject(self.config)
|
||||
mil_project.update_item_name_signal.connect(self.update_config)
|
||||
mil_project.update_item_data_path_signal.connect(self.on_update_data_path_event)
|
||||
mil_project.update_item_file_path_signal.connect(self.on_update_file_path_event)
|
||||
|
||||
item.setSizeHint(QSize(mil_project.sizeHint().width(), 150))
|
||||
self.listWidget.addItem(item)
|
||||
self.listWidget.setCurrentItem(item)
|
||||
self.listWidget.setItemWidget(item, mil_project)
|
||||
pass
|
||||
|
||||
def add_pro_event(self, name):
|
||||
item = QListWidgetItem()
|
||||
mil_project = MILProject(self.config, name=name)
|
||||
mil_project.update_item_name_signal.connect(self.update_config)
|
||||
mil_project.update_item_data_path_signal.connect(self.on_update_data_path_event)
|
||||
mil_project.update_item_file_path_signal.connect(self.on_update_file_path_event)
|
||||
|
||||
item.setSizeHint(QSize(mil_project.sizeHint().width(), 150))
|
||||
self.listWidget.addItem(item)
|
||||
self.listWidget.setCurrentItem(item)
|
||||
self.listWidget.setItemWidget(item, mil_project)
|
||||
|
||||
def on_del_pro_event(self):
|
||||
mil_project = self.listWidget.itemWidget(self.listWidget.currentItem())
|
||||
if not isinstance(mil_project,MILProject):
|
||||
raise "on_del_pro_event error!!!"
|
||||
|
||||
if self.config.CurrProject == mil_project.name:
|
||||
self.config.CurrProject = ""
|
||||
|
||||
self.listWidget.takeItem(self.listWidget.currentRow())
|
||||
self.config.ItemConfigs.pop(mil_project.name)
|
||||
self.config.save_config()
|
||||
|
||||
self.update_config()
|
||||
pass
|
||||
|
||||
def search_mil_pro_item(self, name):
|
||||
for mil_project in self.listWidget.findChildren(MILProject):
|
||||
if not isinstance(mil_project,MILProject):
|
||||
return
|
||||
if mil_project.name == name:
|
||||
return mil_project
|
||||
return None
|
||||
|
||||
def on_load_data_path(self):
|
||||
data_path = self.DataPath.text()
|
||||
if os.path.exists(data_path):
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", data_path,"Excel工作簿(*.xlsx)")
|
||||
else:
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", os.getcwd(),"Excel工作簿(*.xlsx)")
|
||||
self.DataPath.setText(path)
|
||||
|
||||
self.config.ItemConfigs[self.config.CurrProject]["DataPath"] = path
|
||||
mil_project = self.search_mil_pro_item(self.config.CurrProject)
|
||||
mil_project.lineEditDataPath.setText(path)
|
||||
self.config.save_config()
|
||||
|
||||
|
||||
def on_load_file_path(self):
|
||||
file_path = self.FilePath.text()
|
||||
if os.path.exists(file_path):
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", file_path,"Excel工作簿(*.xlsx)")
|
||||
else:
|
||||
path,type = QFileDialog.getOpenFileName(self, "选择文件", os.getcwd(),"Excel工作簿(*.xlsx)")
|
||||
self.FilePath.setText(path)
|
||||
|
||||
self.config.ItemConfigs[self.config.CurrProject]["FilePath"] = path
|
||||
mil_project = self.search_mil_pro_item(self.config.CurrProject)
|
||||
mil_project.lineEditFilePath.setText(path)
|
||||
self.config.save_config()
|
||||
|
||||
+6
-2
@@ -17,7 +17,7 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::TabPosition::South</enum>
|
||||
<enum>QTabWidget::TabPosition::North</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
@@ -354,7 +354,11 @@
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_11">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="listWidget"/>
|
||||
<widget class="QListWidget" name="listWidget">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@@ -29,7 +29,7 @@ class Ui_FrameMILTool(object):
|
||||
self.gridLayout_8.setObjectName(u"gridLayout_8")
|
||||
self.tabWidget = QTabWidget(FrameMILTool)
|
||||
self.tabWidget.setObjectName(u"tabWidget")
|
||||
self.tabWidget.setTabPosition(QTabWidget.TabPosition.South)
|
||||
self.tabWidget.setTabPosition(QTabWidget.TabPosition.North)
|
||||
self.widget = QWidget()
|
||||
self.widget.setObjectName(u"widget")
|
||||
self.gridLayout_12 = QGridLayout(self.widget)
|
||||
@@ -272,6 +272,7 @@ class Ui_FrameMILTool(object):
|
||||
self.gridLayout_11.setObjectName(u"gridLayout_11")
|
||||
self.listWidget = QListWidget(self.tab_2)
|
||||
self.listWidget.setObjectName(u"listWidget")
|
||||
self.listWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||
|
||||
self.gridLayout_11.addWidget(self.listWidget, 0, 0, 1, 1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user