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