From 82be30e69ce5da213ee33eb605eb9b35db94ecd2 Mon Sep 17 00:00:00 2001 From: Zhaowenlong Date: Wed, 20 May 2026 22:45:01 +0800 Subject: [PATCH] =?UTF-8?q?fix/FreeCAD=E6=A8=A1=E6=9D=BF=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E4=B8=8E=E5=90=AF=E5=8A=A8=E7=A8=B3=E5=AE=9A?= =?UTF-8?q?-zwl-0520?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mod/FreeCADExchange/InitGui.py | 181 +++++++++++++----- src/Mod/FreeCADExchange/TemplateAuthoring.py | 14 +- .../FreeCADExchange/TemplateAuthoringPanel.py | 47 ++++- ..._exchange_template_authoring_panel_test.py | 27 +++ ...reecad_exchange_template_authoring_test.py | 17 ++ 5 files changed, 229 insertions(+), 57 deletions(-) diff --git a/src/Mod/FreeCADExchange/InitGui.py b/src/Mod/FreeCADExchange/InitGui.py index 3b9785f..67e9b7f 100644 --- a/src/Mod/FreeCADExchange/InitGui.py +++ b/src/Mod/FreeCADExchange/InitGui.py @@ -2,73 +2,141 @@ import os from pathlib import Path +import traceback import FreeCADGui as Gui -try: - from PySide6 import QtCore -except ImportError: - try: - from PySide2 import QtCore - except ImportError: - from PySide import QtCore - -import ExchangeBootstrap -import ExchangeWriteBack -import ManualWiring -import TemplateAuthoring -import TemplateAuthoringPanel - COMMANDS = [ - TemplateAuthoringPanel.COMMAND_NAME, + "QET_Template_OpenAuthoringPanel", "QET_Template_AddTerminal", "QET_Template_ValidateTerminals", "QET_Template_SaveAsFCStd", ] -def _append_init_log(message): +def _append_init_log(message, os_module=os, path_class=Path): try: - local_app_data = os.environ.get("LOCALAPPDATA", "").strip() + local_app_data = os_module.environ.get("LOCALAPPDATA", "").strip() if local_app_data: - log_path = os.path.join(local_app_data, "QETDeps", "freecad_exchange_bootstrap.log") + log_path = os_module.path.join(local_app_data, "QETDeps", "freecad_exchange_bootstrap.log") else: - log_path = os.path.join(str(Path.home()), "AppData", "Local", "QETDeps", "freecad_exchange_bootstrap.log") - os.makedirs(os.path.dirname(log_path), exist_ok=True) + log_path = os_module.path.join( + str(path_class.home()), + "AppData", + "Local", + "QETDeps", + "freecad_exchange_bootstrap.log", + ) + os_module.makedirs(os_module.path.dirname(log_path), exist_ok=True) with open(log_path, "a", encoding="utf-8") as handle: handle.write(message + "\n") except Exception: pass -_append_init_log("InitGui imported") +_append_init_log("InitGui start") -def _register_exchange_commands(): + +def _safe_import(module_name, append_init_log=_append_init_log, traceback_module=traceback): try: - ExchangeWriteBack.ensure_document_observer_installed() + module = __import__(module_name) + append_init_log("InitGui imported {0}".format(module_name)) + return module except Exception: - pass + append_init_log( + "InitGui failed to import {0}:\n{1}".format( + module_name, + traceback_module.format_exc(), + ) + ) + return None + + +def _register_exchange_commands( + safe_import=_safe_import, + append_init_log=_append_init_log, + traceback_module=traceback, +): + exchange_write_back = safe_import("ExchangeWriteBack") + manual_wiring = safe_import("ManualWiring") + template_authoring = safe_import("TemplateAuthoring") + template_authoring_panel = safe_import("TemplateAuthoringPanel") try: - ExchangeWriteBack.register_commands() + if exchange_write_back is not None: + exchange_write_back.ensure_document_observer_installed() except Exception: - pass + append_init_log( + "InitGui failed to install write-back observer:\n{0}".format( + traceback_module.format_exc() + ) + ) try: - ManualWiring.register_commands() + if exchange_write_back is not None: + exchange_write_back.register_commands() except Exception: - pass + append_init_log( + "InitGui failed to register write-back commands:\n{0}".format( + traceback_module.format_exc() + ) + ) try: - TemplateAuthoring.register_commands() + if manual_wiring is not None: + manual_wiring.register_commands() except Exception: - pass + append_init_log( + "InitGui failed to register wiring commands:\n{0}".format( + traceback_module.format_exc() + ) + ) try: - TemplateAuthoringPanel.register_commands() + if template_authoring is not None: + template_authoring.register_commands() except Exception: - pass + append_init_log( + "InitGui failed to register template authoring commands:\n{0}".format( + traceback_module.format_exc() + ) + ) + + try: + if template_authoring_panel is not None: + template_authoring_panel.register_commands() + except Exception: + append_init_log( + "InitGui failed to register template panel command:\n{0}".format( + traceback_module.format_exc() + ) + ) + + +def _bootstrap_if_requested( + safe_import=_safe_import, + append_init_log=_append_init_log, + traceback_module=traceback, +): + exchange_bootstrap = safe_import("ExchangeBootstrap") + if exchange_bootstrap is None: + return + + try: + exchange_bootstrap.bootstrap_if_requested() + except Exception: + append_init_log( + "InitGui bootstrap_if_requested failed:\n{0}".format( + traceback_module.format_exc() + ) + ) + + +globals()["FreeCADExchange_COMMANDS"] = COMMANDS +globals()["FreeCADExchange_append_init_log"] = _append_init_log +globals()["FreeCADExchange_register_exchange_commands"] = _register_exchange_commands +globals()["FreeCADExchange_bootstrap_if_requested"] = _bootstrap_if_requested class FreeCADExchangeWorkbench(Gui.Workbench): @@ -99,20 +167,47 @@ class FreeCADExchangeWorkbench(Gui.Workbench): "................"}; """ - def Initialize(self): - _register_exchange_commands() - self.appendToolbar("QET模板", COMMANDS) - self.appendMenu("QET模板", COMMANDS) - _append_init_log("FreeCADExchangeWorkbench initialized") - - def Activated(self): - _register_exchange_commands() - _append_init_log("FreeCADExchangeWorkbench activated") + def Initialize( + self, + register_exchange_commands=FreeCADExchange_register_exchange_commands, + append_init_log=FreeCADExchange_append_init_log, + commands=FreeCADExchange_COMMANDS, + ): + register_exchange_commands() + self.appendToolbar("QET模板", commands) + self.appendMenu("QET模板", commands) + append_init_log("FreeCADExchangeWorkbench initialized") + + def Activated( + self, + register_exchange_commands=FreeCADExchange_register_exchange_commands, + append_init_log=FreeCADExchange_append_init_log, + ): + register_exchange_commands() + append_init_log("FreeCADExchangeWorkbench activated") def Deactivated(self): pass + def GetClassName(self): + return "Gui::PythonWorkbench" + -_register_exchange_commands() Gui.addWorkbench(FreeCADExchangeWorkbench()) -QtCore.QTimer.singleShot(0, ExchangeBootstrap.bootstrap_if_requested) +_append_init_log("InitGui workbench registered") + +try: + from PySide6 import QtCore +except ImportError: + try: + from PySide2 import QtCore + except ImportError: + try: + from PySide import QtCore + except ImportError: + QtCore = None + +if QtCore is not None: + QtCore.QTimer.singleShot(0, FreeCADExchange_bootstrap_if_requested) +else: + FreeCADExchange_bootstrap_if_requested() diff --git a/src/Mod/FreeCADExchange/TemplateAuthoring.py b/src/Mod/FreeCADExchange/TemplateAuthoring.py index acf6273..1b8fa9e 100644 --- a/src/Mod/FreeCADExchange/TemplateAuthoring.py +++ b/src/Mod/FreeCADExchange/TemplateAuthoring.py @@ -221,8 +221,8 @@ def _selection_position(): class CommandAddTemplateTerminal: def GetResources(self): return { - "MenuText": "Add Template Terminal", - "ToolTip": "Create a reusable electrical terminal LCS for an FCStd equipment template", + "MenuText": "添加模板端子", + "ToolTip": "在 FCStd 设备模板中创建可接线端子 LCS", } def IsActive(self): @@ -257,8 +257,8 @@ class CommandAddTemplateTerminal: class CommandValidateTemplateTerminals: def GetResources(self): return { - "MenuText": "Validate Template Terminals", - "ToolTip": "Validate electrical terminal LCS objects in the current FCStd template", + "MenuText": "校验模板端子", + "ToolTip": "校验当前 FCStd 模板中的电气端子 LCS", } def IsActive(self): @@ -279,8 +279,8 @@ class CommandValidateTemplateTerminals: class CommandSaveTemplateAsFCStd: def GetResources(self): return { - "MenuText": "Save Template As FCStd", - "ToolTip": "Validate and save the current document as a reusable FCStd equipment template", + "MenuText": "保存模板为 FCStd", + "ToolTip": "校验并保存当前文档为可复用 FCStd 设备模板", } def IsActive(self): @@ -293,7 +293,7 @@ class CommandSaveTemplateAsFCStd: file_path, _selected_filter = QtWidgets.QFileDialog.getSaveFileName( None, - "Save FCStd Equipment Template", + "保存 FCStd 设备模板", "", "FreeCAD template (*.FCStd *.fcstd);;All files (*.*)", ) diff --git a/src/Mod/FreeCADExchange/TemplateAuthoringPanel.py b/src/Mod/FreeCADExchange/TemplateAuthoringPanel.py index 248eaf3..81efca9 100644 --- a/src/Mod/FreeCADExchange/TemplateAuthoringPanel.py +++ b/src/Mod/FreeCADExchange/TemplateAuthoringPanel.py @@ -27,6 +27,26 @@ COMMAND_NAME = "QET_Template_OpenAuthoringPanel" MENU_ACTION_OBJECT_NAME = "QET_Template_OpenAuthoringPanel_MenuAction" TOOLBAR_OBJECT_NAME = "QET_Template_Authoring_Toolbar" TOOLBAR_ACTION_OBJECT_NAME = "QET_Template_OpenAuthoringPanel_ToolbarAction" +TERMINAL_TYPE_OPTIONS = [ + ("通用", "generic"), + ("主回路", "primary"), + ("电源", "power"), + ("控制", "control"), +] + + +def terminal_type_value(combo): + value = None + if hasattr(combo, "currentData"): + value = combo.currentData() + if value: + return str(value).strip() + + text = combo.currentText().strip() if hasattr(combo, "currentText") else "" + for label, option_value in TERMINAL_TYPE_OPTIONS: + if text == label or text == option_value: + return option_value + return "generic" def next_slot_name(report): @@ -64,7 +84,8 @@ class TemplateAuthoringTaskPanel: type_row = QtWidgets.QHBoxLayout() type_row.addWidget(QtWidgets.QLabel("端子类型")) self.terminal_type_combo = QtWidgets.QComboBox() - self.terminal_type_combo.addItems(["generic", "primary", "power", "control"]) + for label, value in TERMINAL_TYPE_OPTIONS: + self.terminal_type_combo.addItem(label, value) type_row.addWidget(self.terminal_type_combo) layout.addLayout(type_row) @@ -140,7 +161,7 @@ class TemplateAuthoringTaskPanel: position = TemplateAuthoring._selection_position() if position is None: raise TemplateAuthoring.TemplateAuthoringError("请先在模型上选择端子位置。") - terminal_type = self.terminal_type_combo.currentText().strip() or "generic" + terminal_type = terminal_type_value(self.terminal_type_combo) TemplateAuthoring.create_template_terminal( doc, slot_name, @@ -283,6 +304,15 @@ def install_toolbar_action(): _TOOLBAR_ACTION_INSTALLED = True +def _warn_register_commands_failed(target, exc): + try: + App.Console.PrintWarning( + "[FreeCADExchange] {0} installation skipped: {1}\n".format(target, exc) + ) + except Exception: + pass + + def register_commands(): global _COMMANDS_REGISTERED if Gui is None or not hasattr(Gui, "addCommand"): @@ -290,8 +320,11 @@ def register_commands(): if not _COMMANDS_REGISTERED: Gui.addCommand(COMMAND_NAME, CommandOpenTemplateAuthoringPanel()) _COMMANDS_REGISTERED = True - install_menu_action() - install_toolbar_action() - - -register_commands() + try: + install_menu_action() + except RuntimeError as exc: + _warn_register_commands_failed("menu action", exc) + try: + install_toolbar_action() + except RuntimeError as exc: + _warn_register_commands_failed("toolbar action", exc) diff --git a/tests/python/freecad_exchange_template_authoring_panel_test.py b/tests/python/freecad_exchange_template_authoring_panel_test.py index 67933a7..4dd9b06 100644 --- a/tests/python/freecad_exchange_template_authoring_panel_test.py +++ b/tests/python/freecad_exchange_template_authoring_panel_test.py @@ -49,6 +49,33 @@ def _reload_panel_module(): class TemplateAuthoringPanelTest(unittest.TestCase): + def test_register_commands_ignores_menu_install_runtime_errors(self): + _install_fake_modules() + panel_module = _reload_panel_module() + panel_module._COMMANDS_REGISTERED = False + + def raise_deleted_menu_error(): + raise RuntimeError("Internal C++ object already deleted") + + panel_module.install_menu_action = raise_deleted_menu_error + panel_module.install_toolbar_action = raise_deleted_menu_error + + panel_module.register_commands() + + def test_terminal_type_options_show_chinese_labels_with_stable_values(self): + _install_fake_modules() + panel_module = _reload_panel_module() + + self.assertEqual( + [ + ("通用", "generic"), + ("主回路", "primary"), + ("电源", "power"), + ("控制", "control"), + ], + panel_module.TERMINAL_TYPE_OPTIONS, + ) + def test_next_slot_name_uses_next_terminal_number(self): _install_fake_modules() panel_module = _reload_panel_module() diff --git a/tests/python/freecad_exchange_template_authoring_test.py b/tests/python/freecad_exchange_template_authoring_test.py index 8f0517a..2c4a839 100644 --- a/tests/python/freecad_exchange_template_authoring_test.py +++ b/tests/python/freecad_exchange_template_authoring_test.py @@ -120,6 +120,23 @@ def _reload_modules(): class TemplateAuthoringTest(unittest.TestCase): + def test_template_authoring_command_titles_are_chinese(self): + _install_fake_freecad() + template_authoring = _reload_modules() + + self.assertEqual( + "添加模板端子", + template_authoring.CommandAddTemplateTerminal().GetResources()["MenuText"], + ) + self.assertEqual( + "校验模板端子", + template_authoring.CommandValidateTemplateTerminals().GetResources()["MenuText"], + ) + self.assertEqual( + "保存模板为 FCStd", + template_authoring.CommandSaveTemplateAsFCStd().GetResources()["MenuText"], + ) + def test_import_skips_command_registration_when_gui_has_no_add_command(self): _install_fake_freecad_without_gui_commands()