From 104d41d29417f2ff3b4c307a9e2e8ba75dc39a97 Mon Sep 17 00:00:00 2001 From: ljz <425868052@qq.com> Date: Wed, 15 Jul 2026 15:16:58 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=84=E8=8C=83=E5=8C=96xml=E5=86=85?= =?UTF-8?q?=E5=AE=B9=EF=BC=8C=E5=B9=B6=E5=9C=A8xml=E4=B8=AD=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BB=BF=E7=9C=9F=E8=AE=BE=E7=BD=AE=EF=BC=8C=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E5=8C=96=E7=89=88=E6=9C=AC=E5=8F=B7=E7=AD=89=E5=86=85?= =?UTF-8?q?=E5=AE=B9=EF=BC=8C=E8=A1=A5=E5=85=85xml=E8=A7=84=E8=8C=83md?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++ app/main.py | 22 ++++++++- docs/system-xml-v1.md | 70 +++++++++++++++++++++++++++ frontend/src/App.tsx | 10 +++- schemas/system-simulation-v1.xsd | 80 +++++++++++++++++++++++++++++++ tests/test_system_xml_protocol.py | 67 ++++++++++++++++++++++++++ 6 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 docs/system-xml-v1.md create mode 100644 schemas/system-simulation-v1.xsd create mode 100644 tests/test_system_xml_protocol.py diff --git a/README.md b/README.md index bcf5f6e..bc1f169 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # SystemSimulationApp +ReactFlow 系统建模与 PythonModels 仿真应用。 + +## 文档 + +- [System XML v1 协议](docs/system-xml-v1.md) +- [System XML v1 XSD](schemas/system-simulation-v1.xsd) + diff --git a/app/main.py b/app/main.py index 281e3b9..3cd340e 100644 --- a/app/main.py +++ b/app/main.py @@ -14,6 +14,8 @@ from pydantic import BaseModel, Field app = FastAPI(title="System Simulation ReactFlow App") FRONTEND_DIST_DIR = Path(__file__).resolve().parent.parent / "frontend" / "dist" PROJECT_STORAGE_DIR = Path(__file__).parent / "data" / "reactflow-projects" +SYSTEM_XML_SCHEMA_VERSION = "1" +SYSTEM_XML_UNIT_SYSTEM = "SI" class ReactFlowPosition(BaseModel): @@ -190,7 +192,25 @@ def simulate_reactflow_testmodel(payload: ReactFlowProjectPayload) -> dict[str, def build_reactflow_system_xml(project: ReactFlowProjectPayload) -> bytes: - system = ET.Element("System", {"name": project.name}) + system = ET.Element( + "System", + { + "name": project.name, + "schemaVersion": SYSTEM_XML_SCHEMA_VERSION, + "unitSystem": SYSTEM_XML_UNIT_SYSTEM, + }, + ) + ET.SubElement( + system, + "Simulation", + { + "tStart": str(project.simulation.t_start), + "tStop": str(project.simulation.t_stop), + "step": str(project.simulation.step), + "maxStep": str(project.simulation.max_step), + "method": project.simulation.method, + }, + ) components_node = ET.SubElement(system, "Components") connections_node = ET.SubElement(system, "Connections") diff --git a/docs/system-xml-v1.md b/docs/system-xml-v1.md new file mode 100644 index 0000000..c91aa24 --- /dev/null +++ b/docs/system-xml-v1.md @@ -0,0 +1,70 @@ +# System XML v1 协议 + +System XML 是 ReactFlow 前端与 PythonModels 仿真层之间的稳定交换格式。 + +## 基本约定 + +- 根元素固定为 `System`,`schemaVersion` 固定为 `1`。 +- `unitSystem` 固定为 `SI`。组件参数必须保存为 SI 基准值,界面显示单位不进入数值换算语义。 +- 子元素顺序固定为 `Simulation`、`Components`、`Connections`。 +- `Component.id` 是稳定的仿真实例 ID;`name` 是用户可编辑的显示名称。 +- `Component.type` 是 PythonModels 模型类型;`componentType` 是前端组件类型。 +- `Connection.source/target` 只标识图形拓扑端点,不代表仿真中的实际流动方向。正流和倒流由求解器决定。 +- v1 文档不携带结果数据;仿真结果通过运行接口返回。 + +## 完整示例 + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +## Simulation + +| 属性 | 类型 | 含义 | 约束 | +|---|---|---|---| +| `tStart` | double | 仿真起始时间,单位 s | 有限数 | +| `tStop` | double | 仿真结束时间,单位 s | 必须大于 `tStart` | +| `step` | double | 结果采样间隔,单位 s | 必须大于 0 | +| `maxStep` | double | 求解器最大积分步长,单位 s | 必须大于 0 | +| `method` | string | 求解器名称,例如 `BDF` | 非空 | + +## Components + +每个 `Component` 必须包含唯一 `id`、显示名称、模型类型、前端类型和画布坐标。组件下可以包含多个 `Port` 和 `Parameter`。 + +参数的 `value` 必须是可转换为有限浮点数的 SI 值。具体组件支持的端口、参数和取值范围由后续组件注册表协议约束。 + +## Connections + +每个 `Connection` 必须包含唯一 `id` 和两个完整端点: + +- `source` + `sourcePort` +- `target` + `targetPort` + +端点中的组件 ID 必须存在,端口名称必须属于对应组件。v1 前端仍使用 source/target 组织连线,但仿真层必须将它们视为连接的两个端点,不得直接解释为固定物理流向。 + +## 版本兼容 + +- 解析器遇到缺少 `schemaVersion` 的旧 XML 时,应明确报告“旧协议”,不能静默当作 v1。 +- v1 新增可选字段时必须保持旧解析器可忽略;删除字段或改变字段语义时必须升级主版本。 +- XSD 只验证文档结构和基础数据类型。跨字段约束、组件端口规则和拓扑可求解性由仿真校验层负责。 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 93d4158..afcbcb7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1814,10 +1814,18 @@ function buildSystemXml(project: ReactFlowProjectPayload) { const xmlDocument = document.implementation.createDocument("", "System", null); const system = xmlDocument.documentElement; system.setAttribute("name", project.name); + system.setAttribute("schemaVersion", "1"); + system.setAttribute("unitSystem", "SI"); + const simulation = xmlDocument.createElement("Simulation"); + simulation.setAttribute("tStart", String(project.simulation.t_start)); + simulation.setAttribute("tStop", String(project.simulation.t_stop)); + simulation.setAttribute("step", String(project.simulation.step)); + simulation.setAttribute("maxStep", String(project.simulation.max_step)); + simulation.setAttribute("method", project.simulation.method); const components = xmlDocument.createElement("Components"); const connections = xmlDocument.createElement("Connections"); - system.append(components, connections); + system.append(simulation, components, connections); project.nodes.forEach((node) => { const component = xmlDocument.createElement("Component"); diff --git a/schemas/system-simulation-v1.xsd b/schemas/system-simulation-v1.xsd new file mode 100644 index 0000000..399ab38 --- /dev/null +++ b/schemas/system-simulation-v1.xsd @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_system_xml_protocol.py b/tests/test_system_xml_protocol.py new file mode 100644 index 0000000..f875a41 --- /dev/null +++ b/tests/test_system_xml_protocol.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import unittest +from xml.etree import ElementTree as ET + +from app.main import ReactFlowProjectPayload, build_reactflow_system_xml + + +class SystemXmlProtocolTests(unittest.TestCase): + def test_v1_xml_contains_simulation_and_stable_section_order(self) -> None: + project = ReactFlowProjectPayload( + name="protocol-test", + nodes=[ + { + "id": "cylinder_1", + "type": "simulationComponent", + "position": {"x": 12.5, "y": 24.0}, + "data": { + "label": "source-cylinder", + "componentType": "cylinder", + "modelType": "cylinder", + "ports": ["port_b"], + "parameters": {"volume": 0.01, "p0": 35000000, "T0": 300}, + }, + } + ], + edges=[], + simulation={ + "t_start": 1.0, + "t_stop": 5.0, + "step": 0.2, + "max_step": 0.01, + "method": "BDF", + }, + ) + + root = ET.fromstring(build_reactflow_system_xml(project)) + + self.assertEqual(root.tag, "System") + self.assertEqual(root.attrib["name"], "protocol-test") + self.assertEqual(root.attrib["schemaVersion"], "1") + self.assertEqual(root.attrib["unitSystem"], "SI") + self.assertEqual([child.tag for child in root], ["Simulation", "Components", "Connections"]) + + simulation = root.find("Simulation") + self.assertIsNotNone(simulation) + assert simulation is not None + self.assertEqual( + simulation.attrib, + { + "tStart": "1.0", + "tStop": "5.0", + "step": "0.2", + "maxStep": "0.01", + "method": "BDF", + }, + ) + + component = root.find("./Components/Component") + self.assertIsNotNone(component) + assert component is not None + self.assertEqual(component.attrib["id"], "cylinder_1") + self.assertEqual(component.find("./Parameter[@name='p0']").attrib["value"], "35000000") + + +if __name__ == "__main__": + unittest.main()