Commit af0b8944 authored by jizhou's avatar jizhou

feat: UI增强 - 重命名为SimuFlow, 可调节侧边栏, 端口位置优化, 内置符号编辑功能

- 应用重命名: EPLAN Visualizer → SimuFlow (Toolbar + index.html)
- 侧边栏: 新增拖拽调节宽度功能, 匹配ComponentEditor样式
- 导入tab文字改为 EPLAN导入
- 所有内置符号端口从顶部迁移至左/右/底部, 避免与header重叠
- Switch节点端口: False→FalseVal, True→TrueVal
- 内置符号编辑: 新增编辑开关, 开启后显示编辑/删除按钮
- 编辑内置符号时加载完整数据到符号编辑器
- 修复内置符号保存写回DEVICE_CATEGORIES而非自定义库
- 修复getDeviceType读取实时数据避免缓存不一致
- 新增runtime-setup skill (nvm/端口转发)
parent 10cbf803
......@@ -72,6 +72,7 @@ npm run build
| `modelica-export` | .mo 导出、模型映射(非因果建模) |
| `data-import` | xlsx 导入、JSON 导入导出、自动布局 |
| `ui-styling` | 暗色主题、CSS Modules、工具栏/侧栏/属性面板 |
| `runtime-setup` | nvm 环境搭建、项目启动、端口转发 |
## 建模理念
......
---
name: runtime-setup
description: 运行环境搭建与项目启动 - 使用 nvm 管理 Node.js/npm,Linux 下端口转发供 Windows 访问
---
# 运行环境搭建与项目启动
> 当用户提到「运行」「启动」「配置运行环境」「搭建环境」等关键字时触发本 Skill。
## 1. Node.js 环境(通过 nvm 管理)
### 1.1 检查 nvm 是否已安装
```bash
bash -c 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version 2>&1 || echo "NVM_NOT_FOUND"'
```
- 若输出 `NVM_NOT_FOUND`,执行步骤 1.2 安装 nvm。
- 若输出版本号,跳到步骤 1.3。
### 1.2 安装 nvm
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
```
安装完成后**不需要**重启终端,后续命令统一使用以下前缀加载 nvm:
```bash
export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh";
```
### 1.3 安装 Node.js LTS
```bash
bash -c 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm install --lts'
```
### 1.4 验证安装
```bash
bash -c 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; node -v && npm -v'
```
## 2. 安装项目依赖
```bash
bash -c 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; npm install'
```
- 工作目录:项目根目录(含 `package.json` 处)
- 忽略 `npm fund` / `npm audit` 提示,不影响运行
## 3. 启动开发服务器
### 3.1 启动命令(监听所有网络接口)
```bash
bash -c 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; npx vite --host 0.0.0.0'
```
> **关键**:必须使用 `--host 0.0.0.0`,否则 Vite 默认只监听 `localhost`,从 Windows 无法访问。
### 3.2 确认启动成功
输出中应包含类似:
```
VITE v7.x.x ready in xxx ms
➜ Local: http://localhost:5173/
➜ Network: http://<linux-ip>:5173/
```
记录 `Network` 地址,告知用户在 Windows 浏览器中打开该地址。
## 4. Linux → Windows 端口转发(跨机器访问)
### 4.1 直接访问(同一局域网)
如果 Windows 和 Linux 在同一局域网,用户可直接在 Windows 浏览器打开:
```
http://<linux-ip>:5173/
```
### 4.2 防火墙放行(如果无法访问)
```bash
sudo ufw allow 5173
```
### 4.3 SSH 端口转发(备选方案)
如果用户通过 SSH 连接 Linux,可在 Windows 端执行:
```powershell
ssh -L 5173:localhost:5173 <username>@<linux-ip>
```
然后在 Windows 浏览器打开 `http://localhost:5173/`
## 5. 注意事项
- **禁止使用 `sudo apt install npm`**:本项目统一使用 nvm 管理 Node.js 和 npm,避免系统级安装导致版本冲突。
- **nvm 命令前缀**:由于 nvm 是 shell 函数而非二进制文件,每条命令都需要先 source nvm.sh,统一使用 `bash -c 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; <命令>'` 格式。
- **默认端口**:Vite 默认使用 5173 端口,如被占用会自动递增(5174、5175…),以实际输出为准。
- **热更新**:开发服务器启动后,修改代码会自动热更新,无需重启。
......@@ -40,7 +40,7 @@ export const DEVICE_CATEGORIES = [
### 新增内置符号必须遵守
1. **code 必须全局唯一**(按分类区间分配:1000+ 基本电子, 100~999 电气控制, 2000+ PLC, 3000+ 水利, 4000+ 气动)
1. **code 必须全局唯一**(按分类区间分配:5000+ 流程, 1000+ 基本电子, 100~999 电气控制, 2000+ PLC, 3000+ 水利, 4000+ 气动)
2. **type 必须为英文小写下划线**,与 `modelMapping.js` 的 MODEL_MAP key 一致
3. **每个端口必须有 connector 字段**
......@@ -67,6 +67,7 @@ export const DEVICE_CATEGORIES = [
| PLC 通道 | di1, do1, ai1, ao1 | PLC 各模块 |
| 流体端口 | inlet, outlet, waterIn | 阀门、水泵 |
| 电源引脚 | vcc, gnd, pe | 传感器、电机 |
| 流程节点信号 | a, b, out, inVal, cond | 逻辑/数值节点 |
| 自定义符号默认 | p1, p2, p3... | addPort 自动生成 |
**禁止使用中文作为 connector 值。**
......@@ -75,9 +76,10 @@ export const DEVICE_CATEGORIES = [
```javascript
export const PORT_TYPES = {
power: { label: '电力', color: '#f44336' },
power: { label: '电力', color: '#FF9800' },
digital: { label: '数字量', color: '#2196F3' },
analog: { label: '模拟量', color: '#4CAF50' },
signal: { label: '信号', color: '#AB47BC' },
generic: { label: '通用', color: '#9E9E9E' },
water: { label: '水流', color: '#00BCD4' },
air: { label: '气流', color: '#8BC34A' },
......@@ -130,6 +132,7 @@ export const PORT_TYPES = {
| 分类 | 数量 | 符号 |
|------|------|------|
| 流程 | 11 | greater_than, less_than, equal, logic_and, logic_or, logic_not, logic_switch, val_integer, val_real, val_string, val_boolean |
| 基本电子元件 | 6 | resistor, capacitor, inductor, diode, voltage_source, ground |
| 电气控制 | 9 | terminal, contactor, relay, breaker, switch, motor, transformer, sensor, cable |
| PLC | 5 | plc_cpu, plc_di, plc_do, plc_ai, plc_ao |
......
......@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EPLAN Visualizer</title>
<title>SimuFlow</title>
</head>
<body>
<div id="root"></div>
......
......@@ -128,8 +128,8 @@ function CustomDeviceNode({ data, selected }) {
handlePos = { top: `${((HEADER_H + tH * port.position) / totalH) * 100}%` };
break;
case 'top':
// CSS 管 top:0 + transform 居中,我们只设 left
handlePos = { left: `${port.position * 100}%` };
// 偏移到 header 底部边缘,避免与 header 文字/图标重叠
handlePos = { left: `${port.position * 100}%`, top: `${(HEADER_H / totalH) * 100}%` };
break;
case 'bottom':
// CSS 管 bottom:0 + transform 居中,我们只设 left
......
......@@ -27,6 +27,9 @@ export default function Sidebar() {
return init;
});
const [sidebarWidth, setSidebarWidth] = useState(280);
const isDragging = useRef(false);
const [builtinEditMode, setBuiltinEditMode] = useState(false);
const connInputRef = useRef(null);
const {
......@@ -37,7 +40,7 @@ export default function Sidebar() {
edges,
} = useFlowStore();
const { templates, customCategories, startEditing, startNew, deleteTemplate, addCategory, deleteCategory, switchView } = useComponentLibrary();
const { templates, deleteTemplate, startEditing, startEditingBuiltin, switchView, startNew, customCategories, addCategory, deleteCategory } = useComponentLibrary();
// ==================== 文件导入 ====================
const handleImport = useCallback(async () => {
......@@ -71,6 +74,12 @@ export default function Sidebar() {
switchView('editor');
}, [startEditing, switchView]);
/** 编辑内置符号 */
const handleEditBuiltin = useCallback((item) => {
startEditingBuiltin(item);
switchView('editor');
}, [startEditingBuiltin, switchView]);
/** 新建自定义符号 */
const handleNewTemplate = useCallback(() => {
startNew();
......@@ -82,8 +91,31 @@ export default function Sidebar() {
setCollapsedCats(prev => ({ ...prev, [catName]: !prev[catName] }));
};
/** 拖拽调整宽度 */
const startResize = useCallback((e) => {
e.preventDefault();
isDragging.current = true;
const startX = e.clientX;
const startWidth = sidebarWidth;
function onMove(ev) {
const delta = ev.clientX - startX;
const newWidth = Math.max(200, Math.min(500, startWidth + delta));
setSidebarWidth(newWidth);
}
function onUp() {
isDragging.current = false;
window.removeEventListener('mousemove', onMove);
window.removeEventListener('mouseup', onUp);
}
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseup', onUp);
}, [sidebarWidth]);
return (
<div className={styles.sidebar}>
<div className={styles.sidebar} style={{ width: sidebarWidth, position: 'relative' }}>
{/* 右侧拖拽手柄 */}
<div className={styles.resizeHandle} onMouseDown={startResize} />
{/* Tab 切换 */}
<div className={styles.tabs}>
<button
......@@ -102,7 +134,7 @@ export default function Sidebar() {
className={`${styles.tab} ${activeTab === 'import' ? styles.activeTab : ''}`}
onClick={() => setActiveTab('import')}
>
导入
EPLAN导入
</button>
</div>
......@@ -151,7 +183,10 @@ export default function Sidebar() {
{/* ==================== 模型映射面板 ==================== */}
{activeTab === 'mapping' && (() => {
const allMappings = getAllMappings();
const categories = DEVICE_CATEGORIES;
// 过滤掉流程节点分类(流程节点无需模型映射,导出时直接生成 Modelica 内部语句)
const categories = DEVICE_CATEGORIES.filter(cat =>
!cat.items.every(item => allMappings[item.type]?.isFlowNode)
);
const saveMappingOverride = (type, field, value) => {
setMappingOverrides(prev => {
const next = { ...prev, [type]: { ...(prev[type] || {}), [field]: value } };
......@@ -163,7 +198,7 @@ export default function Sidebar() {
<div className={styles.panel}>
<div className={styles.sectionTitle}>模型映射配置</div>
<div style={{ fontSize: 10, color: '#666', padding: '0 0 8px 0' }}>
符号类型 → Modelica 模型名称。修改后导出 .mo 时自动生效。
符号类型 → Modelica 模型名称。修改后导出 .mo 时自动生效。流程节点无需映射。
</div>
{categories.map(cat => (
<div key={cat.category} style={{ marginBottom: 12 }}>
......@@ -263,6 +298,14 @@ export default function Sidebar() {
{collapsedCats['__builtin__'] ? '\u25B6' : '\u25BC'}
</span>
<span className={styles.categoryName}>内置符号</span>
<button
className={styles.newTemplateBtn}
onClick={(e) => { e.stopPropagation(); setBuiltinEditMode(prev => !prev); }}
style={builtinEditMode ? { background: '#6366f1', color: '#fff', borderColor: '#6366f1' } : {}}
title={builtinEditMode ? '关闭编辑模式' : '开启编辑模式'}
>
&#9998;
</button>
</div>
{!collapsedCats['__builtin__'] && (
<div className={styles.subTree}>
......@@ -293,7 +336,41 @@ export default function Sidebar() {
>
<span className={styles.libIcon}>{item.icon}</span>
<span className={styles.libLabel}>{item.label}</span>
{builtinEditMode ? (
<span className={styles.customActions}>
<button
className={styles.editBtn}
onClick={(e) => {
e.stopPropagation();
// 使用符号编辑器打开内置符号进行编辑
handleEditBuiltin(item);
}}
title="编辑"
>
&#9998;
</button>
<button
className={styles.deleteBtn}
onClick={(e) => {
e.stopPropagation();
if (window.confirm(`确认删除内置符号"${item.label}"?`)) {
// 从 DEVICE_CATEGORIES 中删除此项
const catObj = DEVICE_CATEGORIES.find(c => c.category === cat.category);
if (catObj) {
catObj.items = catObj.items.filter(i => i.code !== item.code);
// 触发重新渲染
setCollapsedCats(prev => ({ ...prev }));
}
}
}}
title="删除"
>
&#10005;
</button>
</span>
) : (
<span className={styles.libPortCount}>{item.ports.length}P</span>
)}
</div>
))}
</div>
......
.sidebar {
width: 280px;
background: #16161e;
border-right: 1px solid #2a2a3a;
display: flex;
......@@ -8,6 +7,41 @@
overflow: hidden;
}
/* 右侧拖拽调整宽度手柄 */
.resizeHandle {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 5px;
cursor: col-resize;
background: #2a2a3a;
flex-shrink: 0;
transition: background 0.15s;
z-index: 10;
}
.resizeHandle:hover,
.resizeHandle:active {
background: #6366f1;
}
.resizeHandle::after {
content: '';
position: absolute;
top: 50%;
left: 1px;
width: 3px;
height: 30px;
transform: translateY(-50%);
border-radius: 2px;
background: rgba(255,255,255,0.15);
}
.resizeHandle:hover::after {
background: rgba(255,255,255,0.4);
}
/* ===== Tabs ===== */
.tabs {
display: flex;
......
......@@ -91,7 +91,7 @@ export default function Toolbar() {
<div className={styles.toolbar}>
<div className={styles.brand}>
<span className={styles.logo}>&#9883;</span>
<span className={styles.title}>EPLAN Visualizer</span>
<span className={styles.title}>SimuFlow</span>
</div>
<div className={styles.divider} />
......
......@@ -8,6 +8,7 @@
* ports: [{ id, name, type, side, position }]
*/
import { create } from 'zustand';
import { DEVICE_CATEGORIES } from '../utils/constants';
const STORAGE_KEY = 'eplan-component-library';
const CATEGORIES_KEY = 'eplan-custom-categories';
......@@ -110,6 +111,34 @@ const useComponentLibrary = create((set, get) => ({
}
},
/** 开始编辑内置符号(将 DEVICE_CATEGORIES item 转为编辑器模板格式) */
startEditingBuiltin: (builtinItem) => {
const template = {
id: `builtin-${builtinItem.type}`,
name: builtinItem.label,
category: '内置',
color: builtinItem.color,
icon: builtinItem.icon,
width: builtinItem.width,
height: builtinItem.height,
shapes: [],
params: builtinItem.params ? builtinItem.params.map(p => ({ ...p })) : [],
ports: builtinItem.ports.map((p, i) => ({
id: p.id,
portId: i + 1,
name: p.name,
description: '',
type: p.type,
side: p.side,
position: p.position,
connector: p.connector,
})),
_builtinType: builtinItem.type,
_builtinCode: builtinItem.code,
};
set({ editingTemplate: JSON.parse(JSON.stringify(template)), editingHistory: [] });
},
/** 开始创建新模板 */
startNew: () => {
set({ editingTemplate: createBlankTemplate(), editingHistory: [] });
......@@ -213,6 +242,34 @@ const useComponentLibrary = create((set, get) => ({
const current = get().editingTemplate;
if (!current) return;
// 内置符号:写回 DEVICE_CATEGORIES(运行时内存)
if (current._builtinCode != null) {
for (const cat of DEVICE_CATEGORIES) {
const idx = cat.items.findIndex(i => i.code === current._builtinCode);
if (idx >= 0) {
const item = cat.items[idx];
item.label = current.name;
item.color = current.color;
item.icon = current.icon;
item.width = current.width;
item.height = current.height;
item.ports = current.ports.map(p => ({
id: p.id,
name: p.name,
side: p.side,
position: p.position,
type: p.type,
connector: p.connector,
}));
item.params = current.params ? current.params.map(p => ({ ...p })) : [];
break;
}
}
set({ editingTemplate: null });
return;
}
// 自定义符号:保存到 localStorage
const templates = [...get().templates];
const existingIdx = templates.findIndex(t => t.id === current.id);
if (existingIdx >= 0) {
......
This diff is collapsed.
......@@ -118,6 +118,63 @@ const MODEL_MAP = {
portMap: { 'p-ao1': 'AO1', 'p-ao2': 'AO2', 'p-com': 'COM' },
paramMap: {},
},
// ===== 流程节点 =====
greater_than: {
modelName: 'GreaterThan', isFlowNode: true, operator: '>',
portMap: { 'p-a': 'a', 'p-b': 'b', 'p-out': 'out' },
paramMap: {},
},
less_than: {
modelName: 'LessThan', isFlowNode: true, operator: '<',
portMap: { 'p-a': 'a', 'p-b': 'b', 'p-out': 'out' },
paramMap: {},
},
equal: {
modelName: 'Equal', isFlowNode: true, operator: '==',
portMap: { 'p-a': 'a', 'p-b': 'b', 'p-out': 'out' },
paramMap: {},
},
logic_and: {
modelName: 'And', isFlowNode: true, operator: 'and',
portMap: { 'p-a': 'a', 'p-b': 'b', 'p-out': 'out' },
paramMap: {},
},
logic_or: {
modelName: 'Or', isFlowNode: true, operator: 'or',
portMap: { 'p-a': 'a', 'p-b': 'b', 'p-out': 'out' },
paramMap: {},
},
logic_not: {
modelName: 'Not', isFlowNode: true, operator: 'not',
portMap: { 'p-in': 'inVal', 'p-out': 'out' },
paramMap: {},
},
logic_switch: {
modelName: 'Switch', isFlowNode: true, operator: 'if',
portMap: { 'p-switch': 'switchCtrl', 'p-false': 'falseVal', 'p-true': 'trueVal', 'p-out': 'out' },
paramMap: {},
},
val_integer: {
modelName: 'IntegerValue', isFlowNode: true, valueType: 'Integer',
portMap: { 'p-out': 'out' },
paramMap: { value: 'value' },
},
val_real: {
modelName: 'RealValue', isFlowNode: true, valueType: 'Real',
portMap: { 'p-out': 'out' },
paramMap: { value: 'value' },
},
val_string: {
modelName: 'StringValue', isFlowNode: true, valueType: 'String',
portMap: { 'p-out': 'out' },
paramMap: { value: 'value' },
},
val_boolean: {
modelName: 'BooleanValue', isFlowNode: true, valueType: 'Boolean',
portMap: { 'p-out': 'out' },
paramMap: { value: 'value' },
},
};
/**
......
......@@ -46,8 +46,6 @@ function toMoId(str) {
return id;
}
// ===== 核心导出函数 =====
/**
* 将 { nodes, edges } 转为 OpenModelica .mo 模型字符串
* @param {Object} data - { nodes: [], edges: [] }
......@@ -59,7 +57,7 @@ export function exportToModelica(data, modelName = 'Circuit') {
const warnings = [];
const errors = [];
const lines = [];
const instanceMap = {}; // nodeId → { instanceName, type, ports }
const instanceMap = {}; // nodeId → { instanceName, type, ports, isFlowNode }
// 读取用户自定义的映射覆盖
let mappingOverrides = {};
......@@ -70,20 +68,24 @@ export function exportToModelica(data, modelName = 'Circuit') {
lines.push(`model ${toMoId(modelName)}`);
lines.push('');
// ===== 1. 组件声明 =====
// ===== 1. 组件声明(跳过流程节点) =====
nodes.forEach((node, idx) => {
const td = node.data?.templateData;
const type = td?.type;
const label = node.data?.label || `comp_${idx}`;
const instanceName = toMoId(label) + '_' + (idx + 1);
const mapping = getModelMapping(type);
instanceMap[node.id] = {
instanceName,
type,
ports: td?.ports || [],
isFlowNode: mapping?.isFlowNode || false,
};
const mapping = getModelMapping(type);
// 流程节点不生成组件声明
if (mapping?.isFlowNode) return;
if (!mapping) {
// 检查用户自定义映射(自定义符号用 custom_${id} 作为 key)
......@@ -103,7 +105,7 @@ export function exportToModelica(data, modelName = 'Circuit') {
lines.push(` ${customOverride.modelName} ${instanceName}${paramStr};`);
return;
}
errors.push(`"${label}" (type: ${type || 'custom'}) 未建立模型映射,请先在“模型映射”中配置`);
errors.push(`"${label}" (type: ${type || 'custom'}) 未建立模型映射,请先在"模型映射"中配置`);
return;
}
......@@ -131,9 +133,70 @@ export function exportToModelica(data, modelName = 'Circuit') {
lines.push('');
// ===== 2. connect 方程 =====
// ===== 2. 构建流程节点端口到连接源的映射 =====
// flowInputMap: "nodeId:portId" → "对端变量表达式"
const flowInputMap = {};
edges.forEach(edge => {
const srcInfo = instanceMap[edge.source];
const tgtInfo = instanceMap[edge.target];
if (!srcInfo || !tgtInfo) return;
// 如果目标是流程节点,记录其输入端口的信号来源
if (tgtInfo.isFlowNode) {
const tgtPort = resolvePortName(edge.targetHandle, tgtInfo.type, tgtInfo.ports);
const srcPort = resolvePortName(edge.sourceHandle, srcInfo.type, srcInfo.ports);
flowInputMap[`${edge.target}:${tgtPort}`] = `${srcInfo.instanceName}.${srcPort}`;
}
});
// ===== 3. equation 段 =====
lines.push('equation');
// 3a. 流程节点表达式
nodes.forEach((node, idx) => {
const td = node.data?.templateData;
const type = td?.type;
const mapping = getModelMapping(type);
if (!mapping?.isFlowNode) return;
const info = instanceMap[node.id];
const inst = info.instanceName;
const pv = node.data?.paramValues || {};
// 辅助:获取输入端口的信号来源
const getInput = (portName) => flowInputMap[`${node.id}:${portName}`] || '0';
if (mapping.valueType) {
// 数值节点:直接输出常量
const rawVal = pv.value ?? td?.params?.[0]?.defaultValue ?? '0';
let valStr;
if (mapping.valueType === 'String') {
valStr = `"${rawVal}"`;
} else if (mapping.valueType === 'Boolean') {
valStr = rawVal === 'true' || rawVal === true ? 'true' : 'false';
} else {
const numVal = parseEngValue(rawVal);
valStr = numVal != null ? formatMoValue(numVal) : rawVal;
}
lines.push(` ${inst}.out = ${valStr};`);
} else if (mapping.operator === 'not') {
// 非:一元运算
lines.push(` ${inst}.out = not ${getInput('inVal')};`);
} else if (mapping.operator === 'if') {
// Switch 条件选择(Blender 风格:Switch=true → True input, Switch=false → False input)
const switchCtrl = getInput('switchCtrl');
const falseVal = getInput('falseVal');
const trueVal = getInput('trueVal');
lines.push(` ${inst}.out = if ${switchCtrl} then ${trueVal} else ${falseVal};`);
} else if (mapping.operator) {
// 二元运算
const a = getInput('a');
const b = getInput('b');
lines.push(` ${inst}.out = ${a} ${mapping.operator} ${b};`);
}
});
// 3b. 物理节点的 connect 语句(跳过涉及流程节点的连接)
edges.forEach(edge => {
const srcInfo = instanceMap[edge.source];
const tgtInfo = instanceMap[edge.target];
......@@ -142,6 +205,9 @@ export function exportToModelica(data, modelName = 'Circuit') {
return;
}
// 流程节点的连接已在表达式中处理,跳过
if (srcInfo.isFlowNode || tgtInfo.isFlowNode) return;
// 通过映射模块解析端口名
const srcPort = resolvePortName(edge.sourceHandle, srcInfo.type, srcInfo.ports);
const tgtPort = resolvePortName(edge.targetHandle, tgtInfo.type, tgtInfo.ports);
......@@ -151,7 +217,7 @@ export function exportToModelica(data, modelName = 'Circuit') {
lines.push('');
// ===== 3. annotation =====
// ===== 4. annotation =====
if (nodes.length > 0) {
lines.push(' annotation(Diagram(coordinateSystem(preserveAspectRatio=false,');
lines.push(' extent={{-200,-200},{800,800}})));');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment