diff --git a/docs/FreeCAD 端子显示连线保存回写开发文档.md b/docs/FreeCAD 端子显示连线保存回写开发文档.md index 7208208..51899f5 100644 --- a/docs/FreeCAD 端子显示连线保存回写开发文档.md +++ b/docs/FreeCAD 端子显示连线保存回写开发文档.md @@ -573,4 +573,5 @@ ManualWiring.py - 2026-05-20:明确 A 方案:STEP/STP/STE 只作为原始几何输入,正式可复用设备资源统一保存为带 LCS 电气端子的 FCStd 模板;后续设计 `TemplateAuthoring.py` 做模板制作工具。 - 2026-05-20:新增 FCStd 设备模板制作基础能力,支持把模型上的点位创建为带 `Role="Terminal"`、`CanWire=true`、`QetTemplateSlotName` 的模板端子 LCS;已用单元测试验证端子语义写入和模板校验逻辑。 - 2026-05-20:补充 A 方案资产流转设计,明确 `.FCStd` 为正式设备资产;zwl/QET 只负责选择、保存、导出 `.FCStd` 路径,FreeCADExchange 负责读取 LCS 端子语义并生成工程端子。 +- 2026-05-20:补上 `QET_Template_SaveAsFCStd` 模板保存命令,保存前会校验至少存在一个有效模板端子,并自动补 `.FCStd` 后缀;已用单元测试验证保存路径和端子校验结果。 ``` diff --git a/src/Mod/FreeCADExchange/TemplateAuthoring.py b/src/Mod/FreeCADExchange/TemplateAuthoring.py index 1abd26c..a4c4a7b 100644 --- a/src/Mod/FreeCADExchange/TemplateAuthoring.py +++ b/src/Mod/FreeCADExchange/TemplateAuthoring.py @@ -7,6 +7,17 @@ try: except ImportError: Gui = None +try: + from PySide6 import QtWidgets +except ImportError: + try: + from PySide2 import QtWidgets + except ImportError: + try: + from PySide import QtGui as QtWidgets + except ImportError: + QtWidgets = None + import TerminalObjects @@ -151,6 +162,35 @@ def validate_template_terminals(doc): return report +def _fcstd_path(path): + value = (path or "").strip() + if not value: + raise TemplateAuthoringError("A target FCStd path is required.") + if not value.lower().endswith(".fcstd"): + value = value + ".FCStd" + return value + + +def save_template_as_fcstd(doc, path): + if doc is None: + raise TemplateAuthoringError("An active FreeCAD document is required.") + + target_path = _fcstd_path(path) + report = validate_template_terminals(doc) + if report["total_terminals"] <= 0: + raise TemplateAuthoringError("At least one template terminal is required before saving.") + if report["warnings"]: + raise TemplateAuthoringError( + "Template terminals must be valid before saving: {0}".format( + "; ".join(report["warnings"]) + ) + ) + + doc.saveAs(target_path) + report["path"] = target_path + return report + + def _selection_position(): if Gui is None: return None @@ -236,6 +276,44 @@ class CommandValidateTemplateTerminals: App.Console.PrintWarning("[FreeCADExchange] {0}\n".format(warning)) +class CommandSaveTemplateAsFCStd: + def GetResources(self): + return { + "MenuText": "Save Template As FCStd", + "ToolTip": "Validate and save the current document as a reusable FCStd equipment template", + } + + def IsActive(self): + return App.ActiveDocument is not None + + def Activated(self): + if QtWidgets is None: + App.Console.PrintError("[FreeCADExchange] Qt file dialog is not available.\n") + return + + file_path, _selected_filter = QtWidgets.QFileDialog.getSaveFileName( + None, + "Save FCStd Equipment Template", + "", + "FreeCAD template (*.FCStd *.fcstd);;All files (*.*)", + ) + if not file_path: + return + + try: + report = save_template_as_fcstd(App.ActiveDocument, file_path) + App.Console.PrintMessage( + "[FreeCADExchange] Saved FCStd template: {0} ({1} terminals)\n".format( + report["path"], + report["valid_terminals"], + ) + ) + except Exception as exc: + App.Console.PrintError( + "[FreeCADExchange] template save failed: {0}\n".format(exc) + ) + + _COMMANDS_REGISTERED = False @@ -247,6 +325,7 @@ def register_commands(): return Gui.addCommand("QET_Template_AddTerminal", CommandAddTemplateTerminal()) Gui.addCommand("QET_Template_ValidateTerminals", CommandValidateTemplateTerminals()) + Gui.addCommand("QET_Template_SaveAsFCStd", CommandSaveTemplateAsFCStd()) _COMMANDS_REGISTERED = True diff --git a/tests/python/freecad_exchange_template_authoring_test.py b/tests/python/freecad_exchange_template_authoring_test.py index bdb1722..52c401a 100644 --- a/tests/python/freecad_exchange_template_authoring_test.py +++ b/tests/python/freecad_exchange_template_authoring_test.py @@ -88,6 +88,7 @@ class FakeDocument: self.Name = "TemplateDoc" self.Objects = [] self.recomputed = False + self.saved_path = "" def addObject(self, type_name, name): obj = FakeObject(name, type_name) @@ -103,6 +104,9 @@ class FakeDocument: def recompute(self): self.recomputed = True + def saveAs(self, path): + self.saved_path = path + def _reload_modules(): for name in ["TerminalObjects", "TemplateAuthoring"]: @@ -158,6 +162,19 @@ class TemplateAuthoringTest(unittest.TestCase): self.assertEqual(1, len(report["warnings"])) self.assertIn("QetTemplateSlotName", report["warnings"][0]) + def test_save_template_as_fcstd_adds_extension_and_saves_valid_template(self): + _install_fake_freecad() + template_authoring = _reload_modules() + app = sys.modules["FreeCAD"] + doc = FakeDocument() + template_authoring.create_template_terminal(doc, "P1", app.Vector(1, 2, 3)) + + report = template_authoring.save_template_as_fcstd(doc, "D:/tmp/current-transformer") + + self.assertEqual("D:/tmp/current-transformer.FCStd", doc.saved_path) + self.assertEqual("D:/tmp/current-transformer.FCStd", report["path"]) + self.assertEqual(1, report["valid_terminals"]) + if __name__ == "__main__": unittest.main()