Commit 6f2731f4 authored by fenghen777's avatar fenghen777

feat: 仿真时长参数支持 + 按钮布局优化 + 智能状态重置

parent abe22eec
This diff is collapsed.
...@@ -84,9 +84,17 @@ ...@@ -84,9 +84,17 @@
**请求:** **请求:**
```json ```json
{ "model_name": "Circuit" } {
"model_name": "Circuit",
"stop_time": 2.0
}
``` ```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| model_name | string | ✅ | 模型名称 |
| stop_time | number | - | 仿真终止时间(秒),默认 1.0 |
**成功响应** (`code: 0`): **成功响应** (`code: 0`):
```json ```json
{ {
...@@ -138,6 +146,7 @@ ...@@ -138,6 +146,7 @@
"sim_mode": "switch", "sim_mode": "switch",
"ports": ["p", "n"], "ports": ["p", "n"],
"fmu_var_prefix": "idealSwitch_1", "fmu_var_prefix": "idealSwitch_1",
"control_var": "idealSwitch_1.closed",
"topic": "/Circuit_1/idealSwitch_1", "topic": "/Circuit_1/idealSwitch_1",
"hw_label": "开关", "hw_label": "开关",
"hw_node_id": "new-device-3" "hw_node_id": "new-device-3"
...@@ -162,6 +171,7 @@ ...@@ -162,6 +171,7 @@
| hardware_ports[].topic | string | - | ROS2 话题前缀 | | hardware_ports[].topic | string | - | ROS2 话题前缀 |
| hardware_ports[].hw_label | string | - | 节点显示名称 | | hardware_ports[].hw_label | string | - | 节点显示名称 |
| hardware_ports[].hw_node_id | string | - | 前端节点 ID | | hardware_ports[].hw_node_id | string | - | 前端节点 ID |
| hardware_ports[].control_var | string | - | 控制变量全名,如 `idealSwitch_1.closed` |
| step_size | number | - | 仿真步长(秒),默认 0.001 | | step_size | number | - | 仿真步长(秒),默认 0.001 |
| duration | number | - | 仿真时长(秒),默认 10.0 | | duration | number | - | 仿真时长(秒),默认 10.0 |
......
...@@ -54,6 +54,7 @@ export default function ProjectPanel() { ...@@ -54,6 +54,7 @@ export default function ProjectPanel() {
const [showCompileLog, setShowCompileLog] = useState(false); const [showCompileLog, setShowCompileLog] = useState(false);
const [showExecuteLog, setShowExecuteLog] = useState(false); const [showExecuteLog, setShowExecuteLog] = useState(false);
const [showResultsMode, setShowResultsMode] = useState(null); // null | 'sim' | 'hil' const [showResultsMode, setShowResultsMode] = useState(null); // null | 'sim' | 'hil'
const [simDuration, setSimDuration] = useState(1);
const [hilDuration, setHilDuration] = useState(10); const [hilDuration, setHilDuration] = useState(10);
const activeProject = projects.find(p => p.id === activeProjectId) || null; const activeProject = projects.find(p => p.id === activeProjectId) || null;
...@@ -228,12 +229,12 @@ export default function ProjectPanel() { ...@@ -228,12 +229,12 @@ export default function ProjectPanel() {
/> />
</div> </div>
{/* 操作按钮 */} {/* 编译按钮 */}
<div className={styles.simBtnRow}>
<button <button
className={`${styles.simBtn} ${styles.compile}`} className={`${styles.simBtn} ${styles.compile}`}
onClick={(e) => { e.stopPropagation(); compileProject(p.id); }} onClick={(e) => { e.stopPropagation(); compileProject(p.id); }}
disabled={p.compileStatus === 'compiling'} disabled={p.compileStatus === 'compiling'}
style={{ width: '100%', marginBottom: 6 }}
> >
{p.compileStatus === 'compiling' ? ( {p.compileStatus === 'compiling' ? (
<><span className={styles.spinner}></span> 编译中…</> <><span className={styles.spinner}></span> 编译中…</>
...@@ -241,11 +242,33 @@ export default function ProjectPanel() { ...@@ -241,11 +242,33 @@ export default function ProjectPanel() {
<>▶ 编译模型</> <>▶ 编译模型</>
)} )}
</button> </button>
{/* 普通仿真: 时长 + 执行按钮 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<label style={{ fontSize: 11, color: '#888', whiteSpace: 'nowrap' }}>仿真时长:</label>
<input
type="number"
min="0.1" max="3600" step="0.1"
value={simDuration}
onChange={(e) => setSimDuration(e.target.value === '' ? '' : Number(e.target.value))}
onBlur={() => { if (!simDuration || simDuration <= 0) setSimDuration(1); }}
onClick={(e) => e.stopPropagation()}
disabled={p.compileStatus !== 'success'}
style={{
flex: 1, padding: '3px 6px', fontSize: 11,
background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)',
borderRadius: 4, color: '#ccc', outline: 'none',
opacity: p.compileStatus !== 'success' ? 0.4 : 1,
}}
/>
<span style={{ fontSize: 11, color: '#666' }}></span>
</div>
<button <button
className={`${styles.simBtn} ${styles.execute}`} className={`${styles.simBtn} ${styles.execute}`}
onClick={(e) => { e.stopPropagation(); executeProject(p.id); }} onClick={(e) => { e.stopPropagation(); executeProject(p.id, { stopTime: simDuration }); }}
disabled={p.compileStatus !== 'success' || p.executeStatus === 'running'} disabled={p.compileStatus !== 'success' || p.executeStatus === 'running'}
title={p.compileStatus !== 'success' ? '请先编译成功' : '下发等效设备执行'} title={p.compileStatus !== 'success' ? '请先编译成功' : '下发等效设备执行'}
style={{ width: '100%', marginBottom: 6 }}
> >
{p.executeStatus === 'running' ? ( {p.executeStatus === 'running' ? (
<><span className={styles.spinner}></span> 下发中…</> <><span className={styles.spinner}></span> 下发中…</>
...@@ -253,27 +276,28 @@ export default function ProjectPanel() { ...@@ -253,27 +276,28 @@ export default function ProjectPanel() {
<>⏵ 下发等效设备执行</> <>⏵ 下发等效设备执行</>
)} )}
</button> </button>
</div>
{/* 半实物仿真按钮 */} {/* 半实物仿真: 时长 + HIL 按钮 */}
{(() => { {(() => {
const hwCount = (p.nodes || []).filter(n => n.data?.isHardware).length; const hwCount = (p.nodes || []).filter(n => n.data?.isHardware).length;
const hilDisabled = hwCount === 0 || p.compileStatus !== 'success' || p.hilStatus === 'starting' || p.hilStatus === 'running';
return ( return (
<> <>
{/* 仿真时长输入 */} {hwCount > 0 && (
{hwCount > 0 && p.hilStatus !== 'running' && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<label style={{ fontSize: 11, color: '#888', whiteSpace: 'nowrap' }}>仿真时长:</label> <label style={{ fontSize: 11, color: '#888', whiteSpace: 'nowrap' }}>HIL时长:</label>
<input <input
type="number" type="number"
min="1" max="3600" step="1" min="1" max="3600" step="1"
value={hilDuration} value={hilDuration}
onChange={(e) => setHilDuration(Number(e.target.value) || 10)} onChange={(e) => setHilDuration(Number(e.target.value) || 10)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
disabled={hilDisabled}
style={{ style={{
flex: 1, padding: '3px 6px', fontSize: 11, flex: 1, padding: '3px 6px', fontSize: 11,
background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)', background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)',
borderRadius: 4, color: '#ccc', outline: 'none', borderRadius: 4, color: '#ccc', outline: 'none',
opacity: hilDisabled ? 0.4 : 1,
}} }}
/> />
<span style={{ fontSize: 11, color: '#666' }}></span> <span style={{ fontSize: 11, color: '#666' }}></span>
...@@ -287,8 +311,8 @@ export default function ProjectPanel() { ...@@ -287,8 +311,8 @@ export default function ProjectPanel() {
borderColor: hwCount > 0 ? 'rgba(251,146,60,0.3)' : undefined, borderColor: hwCount > 0 ? 'rgba(251,146,60,0.3)' : undefined,
color: hwCount > 0 ? '#fb923c' : undefined, color: hwCount > 0 ? '#fb923c' : undefined,
}} }}
disabled={hwCount === 0 || p.hilStatus === 'starting' || p.hilStatus === 'running'} disabled={hilDisabled}
title={hwCount === 0 ? '请先在属性面板中将节点标记为实物' : `${hwCount} 个实物节点`} title={p.compileStatus !== 'success' ? '请先编译成功' : hwCount === 0 ? '请先在属性面板中将节点标记为实物' : `${hwCount} 个实物节点`}
onClick={(e) => { e.stopPropagation(); startHil(p.id, { duration: hilDuration }); }} onClick={(e) => { e.stopPropagation(); startHil(p.id, { duration: hilDuration }); }}
> >
{p.hilStatus === 'starting' ? ( {p.hilStatus === 'starting' ? (
......
...@@ -53,9 +53,18 @@ const useProjectStore = create((set, get) => ({ ...@@ -53,9 +53,18 @@ const useProjectStore = create((set, get) => ({
if (activeProjectId) { if (activeProjectId) {
// 更新已有项目 // 更新已有项目
const prev = projects.find(p => p.id === activeProjectId);
// 仅当节点/连线发生变化时重置编译/执行/HIL 状态
const nodesChanged = JSON.stringify(prev?.nodes) !== JSON.stringify(nodes)
|| JSON.stringify(prev?.edges) !== JSON.stringify(edges);
const resetFields = nodesChanged ? {
compileStatus: 'none', compileResult: null,
executeStatus: 'none', executeResult: null,
hilStatus: 'none', hilResult: null,
} : {};
const updated = projects.map(p => const updated = projects.map(p =>
p.id === activeProjectId p.id === activeProjectId
? { ...p, nodes, edges, updatedAt: now, name: name || p.name } ? { ...p, nodes, edges, updatedAt: now, name: name || p.name, ...resetFields }
: p : p
); );
persistProjects(updated); persistProjects(updated);
...@@ -211,7 +220,7 @@ const useProjectStore = create((set, get) => ({ ...@@ -211,7 +220,7 @@ const useProjectStore = create((set, get) => ({
}, },
/** 执行仿真 — 调用后端 API(仅编译成功后可执行) */ /** 执行仿真 — 调用后端 API(仅编译成功后可执行) */
executeProject: async (id) => { executeProject: async (id, { stopTime = 1.0 } = {}) => {
const { projects } = get(); const { projects } = get();
const project = projects.find(p => p.id === id); const project = projects.find(p => p.id === id);
if (!project || project.compileStatus !== 'success') return; if (!project || project.compileStatus !== 'success') return;
...@@ -228,7 +237,7 @@ const useProjectStore = create((set, get) => ({ ...@@ -228,7 +237,7 @@ const useProjectStore = create((set, get) => ({
try { try {
// 响应格式: { code, message, data: { sim_log, csv_data } } // 响应格式: { code, message, data: { sim_log, csv_data } }
const resp = await executeModel(project.modelName || 'Circuit'); const resp = await executeModel(project.modelName || 'Circuit', stopTime);
const isOk = resp.code === 0; const isOk = resp.code === 0;
setStatus({ setStatus({
executeStatus: isOk ? 'success' : 'error', executeStatus: isOk ? 'success' : 'error',
...@@ -251,7 +260,6 @@ const useProjectStore = create((set, get) => ({ ...@@ -251,7 +260,6 @@ const useProjectStore = create((set, get) => ({
/** 启动半实物仿真 */ /** 启动半实物仿真 */
startHil: async (id, { duration = 10, stepSize = 0.001 } = {}) => { startHil: async (id, { duration = 10, stepSize = 0.001 } = {}) => {
get().saveProject();
const { projects } = get(); const { projects } = get();
const project = projects.find(p => p.id === id); const project = projects.find(p => p.id === id);
......
...@@ -39,10 +39,10 @@ export async function compileModel(moCode, modelName) { ...@@ -39,10 +39,10 @@ export async function compileModel(moCode, modelName) {
* @param {string} modelName - 已编译的模型名称 * @param {string} modelName - 已编译的模型名称
* @returns {Promise<{code, message, data: {sim_log, model_name, csv_data?}}>} * @returns {Promise<{code, message, data: {sim_log, model_name, csv_data?}}>}
*/ */
export async function executeModel(modelName) { export async function executeModel(modelName, stopTime = 1.0) {
return request('/execute', { return request('/execute', {
method: 'POST', method: 'POST',
body: JSON.stringify({ model_name: modelName }), body: JSON.stringify({ model_name: modelName, stop_time: stopTime }),
}); });
} }
......
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