feature/设备模板端子制作面板-zwl-0520
parent
9bd8e15f31
commit
70b0534964
@ -0,0 +1,260 @@
|
||||
# FreeCADExchange GUI panel for FCStd equipment template authoring.
|
||||
|
||||
import FreeCAD as App
|
||||
|
||||
try:
|
||||
import FreeCADGui as Gui
|
||||
except ImportError:
|
||||
Gui = None
|
||||
|
||||
try:
|
||||
from PySide6 import QtGui, QtWidgets
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide2 import QtGui, QtWidgets
|
||||
except ImportError:
|
||||
try:
|
||||
from PySide import QtGui
|
||||
from PySide import QtGui as QtWidgets
|
||||
except ImportError:
|
||||
QtGui = None
|
||||
QtWidgets = None
|
||||
|
||||
import TemplateAuthoring
|
||||
|
||||
|
||||
COMMAND_NAME = "QET_Template_OpenAuthoringPanel"
|
||||
MENU_ACTION_OBJECT_NAME = "QET_Template_OpenAuthoringPanel_MenuAction"
|
||||
|
||||
|
||||
def next_slot_name(report):
|
||||
terminals = list((report or {}).get("terminals", []) or [])
|
||||
return "T{0}".format(len(terminals) + 1)
|
||||
|
||||
|
||||
def terminal_list_text(report):
|
||||
rows = []
|
||||
for item in list((report or {}).get("terminals", []) or []):
|
||||
slot_name = (item.get("slot_name", "") or "").strip() or "(unnamed)"
|
||||
object_name = (item.get("name", "") or "").strip() or "(object)"
|
||||
suffix = "" if item.get("can_wire", False) and slot_name != "(unnamed)" else " [invalid]"
|
||||
rows.append("{0} - {1}{2}".format(slot_name, object_name, suffix))
|
||||
return rows
|
||||
|
||||
|
||||
class TemplateAuthoringTaskPanel:
|
||||
def __init__(self):
|
||||
if QtWidgets is None:
|
||||
raise TemplateAuthoring.TemplateAuthoringError("Qt widgets are not available.")
|
||||
|
||||
self.form = QtWidgets.QWidget()
|
||||
self.form.setWindowTitle("设备模板端子制作")
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self.form)
|
||||
|
||||
name_row = QtWidgets.QHBoxLayout()
|
||||
name_row.addWidget(QtWidgets.QLabel("端子名"))
|
||||
self.slot_name_edit = QtWidgets.QLineEdit()
|
||||
self.slot_name_edit.setPlaceholderText("P1")
|
||||
name_row.addWidget(self.slot_name_edit)
|
||||
layout.addLayout(name_row)
|
||||
|
||||
type_row = QtWidgets.QHBoxLayout()
|
||||
type_row.addWidget(QtWidgets.QLabel("端子类型"))
|
||||
self.terminal_type_combo = QtWidgets.QComboBox()
|
||||
self.terminal_type_combo.addItems(["generic", "primary", "power", "control"])
|
||||
type_row.addWidget(self.terminal_type_combo)
|
||||
layout.addLayout(type_row)
|
||||
|
||||
self.add_button = QtWidgets.QPushButton("添加端子")
|
||||
self.validate_button = QtWidgets.QPushButton("校验端子")
|
||||
self.save_button = QtWidgets.QPushButton("保存为 FCStd")
|
||||
self.refresh_button = QtWidgets.QPushButton("刷新列表")
|
||||
|
||||
layout.addWidget(self.add_button)
|
||||
layout.addWidget(self.validate_button)
|
||||
layout.addWidget(self.save_button)
|
||||
layout.addWidget(self.refresh_button)
|
||||
|
||||
self.terminal_list = QtWidgets.QListWidget()
|
||||
layout.addWidget(self.terminal_list)
|
||||
|
||||
self.status_label = QtWidgets.QLabel("")
|
||||
self.status_label.setWordWrap(True)
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
self.add_button.clicked.connect(self.add_terminal)
|
||||
self.validate_button.clicked.connect(self.validate_terminals)
|
||||
self.save_button.clicked.connect(self.save_template)
|
||||
self.refresh_button.clicked.connect(self.refresh)
|
||||
|
||||
self.refresh()
|
||||
|
||||
def _document(self):
|
||||
doc = getattr(App, "ActiveDocument", None)
|
||||
if doc is None:
|
||||
raise TemplateAuthoring.TemplateAuthoringError("请先打开或创建一个 FreeCAD 文档。")
|
||||
return doc
|
||||
|
||||
def _set_status(self, message):
|
||||
self.status_label.setText(message)
|
||||
try:
|
||||
App.Console.PrintMessage("[FreeCADExchange] {0}\n".format(message))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _set_error(self, message):
|
||||
self.status_label.setText(message)
|
||||
try:
|
||||
App.Console.PrintError("[FreeCADExchange] {0}\n".format(message))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def refresh(self):
|
||||
try:
|
||||
report = TemplateAuthoring.validate_template_terminals(getattr(App, "ActiveDocument", None))
|
||||
except Exception as exc:
|
||||
self._set_error(str(exc))
|
||||
return
|
||||
|
||||
self.terminal_list.clear()
|
||||
for row in terminal_list_text(report):
|
||||
self.terminal_list.addItem(row)
|
||||
|
||||
if not self.slot_name_edit.text().strip():
|
||||
self.slot_name_edit.setText(next_slot_name(report))
|
||||
|
||||
self.status_label.setText(
|
||||
"端子:{0} 个,有效:{1} 个".format(
|
||||
report.get("total_terminals", 0),
|
||||
report.get("valid_terminals", 0),
|
||||
)
|
||||
)
|
||||
|
||||
def add_terminal(self):
|
||||
try:
|
||||
doc = self._document()
|
||||
slot_name = self.slot_name_edit.text().strip()
|
||||
position = TemplateAuthoring._selection_position()
|
||||
if position is None:
|
||||
raise TemplateAuthoring.TemplateAuthoringError("请先在模型上选择端子位置。")
|
||||
terminal_type = self.terminal_type_combo.currentText().strip() or "generic"
|
||||
TemplateAuthoring.create_template_terminal(
|
||||
doc,
|
||||
slot_name,
|
||||
position,
|
||||
terminal_type=terminal_type,
|
||||
)
|
||||
self.slot_name_edit.clear()
|
||||
self.refresh()
|
||||
self._set_status("已添加端子:{0}".format(slot_name))
|
||||
except Exception as exc:
|
||||
self._set_error(str(exc))
|
||||
|
||||
def validate_terminals(self):
|
||||
try:
|
||||
report = TemplateAuthoring.validate_template_terminals(self._document())
|
||||
self.refresh()
|
||||
if report.get("warnings"):
|
||||
self._set_error("校验发现问题:{0}".format("; ".join(report["warnings"])))
|
||||
else:
|
||||
self._set_status(
|
||||
"校验通过:{0}/{1} 个端子有效".format(
|
||||
report.get("valid_terminals", 0),
|
||||
report.get("total_terminals", 0),
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
self._set_error(str(exc))
|
||||
|
||||
def save_template(self):
|
||||
try:
|
||||
doc = self._document()
|
||||
file_path, _selected_filter = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self.form,
|
||||
"保存 FCStd 设备模板",
|
||||
"",
|
||||
"FreeCAD template (*.FCStd *.fcstd);;All files (*.*)",
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
report = TemplateAuthoring.save_template_as_fcstd(doc, file_path)
|
||||
self.refresh()
|
||||
self._set_status("已保存模板:{0}".format(report["path"]))
|
||||
except Exception as exc:
|
||||
self._set_error(str(exc))
|
||||
|
||||
def accept(self):
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
return True
|
||||
|
||||
|
||||
class CommandOpenTemplateAuthoringPanel:
|
||||
def GetResources(self):
|
||||
return {
|
||||
"MenuText": "设备模板端子制作",
|
||||
"ToolTip": "打开 FCStd 设备模板端子制作面板",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return getattr(App, "ActiveDocument", None) is not None and Gui is not None
|
||||
|
||||
def Activated(self):
|
||||
if Gui is None or not hasattr(Gui, "Control"):
|
||||
return
|
||||
if hasattr(Gui.Control, "activeDialog") and Gui.Control.activeDialog():
|
||||
Gui.Control.closeDialog()
|
||||
Gui.Control.showDialog(TemplateAuthoringTaskPanel())
|
||||
|
||||
|
||||
_COMMANDS_REGISTERED = False
|
||||
_MENU_ACTION_INSTALLED = False
|
||||
|
||||
|
||||
def _tools_menu():
|
||||
if Gui is None or QtWidgets is None or QtGui is None:
|
||||
return None
|
||||
if not hasattr(Gui, "getMainWindow"):
|
||||
return None
|
||||
main_window = Gui.getMainWindow()
|
||||
if main_window is None:
|
||||
return None
|
||||
menu_bar = main_window.menuBar()
|
||||
for action in menu_bar.actions():
|
||||
menu = action.menu()
|
||||
if menu is not None and action.text().replace("&", "") in {"工具", "Tools"}:
|
||||
return menu
|
||||
return menu_bar.addMenu("工具")
|
||||
|
||||
|
||||
def install_menu_action():
|
||||
global _MENU_ACTION_INSTALLED
|
||||
if _MENU_ACTION_INSTALLED:
|
||||
return
|
||||
menu = _tools_menu()
|
||||
if menu is None or QtGui is None:
|
||||
return
|
||||
for action in menu.actions():
|
||||
if action.objectName() == MENU_ACTION_OBJECT_NAME:
|
||||
_MENU_ACTION_INSTALLED = True
|
||||
return
|
||||
action = QtGui.QAction("设备模板端子制作", menu)
|
||||
action.setObjectName(MENU_ACTION_OBJECT_NAME)
|
||||
action.triggered.connect(lambda: Gui.runCommand(COMMAND_NAME))
|
||||
menu.addAction(action)
|
||||
_MENU_ACTION_INSTALLED = True
|
||||
|
||||
|
||||
def register_commands():
|
||||
global _COMMANDS_REGISTERED
|
||||
if Gui is None or not hasattr(Gui, "addCommand"):
|
||||
return
|
||||
if not _COMMANDS_REGISTERED:
|
||||
Gui.addCommand(COMMAND_NAME, CommandOpenTemplateAuthoringPanel())
|
||||
_COMMANDS_REGISTERED = True
|
||||
install_menu_action()
|
||||
|
||||
|
||||
register_commands()
|
||||
@ -0,0 +1,85 @@
|
||||
import importlib
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
MODULE_DIR = REPO_ROOT / "src" / "Mod" / "FreeCADExchange"
|
||||
if str(MODULE_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(MODULE_DIR))
|
||||
|
||||
|
||||
def _install_fake_modules():
|
||||
fake_freecad = types.ModuleType("FreeCAD")
|
||||
fake_freecad.ActiveDocument = None
|
||||
fake_freecad.Console = types.SimpleNamespace(
|
||||
PrintMessage=lambda *args, **kwargs: None,
|
||||
PrintWarning=lambda *args, **kwargs: None,
|
||||
PrintError=lambda *args, **kwargs: None,
|
||||
)
|
||||
sys.modules["FreeCAD"] = fake_freecad
|
||||
|
||||
fake_freecadgui = types.ModuleType("FreeCADGui")
|
||||
fake_freecadgui.addCommand = lambda *args, **kwargs: None
|
||||
fake_freecadgui.Control = types.SimpleNamespace(
|
||||
activeDialog=lambda: False,
|
||||
showDialog=lambda panel: panel,
|
||||
closeDialog=lambda: None,
|
||||
)
|
||||
sys.modules["FreeCADGui"] = fake_freecadgui
|
||||
|
||||
fake_template_authoring = types.ModuleType("TemplateAuthoring")
|
||||
fake_template_authoring.validate_template_terminals = lambda doc: {
|
||||
"terminals": [],
|
||||
"total_terminals": 0,
|
||||
"valid_terminals": 0,
|
||||
"warnings": [],
|
||||
}
|
||||
fake_template_authoring._selection_position = lambda: None
|
||||
fake_template_authoring.create_template_terminal = lambda *args, **kwargs: None
|
||||
fake_template_authoring.save_template_as_fcstd = lambda *args, **kwargs: {}
|
||||
sys.modules["TemplateAuthoring"] = fake_template_authoring
|
||||
|
||||
|
||||
def _reload_panel_module():
|
||||
sys.modules.pop("TemplateAuthoringPanel", None)
|
||||
return importlib.import_module("TemplateAuthoringPanel")
|
||||
|
||||
|
||||
class TemplateAuthoringPanelTest(unittest.TestCase):
|
||||
def test_next_slot_name_uses_next_terminal_number(self):
|
||||
_install_fake_modules()
|
||||
panel_module = _reload_panel_module()
|
||||
|
||||
self.assertEqual(
|
||||
"T3",
|
||||
panel_module.next_slot_name(
|
||||
{
|
||||
"terminals": [
|
||||
{"slot_name": "P1"},
|
||||
{"slot_name": "P2"},
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def test_terminal_list_text_marks_invalid_terminal(self):
|
||||
_install_fake_modules()
|
||||
panel_module = _reload_panel_module()
|
||||
|
||||
rows = panel_module.terminal_list_text(
|
||||
{
|
||||
"terminals": [
|
||||
{"name": "Terminal_P1", "slot_name": "P1", "can_wire": True},
|
||||
{"name": "Broken", "slot_name": "", "can_wire": False},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(["P1 - Terminal_P1", "(unnamed) - Broken [invalid]"], rows)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue