多物理场仿真组件 - 风力发电机模拟器
简介
这是一个基于Web的多物理场仿真组件,专注于模拟风力发电机在不同工况下的行为。它提供了一个交互式界面,允许用户设置结构、流体和电磁相关的参数,运行模拟仿真,并可视化结果。
该组件旨在为工程设计、教育和研究提供一个直观的工具,用于理解复杂的多物理场现象。
功能特点
- 多物理场支持: 支持结构分析、流体动力学分析和电磁场分析,以及它们的耦合模拟(耦合分析待实现)。
- 参数化设置: 通过左侧面板设置材料属性、载荷条件、流体属性、电磁属性和仿真控制参数。
- 实时3D可视化: 使用Three.js渲染风力发电机的简化3D模型,并根据仿真结果和用户选择更新颜色映射以显示物理量分布(如应力、速度)。
- 模型交互: 支持通过轨道控制器(OrbitControls)旋转、缩放和平移3D视图。
- 结果展示: 在右侧面板通过选项卡展示不同物理场的关键结果指标(如最大应力、最大速度、安全系数等)和相关图表。
- 仿真流程模拟: 模拟仿真计算过程,显示进度条、处理时间和状态信息。
- 仿真报告: 在右侧面板实时显示仿真过程中的关键信息、警告和错误。
- 响应式布局: 界面元素在不同屏幕尺寸下会进行调整。
使用说明
前提条件
- 现代浏览器: 支持WebGL和最新JavaScript特性(推荐Chrome, Firefox, Edge, Safari)。
- 网络连接: 需要加载外部库(Three.js, Chart.js, math.js - 如果需要)。
运行组件
- 确保
index.html
,styles.css
,script.js
文件在同一目录下。 - 在浏览器中打开
index.html
文件。 - 如果本地运行,可能需要一个简单的本地Web服务器来避免某些浏览器关于本地文件访问的限制。
基本操作流程
- 选择仿真类型: 在顶部工具栏的下拉菜单中选择要进行的仿真类型(结构、流体、电磁场或耦合)。选择后,左侧参数面板会自动更新显示相关参数。
- 设置参数: 在左侧面板调整所选仿真类型对应的物理参数和仿真设置(如材料、风速、网格密度、仿真时间等)。
- 运行仿真: 点击顶部工具栏的"运行仿真"按钮 ()。
- 观察过程: 状态栏会显示仿真状态和进度。右侧的仿真报告区会输出日志信息。
- 查看结果: 仿真完成后,状态栏会显示"完成"。右侧结果面板会自动更新。
- 点击结果面板上方的选项卡(应力分析、流场分析、电磁场)切换查看不同物理场的结果。
- 查看关键指标数值。
- 查看对应的结果图表。
- 中间的3D视图会根据当前选择的结果选项卡更新颜色映射,图例会显示颜色对应的数值范围。
- 交互操作:
- 使用鼠标在3D视图中拖动进行旋转,滚轮缩放,右键(或Ctrl/Cmd+拖动)平移。
- 点击模型树中的部件名称(如"叶片1"、“塔架”)可以高亮显示对应的3D模型部分。
- 勾选/取消勾选"线框模式"复选框可以切换模型的显示方式。
- 停止/重置:
- 在仿真运行时,可以点击"停止"按钮 () 中断计算。
- 点击"重置"按钮 () 可以将所有参数恢复到默认值,并清除仿真结果和状态。
参数说明
(根据 index.html
中定义的参数进行详细说明)
结构分析 (structural
)
- 材料类型: 定义模型部件的主要材料。
- 密度 (kg/m³): 材料的质量密度。
- 杨氏模量 (GPa): 材料抵抗弹性变形的能力。
- 泊松比: 材料横向应变与纵向应变之比。
- 风速 (m/s): 施加在结构上的等效风载荷速度。
- 湍流强度 (%): 风载荷的不规则性程度。
- 旋转速度 (rpm): 叶片或整个结构的旋转速度,用于计算离心力。
流体分析 (fluid
)
- 流体类型: 模拟的流体介质(如空气、水)。
- 密度 (kg/m³): 流体的质量密度。
- 黏度 (Pa·s): 流体的内摩擦力。
- 入口速度 (m/s): 流体进入仿真区域的速度。
- 湍流模型: 用于模拟湍流效应的数学模型(如k-ε, k-ω)。
电磁场分析 (electromagnetic
)
- 电导率 (S/m): 材料传导电流的能力。
- 磁导率 (H/m): 材料对磁场的响应程度。
- 频率 (Hz): 电磁场的频率。
- 场强 (A/m): 施加的外部磁场强度。
仿真设置 (settings
- 通用)
- 网格密度: 控制计算网格的精细程度(粗糙、中等、精细等),影响精度和计算时间。
- 时间步长 (s): 对于瞬态分析,每个计算步的时间长度。
- 总时间 (s): 瞬态分析的总模拟时长。
技术实现
- 用户界面: HTML, CSS
- 交互逻辑: JavaScript (ES6+)
- 3D渲染: Three.js (r128 或更高版本)
- 3D交互: Three.js OrbitControls
- 图表展示: Chart.js
- 数学计算: (可选) Math.js (当前版本未使用,但已在HTML中引入)
- 仿真计算: 当前版本使用 模拟数据 生成结果,未集成真实的物理求解器。
待办事项与未来改进
- 集成真实的物理求解器(例如通过WebAssembly调用C++/Fortran库,或连接后端计算服务)。
- 实现模型树与参数面板的联动(选择部件显示对应参数)。
- 实现更精细的3D模型高亮效果。
- 完成"显示网格"和"显示矢量"功能。
- 实现"新建"、“载入”、"保存"仿真配置和结果的功能。
- 实现耦合分析的参数设置和结果展示逻辑。
- 优化3D模型和渲染性能。
- 提供更详细的错误处理和用户反馈。
- 增加导出仿真报告和图表数据的功能。
常见问题解答
为什么3D模型是简化的?
为了快速展示组件的核心功能和交互流程,当前版本使用了简化的几何体。在集成真实求解器后,可以替换为更详细的模型。
仿真结果准确吗?
不准确。当前版本仅使用随机生成的模拟数据来填充结果面板和图表,用于演示UI效果。结果不代表真实的物理计算。
如何集成真实的求解器?
这通常需要更复杂的技术:
- 后端计算: 将参数发送到服务器,由服务器上的专业仿真软件(如ANSYS, COMSOL, OpenFOAM)进行计算,然后将结果返回给前端展示。
- WebAssembly: 将C/C++或Fortran编写的求解器代码编译成WebAssembly,在浏览器中直接运行。这对于计算量不是特别巨大的问题是可行的。
点击模型树部件没有反应?
当前版本实现了点击模型树条目时高亮对应3D部件的功能(基于名称匹配),但尚未实现参数面板的联动更新。
更新日志
v1.0.0 (2024-04-09)
- 初始版本,基于提供的 HTML/CSS 结构完成 JavaScript 实现。
- 实现基本UI布局和交互逻辑。
- 支持结构、流体、电磁仿真类型切换及对应参数面板显示。
- 集成 Three.js 显示简化风力发电机模型及基本交互。
- 集成 Chart.js 显示结果图表。
- 模拟仿真运行流程、进度显示和报告生成。
- 使用模拟数据填充结果面板和图表。
- 实现模型树选择高亮3D部件功能。
- 实现线框模式切换。
效果展示
源码
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多物理场仿真组件 - 风力发电机叶片应力分析</title>
<link rel="stylesheet" href="styles.css">
<!-- Three.js库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- OrbitControls扩展 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
<!-- 数学计算库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/9.4.4/math.min.js"></script>
<!-- 图表库 -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
</head>
<body>
<div class="simulation-container">
<!-- 顶部工具栏 -->
<div class="toolbar">
<div class="toolbar-section">
<button id="new-simulation-btn" class="btn primary">新建仿真</button>
<button id="load-simulation-btn" class="btn">载入模型</button>
<button id="save-simulation-btn" class="btn">保存模型</button>
</div>
<div class="toolbar-section">
<button id="run-simulation-btn" class="btn success">运行仿真</button>
<button id="stop-simulation-btn" class="btn danger">停止</button>
<button id="reset-simulation-btn" class="btn">重置</button>
</div>
<div class="toolbar-section simulation-type">
<span>仿真类型:</span>
<select id="simulation-type">
<option value="structural" selected>结构分析</option>
<option value="fluid">流体分析</option>
<option value="electromagnetic">电磁场分析</option>
<option value="coupled">耦合分析</option>
</select>
</div>
</div>
<!-- 主内容区域 -->
<div class="main-content">
<div class="left-panel">
<div class="panel-header">
<h3>模型树</h3>
</div>
<div class="panel-content">
<div class="model-tree">
<ul>
<li class="expanded">
<div class="tree-item active">风力发电机</div>
<ul>
<li>
<div class="tree-item">叶片系统</div>
<ul>
<li><div class="tree-item">叶片1</div></li>
<li><div class="tree-item">叶片2</div></li>
<li><div class="tree-item">叶片3</div></li>
</ul>
</li>
<li><div class="tree-item">轮毂</div></li>
<li><div class="tree-item">传动系统</div></li>
<li><div class="tree-item">发电机</div></li>
<li><div class="tree-item">塔架</div></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="panel-header">
<h3>物理参数</h3>
</div>
<div class="panel-content">
<div class="physics-params">
<!-- 结构分析参数 -->
<div class="param-group" id="structural-params">
<h4>材料属性</h4>
<div class="param-row">
<label for="material-type">材料类型:</label>
<select id="material-type">
<option value="composite">复合材料</option>
<option value="aluminum">铝合金</option>
<option value="steel">钢</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="param-row">
<label for="density">密度 (kg/m³):</label>
<input type="number" id="density" value="1850" min="0" step="10">
</div>
<div class="param-row">
<label for="youngs-modulus">杨氏模量 (GPa):</label>
<input type="number" id="youngs-modulus" value="70" min="0" step="1">
</div>
<div class="param-row">
<label for="poisson-ratio">泊松比:</label>
<input type="number" id="poisson-ratio" value="0.33" min="0" max="0.5" step="0.01">
</div>
<h4>载荷条件</h4>
<div class="param-row">
<label for="wind-speed">风速 (m/s):</label>
<input type="number" id="wind-speed" value="12" min="0" max="50" step="0.5">
</div>
<div class="param-row">
<label for="turbulence-intensity">湍流强度 (%):</label>
<input type="number" id="turbulence-intensity" value="15" min="0" max="100" step="1">
</div>
<div class="param-row">
<label for="rotation-speed">旋转速度 (rpm):</label>
<input type="number" id="rotation-speed" value="15" min="0" max="30" step="0.1">
</div>
</div>
<!-- 流体分析参数 -->
<div class="param-group" id="fluid-params" style="display: none;">
<h4>流体属性</h4>
<div class="param-row">
<label for="fluid-type">流体类型:</label>
<select id="fluid-type">
<option value="air">空气</option>
<option value="water">水</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="param-row">
<label for="fluid-density">密度 (kg/m³):</label>
<input type="number" id="fluid-density" value="1.225" step="0.001">
</div>
<div class="param-row">
<label for="fluid-viscosity">黏度 (Pa·s):</label>
<input type="number" id="fluid-viscosity" value="1.8e-5" step="1e-6">
</div>
<h4>流动条件</h4>
<div class="param-row">
<label for="inlet-velocity">入口速度 (m/s):</label>
<input type="number" id="inlet-velocity" value="12" min="0" step="0.5">
</div>
<div class="param-row">
<label for="turbulence-model">湍流模型:</label>
<select id="turbulence-model">
<option value="ke">k-ε</option>
<option value="kw">k-ω</option>
<option value="laminar">层流</option>
</select>
</div>
</div>
<!-- 电磁场分析参数 -->
<div class="param-group" id="em-params" style="display: none;">
<h4>电磁属性</h4>
<div class="param-row">
<label for="conductivity">电导率 (S/m):</label>
<input type="number" id="conductivity" value="0" min="0" step="100">
</div>
<div class="param-row">
<label for="permeability">磁导率 (H/m):</label>
<input type="number" id="permeability" value="1.257e-6" step="1e-7">
</div>
<h4>电磁场条件</h4>
<div class="param-row">
<label for="frequency">频率 (Hz):</label>
<input type="number" id="frequency" value="50" min="0" step="1">
</div>
<div class="param-row">
<label for="em-field-strength">场强 (A/m):</label>
<input type="number" id="em-field-strength" value="1000" min="0" step="100">
</div>
</div>
<!-- 仿真设置 -->
<h4>仿真设置</h4>
<div class="param-row">
<label for="mesh-density">网格密度:</label>
<select id="mesh-density">
<option value="coarse">粗糙</option>
<option value="medium" selected>中等</option>
<option value="fine">精细</option>
<option value="very-fine">非常精细</option>
</select>
</div>
<div class="param-row">
<label for="time-step">时间步长 (s):</label>
<input type="number" id="time-step" value="0.01" min="0.001" max="1" step="0.001">
</div>
<div class="param-row">
<label for="total-time">总时间 (s):</label>
<input type="number" id="total-time" value="5" min="0.1" step="0.1">
</div>
</div>
</div>
</div>
<div class="center-panel">
<div class="visualization-container">
<canvas id="main-canvas"></canvas>
<div class="visualization-controls">
<button id="zoom-in-btn" class="control-btn">+</button>
<button id="zoom-out-btn" class="control-btn">-</button>
<button id="rotate-btn" class="control-btn">
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M7.11 8.53L5.7 7.11C4.8 8.27 4.24 9.61 4.07 11h2.02c.14-.87.49-1.72 1.02-2.47zM6.09 13H4.07c.17 1.39.72 2.73 1.62 3.89l1.41-1.42c-.52-.75-.87-1.59-1.01-2.47zm1.01 5.32c1.16.9 2.51 1.44 3.9 1.61V17.9c-.87-.15-1.71-.49-2.46-1.03L7.1 18.32zM13 4.07V1L8.45 5.55 13 10V6.09c2.84.48 5 2.94 5 5.91s-2.16 5.43-5 5.91v2.02c3.95-.49 7-3.85 7-7.93s-3.05-7.44-7-7.93z"/>
</svg>
</button>
<button id="pan-btn" class="control-btn">
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/>
</svg>
</button>
</div>
<div class="visualization-legend">
<div class="legend-title">应力 (MPa)</div>
<div class="legend-gradient">
<div class="gradient"></div>
<div class="legend-labels">
<span>0</span>
<span>50</span>
<span>100</span>
<span>150</span>
<span>200</span>
</div>
</div>
</div>
<div class="visualization-options">
<div class="view-option">
<input type="checkbox" id="show-mesh" class="toggle-switch">
<label for="show-mesh">显示网格</label>
</div>
<div class="view-option">
<input type="checkbox" id="show-wireframe" class="toggle-switch">
<label for="show-wireframe">线框模式</label>
</div>
<div class="view-option">
<input type="checkbox" id="show-vectors" class="toggle-switch" checked>
<label for="show-vectors">显示矢量</label>
</div>
</div>
</div>
</div>
<div class="right-panel">
<div class="panel-header">
<h3>仿真结果</h3>
</div>
<div class="panel-content">
<div class="results-tabs">
<div class="tab-header">
<div class="tab-btn active" data-tab="stress">应力分析</div>
<div class="tab-btn" data-tab="flow">流场分析</div>
<div class="tab-btn" data-tab="em-field">电磁场</div>
</div>
<div class="tab-content">
<div class="tab-pane active" id="stress-tab">
<div class="result-section">
<h4>叶片应力分析</h4>
<div class="result-stats">
<div class="stat-item">
<div class="stat-label">最大应力:</div>
<div class="stat-value" id="max-stress">0 MPa</div>
</div>
<div class="stat-item">
<div class="stat-label">最小应力:</div>
<div class="stat-value" id="min-stress">0 MPa</div>
</div>
<div class="stat-item">
<div class="stat-label">平均应力:</div>
<div class="stat-value" id="avg-stress">0 MPa</div>
</div>
<div class="stat-item">
<div class="stat-label">最大位移:</div>
<div class="stat-value" id="max-displacement">0 mm</div>
</div>
</div>
<div class="chart-container">
<canvas id="stress-chart"></canvas>
</div>
<div class="safety-factor">
<div class="safety-label">安全系数:</div>
<div class="safety-value" id="safety-factor">N/A</div>
<div class="safety-status" id="safety-status">未计算</div>
</div>
</div>
</div>
<div class="tab-pane" id="flow-tab">
<div class="result-section">
<h4>流体动力学分析</h4>
<div class="result-stats">
<div class="stat-item">
<div class="stat-label">最大速度:</div>
<div class="stat-value" id="max-velocity">0 m/s</div>
</div>
<div class="stat-item">
<div class="stat-label">最大压力:</div>
<div class="stat-value" id="max-pressure">0 Pa</div>
</div>
<div class="stat-item">
<div class="stat-label">升力系数:</div>
<div class="stat-value" id="lift-coefficient">0</div>
</div>
<div class="stat-item">
<div class="stat-label">阻力系数:</div>
<div class="stat-value" id="drag-coefficient">0</div>
</div>
</div>
<div class="chart-container">
<canvas id="velocity-chart"></canvas>
</div>
</div>
</div>
<div class="tab-pane" id="em-field-tab">
<div class="result-section">
<h4>电磁场分析</h4>
<div class="result-stats">
<div class="stat-item">
<div class="stat-label">最大磁场强度:</div>
<div class="stat-value" id="max-magnetic-field">0 T</div>
</div>
<div class="stat-item">
<div class="stat-label">最大电场强度:</div>
<div class="stat-value" id="max-electric-field">0 V/m</div>
</div>
<div class="stat-item">
<div class="stat-label">感应电流密度:</div>
<div class="stat-value" id="current-density">0 A/m²</div>
</div>
</div>
<div class="chart-container">
<canvas id="em-field-chart"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="report-section">
<div class="panel-header">
<h3>仿真报告</h3>
</div>
<div class="report-content">
<div class="report-item">
<span class="report-time">08:30:45</span>
<span class="report-message info">初始化仿真环境</span>
</div>
<div class="report-item">
<span class="report-time">08:31:12</span>
<span class="report-message">生成风力发电机模型网格</span>
</div>
<div class="report-item">
<span class="report-time">08:31:45</span>
<span class="report-message">网格生成完成,共125,342个单元</span>
</div>
<div class="report-item">
<span class="report-time">08:32:10</span>
<span class="report-message success">应力分析计算完成</span>
</div>
<div class="report-item">
<span class="report-time">08:33:05</span>
<span class="report-message warning">检测到叶片1尖端应力超过阈值</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="status-bar">
<div class="status-info">
<span>仿真状态: </span>
<span id="simulation-status" class="status-ready">就绪</span>
</div>
<div class="status-progress">
<div class="progress-bar" id="simulation-progress" style="width: 0%;"></div>
</div>
<div class="status-time">
<span>处理时间: </span>
<span id="processing-time">0.00 s</span>
</div>
<div class="status-memory">
<span>内存使用: </span>
<span id="memory-usage">0 MB</span>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
styles.css
/* 全局变量 */
:root {
--primary-color: #0070f3;
--secondary-color: #f5f5f7;
--text-color: #333;
--light-text: #86868b;
--border-color: #d2d2d7;
--danger-color: #ff3b30;
--success-color: #34c759;
--warning-color: #ffcc00;
--info-color: #5ac8fa;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
--panel-width: 280px;
--toolbar-height: 50px;
--status-bar-height: 30px;
}
/* 基础样式 */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.5;
color: var(--text-color);
background-color: white;
overflow: hidden;
height: 100vh;
}
h3 {
font-size: 16px;
font-weight: 600;
margin: 0;
}
h4 {
font-size: 14px;
font-weight: 600;
margin: 0 0 10px 0;
color: var(--light-text);
}
/* 容器样式 */
.simulation-container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 工具栏 */
.toolbar {
height: var(--toolbar-height);
background-color: var(--secondary-color);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
padding: 0 15px;
justify-content: space-between;
}
.toolbar-section {
display: flex;
gap: 8px;
align-items: center;
}
.simulation-type span {
font-size: 13px;
margin-right: 5px;
}
.simulation-type select {
width: 120px;
}
/* 按钮样式 */
.btn {
border: none;
border-radius: 6px;
padding: 8px 12px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
background-color: #e8e8ed;
color: var(--text-color);
transition: all 0.2s ease;
}
.btn:hover {
background-color: #d8d8d8;
}
.btn.primary {
background-color: var(--primary-color);
color: white;
}
.btn.primary:hover {
background-color: #005acf;
}
.btn.success {
background-color: var(--success-color);
color: white;
}
.btn.success:hover {
background-color: #2db64f;
}
.btn.danger {
background-color: var(--danger-color);
color: white;
}
.btn.danger:hover {
background-color: #e63028;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* 主内容区域 */
.main-content {
display: flex;
flex-grow: 1;
overflow: hidden;
}
/* 左侧面板 */
.left-panel {
width: var(--panel-width);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #fff;
}
/* 中间面板 */
.center-panel {
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
/* 右侧面板 */
.right-panel {
width: var(--panel-width);
border-left: 1px solid var(--border-color);
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #fff;
}
/* 面板样式 */
.panel-header {
padding: 15px;
border-bottom: 1px solid var(--border-color);
background-color: var(--secondary-color);
}
.panel-content {
padding: 15px;
overflow-y: auto;
flex-grow: 1;
}
/* 模型树 */
.model-tree {
margin-bottom: 20px;
}
.model-tree ul {
list-style: none;
margin: 0;
padding: 0;
}
.model-tree li {
margin: 0;
padding: 0;
}
.model-tree ul ul {
margin-left: 20px;
}
.tree-item {
padding: 6px 10px;
border-radius: 4px;
cursor: pointer;
margin: 2px 0;
font-size: 13px;
}
.tree-item:hover {
background-color: #f5f5f7;
}
.tree-item.active {
background-color: var(--primary-color);
color: white;
}
li.expanded > .tree-item::before {
content: "▼";
font-size: 8px;
margin-right: 5px;
}
li:not(.expanded) > .tree-item::before {
content: "▶";
font-size: 8px;
margin-right: 5px;
}
/* 参数输入样式 */
.param-group {
margin-bottom: 20px;
}
.param-row {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.param-row label {
flex: 0 0 120px;
font-size: 13px;
}
.param-row input,
.param-row select {
flex: 1;
padding: 6px 8px;
border-radius: 6px;
border: 1px solid var(--border-color);
font-size: 13px;
background: white;
}
.param-row input:focus,
.param-row select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.2);
}
/* 可视化容器 */
.visualization-container {
position: relative;
flex-grow: 1;
background-color: #f8f8f8;
overflow: hidden;
}
#main-canvas {
width: 100%;
height: 100%;
display: block;
}
.visualization-controls {
position: absolute;
bottom: 15px;
right: 15px;
display: flex;
gap: 5px;
}
.control-btn {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: white;
border: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: var(--shadow);
}
.control-btn:hover {
background-color: #f5f5f5;
}
.control-btn svg {
fill: var(--text-color);
}
/* 可视化图例 */
.visualization-legend {
position: absolute;
bottom: 15px;
left: 15px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 6px;
padding: 10px;
box-shadow: var(--shadow);
}
.legend-title {
font-size: 12px;
font-weight: 600;
margin-bottom: 5px;
}
.legend-gradient {
width: 200px;
}
.gradient {
height: 10px;
border-radius: 5px;
background: linear-gradient(to right, #0000ff, #00ffff, #00ff00, #ffff00, #ff0000);
}
.legend-labels {
display: flex;
justify-content: space-between;
margin-top: 5px;
font-size: 10px;
}
/* 可视化选项 */
.visualization-options {
position: absolute;
top: 15px;
right: 15px;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 6px;
padding: 10px;
box-shadow: var(--shadow);
}
.view-option {
display: flex;
align-items: center;
margin-bottom: 5px;
font-size: 12px;
}
.view-option:last-child {
margin-bottom: 0;
}
.toggle-switch {
margin-right: 5px;
}
/* 结果选项卡 */
.results-tabs {
margin-bottom: 20px;
}
.tab-header {
display: flex;
border-bottom: 1px solid var(--border-color);
margin-bottom: 15px;
}
.tab-btn {
padding: 8px 15px;
font-size: 13px;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.tab-btn.active {
border-bottom: 2px solid var(--primary-color);
color: var(--primary-color);
font-weight: 500;
}
.tab-content {
position: relative;
}
.tab-pane {
display: none;
}
.tab-pane.active {
display: block;
}
/* 结果统计 */
.result-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-bottom: 15px;
}
.stat-item {
background-color: var(--secondary-color);
border-radius: 6px;
padding: 10px;
}
.stat-label {
font-size: 12px;
color: var(--light-text);
}
.stat-value {
font-size: 16px;
font-weight: 600;
}
/* 图表容器 */
.chart-container {
height: 180px;
margin-bottom: 15px;
}
/* 安全系数 */
.safety-factor {
display: flex;
align-items: center;
background-color: var(--secondary-color);
border-radius: 6px;
padding: 10px;
}
.safety-label {
font-size: 12px;
color: var(--light-text);
margin-right: 10px;
}
.safety-value {
font-size: 18px;
font-weight: 700;
margin-right: 10px;
}
.safety-status {
font-size: 12px;
padding: 3px 8px;
border-radius: 4px;
background-color: #eee;
}
.safety-status.safe {
background-color: var(--success-color);
color: white;
}
.safety-status.warning {
background-color: var(--warning-color);
color: #333;
}
.safety-status.danger {
background-color: var(--danger-color);
color: white;
}
/* 仿真报告 */
.report-section {
margin-top: 20px;
}
.report-content {
max-height: 180px;
overflow-y: auto;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 12px;
}
.report-item {
padding: 8px 10px;
border-bottom: 1px solid var(--border-color);
display: flex;
}
.report-item:last-child {
border-bottom: none;
}
.report-time {
flex: 0 0 70px;
color: var(--light-text);
}
.report-message {
flex: 1;
}
.report-message.success {
color: var(--success-color);
}
.report-message.warning {
color: var(--warning-color);
}
.report-message.error {
color: var(--danger-color);
}
.report-message.info {
color: var(--info-color);
}
/* 状态栏 */
.status-bar {
height: var(--status-bar-height);
background-color: var(--secondary-color);
border-top: 1px solid var(--border-color);
display: flex;
align-items: center;
padding: 0 15px;
font-size: 12px;
color: var(--light-text);
}
.status-info {
margin-right: 15px;
}
.status-ready {
color: var(--light-text);
}
.status-running {
color: var(--primary-color);
}
.status-success {
color: var(--success-color);
}
.status-error {
color: var(--danger-color);
}
.status-progress {
flex: 1;
height: 4px;
background-color: #e0e0e0;
border-radius: 2px;
margin: 0 15px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: var(--primary-color);
border-radius: 2px;
transition: width 0.3s ease;
}
.status-time, .status-memory {
margin-left: 15px;
}
/* 响应式调整 */
@media (max-width: 1200px) {
.main-content {
flex-wrap: wrap;
}
.left-panel, .right-panel {
width: 50%;
border: none;
border-bottom: 1px solid var(--border-color);
}
.center-panel {
width: 100%;
order: -1;
flex-basis: 60%;
}
}
@media (max-width: 768px) {
.left-panel, .right-panel {
width: 100%;
}
.center-panel {
flex-basis: 50%;
}
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d1d1d6;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a1a1a6;
}
script.js
// 多物理场仿真组件 - JavaScript 实现
// V1.0.0
document.addEventListener('DOMContentLoaded', init);
// --- 全局变量 ---
let scene, camera, renderer, controls, modelGroup;
let simulationInterval = null; // 存储仿真定时器ID
let chartInstances = {}; // 存储图表实例
// --- DOM元素引用 ---
const elements = {
// 工具栏
newSimulationBtn: document.getElementById('new-simulation-btn'),
loadSimulationBtn: document.getElementById('load-simulation-btn'),
saveSimulationBtn: document.getElementById('save-simulation-btn'),
runSimulationBtn: document.getElementById('run-simulation-btn'),
stopSimulationBtn: document.getElementById('stop-simulation-btn'),
resetSimulationBtn: document.getElementById('reset-simulation-btn'),
simulationTypeSelect: document.getElementById('simulation-type'),
// 左侧面板 - 模型树 (示例,实际交互可能更复杂)
modelTreeItems: document.querySelectorAll('.model-tree .tree-item'),
// 左侧面板 - 参数
structuralParams: document.getElementById('structural-params'),
fluidParams: document.getElementById('fluid-params'),
emParams: document.getElementById('em-params'),
materialType: document.getElementById('material-type'),
density: document.getElementById('density'),
youngsModulus: document.getElementById('youngs-modulus'),
poissonRatio: document.getElementById('poisson-ratio'),
windSpeed: document.getElementById('wind-speed'),
turbulenceIntensity: document.getElementById('turbulence-intensity'),
rotationSpeed: document.getElementById('rotation-speed'),
// ... (添加其他参数元素的引用)
meshDensity: document.getElementById('mesh-density'),
timeStep: document.getElementById('time-step'),
totalTime: document.getElementById('total-time'),
// 流体参数
fluidType: document.getElementById('fluid-type'),
fluidDensity: document.getElementById('fluid-density'),
fluidViscosity: document.getElementById('fluid-viscosity'),
inletVelocity: document.getElementById('inlet-velocity'),
turbulenceModel: document.getElementById('turbulence-model'),
// 电磁参数
conductivity: document.getElementById('conductivity'),
permeability: document.getElementById('permeability'),
frequency: document.getElementById('frequency'),
emFieldStrength: document.getElementById('em-field-strength'),
// 中间面板 - 可视化
mainCanvas: document.getElementById('main-canvas'),
zoomInBtn: document.getElementById('zoom-in-btn'),
zoomOutBtn: document.getElementById('zoom-out-btn'),
rotateBtn: document.getElementById('rotate-btn'), // 注意:OrbitControls默认处理旋转
panBtn: document.getElementById('pan-btn'), // 注意:OrbitControls默认处理平移
showMeshCheckbox: document.getElementById('show-mesh'),
showWireframeCheckbox: document.getElementById('show-wireframe'),
showVectorsCheckbox: document.getElementById('show-vectors'),
legendTitle: document.querySelector('.visualization-legend .legend-title'),
legendGradient: document.querySelector('.visualization-legend .gradient'),
legendLabels: document.querySelector('.visualization-legend .legend-labels'),
// 右侧面板 - 结果
resultTabBtns: document.querySelectorAll('.results-tabs .tab-btn'),
stressTabPane: document.getElementById('stress-tab'),
flowTabPane: document.getElementById('flow-tab'),
emFieldTabPane: document.getElementById('em-field-tab'),
maxStress: document.getElementById('max-stress'),
minStress: document.getElementById('min-stress'),
avgStress: document.getElementById('avg-stress'),
maxDisplacement: document.getElementById('max-displacement'),
safetyFactor: document.getElementById('safety-factor'),
safetyStatus: document.getElementById('safety-status'),
// 流体结果
maxVelocity: document.getElementById('max-velocity'),
maxPressure: document.getElementById('max-pressure'),
liftCoefficient: document.getElementById('lift-coefficient'),
dragCoefficient: document.getElementById('drag-coefficient'),
// 电磁结果
maxMagneticField: document.getElementById('max-magnetic-field'),
maxElectricField: document.getElementById('max-electric-field'),
currentDensity: document.getElementById('current-density'),
// 图表画布
stressChartCanvas: document.getElementById('stress-chart'),
velocityChartCanvas: document.getElementById('velocity-chart'),
emFieldChartCanvas: document.getElementById('em-field-chart'),
// 报告
reportContent: document.querySelector('.report-content'),
// 状态栏
simulationStatus: document.getElementById('simulation-status'),
simulationProgress: document.getElementById('simulation-progress'),
processingTime: document.getElementById('processing-time'),
memoryUsage: document.getElementById('memory-usage'),
};
// --- 应用程序状态 ---
const appState = {
isSimulationRunning: false,
currentSimulationType: 'structural', // 'structural', 'fluid', 'electromagnetic', 'coupled'
simulationStartTime: 0,
simulationProgress: 0,
activeModelItem: null,
activeResultTab: 'stress',
memoryUsage: 50 + Math.random() * 10, // 初始内存使用模拟
parameters: { // 用于存储参数值,便于重置和保存/加载
structural: {},
fluid: {},
em: {},
settings: {}
},
defaultParameters: { // 存储默认值
// 在 init 或 setupEventListeners 中填充
}
};
// --- 日志记录 ---
const logger = {
info: (message) => {
console.log(`[INFO] ${message}`);
addReportItem('info', message);
},
warn: (message) => {
console.warn(`[WARN] ${message}`);
addReportItem('warning', message);
},
error: (message) => {
console.error(`[ERROR] ${message}`);
addReportItem('error', message);
},
success: (message) => {
console.log(`[SUCCESS] ${message}`);
addReportItem('success', message);
}
};
// --- 初始化 ---
function init() {
logger.info("初始化多物理场仿真组件...");
if (!elements.mainCanvas) {
logger.error("初始化失败: 未找到主画布元素 'main-canvas'。");
return;
}
if (typeof THREE === 'undefined') {
logger.error("初始化失败: Three.js 库未加载。");
return;
}
if (typeof Chart === 'undefined') {
logger.warn("Chart.js 库未加载,图表功能将不可用。");
}
setupEventListeners();
setupThreeJS();
setDefaultParameters(); // 必须在setupEventListeners之后,因为它会读取初始值
handleSimulationTypeChange(); // 根据默认选择显示正确的参数面板
handleResultTabChange(appState.activeResultTab); // 显示默认结果选项卡
updateStatus('就绪', 'ready');
updateMemoryUsage();
animate(); // 启动Three.js渲染循环
initializeCharts(); // 初始化图表
logger.info("组件初始化完成。");
}
// --- 事件监听器 ---
function setupEventListeners() {
// 工具栏按钮
elements.runSimulationBtn.addEventListener('click', startSimulation);
elements.stopSimulationBtn.addEventListener('click', stopSimulation);
elements.resetSimulationBtn.addEventListener('click', resetSimulation);
elements.newSimulationBtn.addEventListener('click', () => logger.info('点击了“新建仿真”按钮(功能待实现)'));
elements.loadSimulationBtn.addEventListener('click', () => logger.info('点击了“载入模型”按钮(功能待实现)'));
elements.saveSimulationBtn.addEventListener('click', () => logger.info('点击了“保存模型”按钮(功能待实现)'));
// 仿真类型切换
elements.simulationTypeSelect.addEventListener('change', handleSimulationTypeChange);
// 模型树点击
elements.modelTreeItems.forEach(item => {
item.addEventListener('click', () => handleModelTreeClick(item));
});
// 结果选项卡切换
elements.resultTabBtns.forEach(btn => {
btn.addEventListener('click', () => handleResultTabChange(btn.dataset.tab));
});
// 可视化控制
elements.zoomInBtn.addEventListener('click', () => zoomCamera(0.8));
elements.zoomOutBtn.addEventListener('click', () => zoomCamera(1.2));
// pan/rotate 由 OrbitControls 处理
// 可视化选项
elements.showMeshCheckbox.addEventListener('change', toggleMeshVisibility);
elements.showWireframeCheckbox.addEventListener('change', toggleWireframe);
// elements.showVectorsCheckbox.addEventListener('change', toggleVectors); // 待实现
// 参数输入 (示例 - 需要为所有参数添加监听)
elements.windSpeed.addEventListener('change', () => updateParameter('structural', 'windSpeed', elements.windSpeed.value));
elements.density.addEventListener('change', () => updateParameter('structural', 'density', elements.density.value));
// ... 为所有 structural, fluid, em, settings 参数添加监听 ...
elements.materialType.addEventListener('change', () => updateParameter('structural', 'materialType', elements.materialType.value));
elements.youngsModulus.addEventListener('change', () => updateParameter('structural', 'youngsModulus', elements.youngsModulus.value));
elements.poissonRatio.addEventListener('change', () => updateParameter('structural', 'poissonRatio', elements.poissonRatio.value));
elements.turbulenceIntensity.addEventListener('change', () => updateParameter('structural', 'turbulenceIntensity', elements.turbulenceIntensity.value));
elements.rotationSpeed.addEventListener('change', () => updateParameter('structural', 'rotationSpeed', elements.rotationSpeed.value));
elements.fluidType.addEventListener('change', () => updateParameter('fluid', 'fluidType', elements.fluidType.value));
elements.fluidDensity.addEventListener('change', () => updateParameter('fluid', 'fluidDensity', elements.fluidDensity.value));
elements.fluidViscosity.addEventListener('change', () => updateParameter('fluid', 'fluidViscosity', elements.fluidViscosity.value));
elements.inletVelocity.addEventListener('change', () => updateParameter('fluid', 'inletVelocity', elements.inletVelocity.value));
elements.turbulenceModel.addEventListener('change', () => updateParameter('fluid', 'turbulenceModel', elements.turbulenceModel.value));
elements.conductivity.addEventListener('change', () => updateParameter('em', 'conductivity', elements.conductivity.value));
elements.permeability.addEventListener('change', () => updateParameter('em', 'permeability', elements.permeability.value));
elements.frequency.addEventListener('change', () => updateParameter('em', 'frequency', elements.frequency.value));
elements.emFieldStrength.addEventListener('change', () => updateParameter('em', 'emFieldStrength', elements.emFieldStrength.value));
elements.meshDensity.addEventListener('change', () => updateParameter('settings', 'meshDensity', elements.meshDensity.value));
elements.timeStep.addEventListener('change', () => updateParameter('settings', 'timeStep', elements.timeStep.value));
elements.totalTime.addEventListener('change', () => updateParameter('settings', 'totalTime', elements.totalTime.value));
// 窗口大小调整
window.addEventListener('resize', onWindowResize);
}
// --- Three.js 相关 ---
function setupThreeJS() {
const container = elements.mainCanvas.parentElement;
const width = container.clientWidth;
const height = container.clientHeight;
// 场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf8f8f8);
// 相机
camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 2000);
camera.position.set(15, 10, 25); // 调整相机初始位置
// 渲染器
renderer = new THREE.WebGLRenderer({ canvas: elements.mainCanvas, antialias: true });
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true; // 启用阴影
// 光照
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 15);
directionalLight.castShadow = true;
// 配置阴影贴图质量
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -15;
directionalLight.shadow.camera.right = 15;
directionalLight.shadow.camera.top = 15;
directionalLight.shadow.camera.bottom = -15;
scene.add(directionalLight);
// 控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.1;
controls.screenSpacePanning = false; // 限制平移在一个平面上可能更直观
controls.target.set(0, 2, 0); // 设置控制器的目标点
controls.update();
// 网格地面
const gridHelper = new THREE.GridHelper(50, 50, 0xcccccc, 0xe0e0e0);
scene.add(gridHelper);
// 创建风力发电机模型组
modelGroup = new THREE.Group();
scene.add(modelGroup);
// 创建风力发电机模型 (简化版)
createWindTurbineModel();
}
function createWindTurbineModel() {
// 清除旧模型
while (modelGroup.children.length > 0) {
modelGroup.remove(modelGroup.children[0]);
}
const towerHeight = 12;
const towerRadius = 0.5;
const bladeLength = 6;
const hubRadius = 0.8;
// 材料
const metalMaterial = new THREE.MeshStandardMaterial({ color: 0xb0b0b0, metalness: 0.8, roughness: 0.5 });
const bladeMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.8 });
// 塔架
const towerGeometry = new THREE.CylinderGeometry(towerRadius * 0.8, towerRadius, towerHeight, 16);
const tower = new THREE.Mesh(towerGeometry, metalMaterial);
tower.position.y = towerHeight / 2;
tower.castShadow = true;
tower.receiveShadow = true;
modelGroup.add(tower);
// 机舱 (简化为盒子)
const nacelleGeometry = new THREE.BoxGeometry(2, 1.5, 3);
const nacelle = new THREE.Mesh(nacelleGeometry, metalMaterial);
nacelle.position.y = towerHeight;
nacelle.position.z = -0.5; // 向前偏移一点
nacelle.castShadow = true;
nacelle.receiveShadow = true;
modelGroup.add(nacelle);
// 轮毂
const hubGeometry = new THREE.CylinderGeometry(hubRadius * 0.8, hubRadius, 0.8, 16);
const hub = new THREE.Mesh(hubGeometry, metalMaterial);
hub.position.y = towerHeight;
hub.position.z = 1; // 机舱前方
hub.rotation.x = Math.PI / 2; // 旋转使其朝前
hub.castShadow = true;
hub.receiveShadow = true;
modelGroup.add(hub);
// 叶片 (简化为长方体)
for (let i = 0; i < 3; i++) {
const bladeGeometry = new THREE.BoxGeometry(0.3, bladeLength, 0.1); // 宽度, 长度, 厚度
const blade = new THREE.Mesh(bladeGeometry, bladeMaterial);
// 设置叶片的中心点在根部
blade.geometry.translate(0, bladeLength / 2, 0);
// 定位和旋转叶片
blade.position.y = towerHeight;
blade.position.z = 1;
const angle = (i * 2 * Math.PI) / 3;
blade.rotation.z = angle;
// 让叶片稍微倾斜一点(模拟攻角)
blade.rotation.x = -Math.PI / 18; // 10度
blade.castShadow = true;
blade.receiveShadow = true;
modelGroup.add(blade);
}
logger.info("创建了简化的风力发电机模型。");
}
function animate() {
requestAnimationFrame(animate);
controls.update(); // 更新控制器 (阻尼效果)
renderer.render(scene, camera);
// 更新状态栏信息 (可以降低频率)
if (Math.random() < 0.05) { // 每秒大约更新一次
updateMemoryUsage();
}
}
function onWindowResize() {
const container = elements.mainCanvas.parentElement;
const width = container.clientWidth;
const height = container.clientHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
function zoomCamera(factor) {
camera.position.multiplyScalar(factor);
// 保持相机朝向目标点
controls.update();
}
function toggleMeshVisibility() {
const showMesh = elements.showMeshCheckbox.checked;
logger.info(`切换网格显示: ${showMesh ? '开启' : '关闭'} (功能待实现)`);
// 实际实现需要遍历模型,找到带有顶点信息的几何体,并可能使用如 EdgesGeometry 来显示边框
}
function toggleWireframe() {
const showWireframe = elements.showWireframeCheckbox.checked;
modelGroup.traverse((child) => {
if (child.isMesh && child.material) {
if (Array.isArray(child.material)) {
child.material.forEach(mat => mat.wireframe = showWireframe);
} else {
child.material.wireframe = showWireframe;
}
}
});
logger.info(`切换线框模式: ${showWireframe ? '开启' : '关闭'}`);
}
// --- 仿真控制 ---
function startSimulation() {
if (appState.isSimulationRunning) {
logger.warn("仿真已经在运行中。");
return;
}
logger.info(`开始 ${appState.currentSimulationType} 仿真...`);
appState.isSimulationRunning = true;
appState.simulationStartTime = Date.now();
appState.simulationProgress = 0;
updateStatus('运行中', 'running');
setControlsDisabled(true);
elements.simulationProgress.style.width = '0%';
// 清除旧的报告项 (可选)
// elements.reportContent.innerHTML = '';
addReportItem('info', `启动 ${getSimulationTypeName(appState.currentSimulationType)} 仿真`);
addReportItem('info', '生成网格...');
let progress = 0;
let currentTime = 0;
const totalDuration = parseFloat(elements.totalTime.value) || 5; // 总仿真时间 (s)
const timeStep = parseFloat(elements.timeStep.value) || 0.01;
const totalSteps = Math.ceil(totalDuration / timeStep);
let currentStep = 0;
// 模拟仿真过程
simulationInterval = setInterval(() => {
currentStep++;
progress = Math.min(100, (currentStep / totalSteps) * 100);
currentTime = currentStep * timeStep;
appState.simulationProgress = progress;
elements.simulationProgress.style.width = `${progress}%`;
elements.processingTime.textContent = `${((Date.now() - appState.simulationStartTime) / 1000).toFixed(2)} s`;
// 模拟报告更新
if (currentStep === Math.floor(totalSteps * 0.1)) {
addReportItem('info', `网格生成完成,共 ${Math.floor(80000 + Math.random() * 100000)} 个单元`);
}
if (currentStep === Math.floor(totalSteps * 0.5)) {
addReportItem('info', '求解器开始迭代...');
if (appState.currentSimulationType === 'structural' && Math.random() < 0.3) {
addReportItem('warning', '检测到单元质量较低,可能影响精度');
}
}
if (currentStep === Math.floor(totalSteps * 0.8) && appState.currentSimulationType === 'fluid') {
addReportItem('info', '流场逐渐稳定...');
}
if (progress >= 100) {
simulationComplete();
}
}, 50); // 每50毫秒更新一次进度
}
function stopSimulation() {
if (!appState.isSimulationRunning) {
logger.warn("仿真未在运行。");
return;
}
clearInterval(simulationInterval);
simulationInterval = null;
appState.isSimulationRunning = false;
updateStatus('已停止', 'error'); // 使用错误状态表示非正常结束
setControlsDisabled(false);
logger.info("仿真已手动停止。");
addReportItem('warning', '仿真被用户手动停止');
}
function simulationComplete() {
clearInterval(simulationInterval);
simulationInterval = null;
appState.isSimulationRunning = false;
appState.simulationProgress = 100;
elements.simulationProgress.style.width = '100%';
updateStatus('完成', 'success');
setControlsDisabled(false);
logger.success(`仿真完成,总用时: ${((Date.now() - appState.simulationStartTime) / 1000).toFixed(2)} s`);
addReportItem('success', `仿真计算完成`);
// 生成模拟结果
generateMockResults();
updateResultDisplay();
updateCharts();
}
function resetSimulation() {
logger.info("重置仿真设置和结果...");
if (appState.isSimulationRunning) {
stopSimulation();
}
// 重置参数到默认值
resetParametersToDefault();
// 重置状态栏
appState.simulationProgress = 0;
elements.simulationProgress.style.width = '0%';
elements.processingTime.textContent = '0.00 s';
updateStatus('就绪', 'ready');
// 清除结果显示
clearResultsDisplay();
clearCharts(); // 清除图表数据
// 重置模型视觉效果 (例如线框模式)
if (elements.showWireframeCheckbox.checked) {
elements.showWireframeCheckbox.checked = false;
toggleWireframe();
}
// ... 其他视觉重置
// 清空报告区 (可选)
// elements.reportContent.innerHTML = '';
addReportItem('info', '仿真环境已重置');
logger.info("仿真已重置。");
}
function setControlsDisabled(isDisabled) {
elements.runSimulationBtn.disabled = isDisabled;
elements.stopSimulationBtn.disabled = !isDisabled; // 停止按钮只在运行时启用
elements.resetSimulationBtn.disabled = isDisabled;
elements.newSimulationBtn.disabled = isDisabled;
elements.loadSimulationBtn.disabled = isDisabled;
elements.saveSimulationBtn.disabled = isDisabled;
elements.simulationTypeSelect.disabled = isDisabled;
// 禁用所有参数输入
const paramInputs = document.querySelectorAll('.physics-params input, .physics-params select');
paramInputs.forEach(input => input.disabled = isDisabled);
}
// --- 参数与面板处理 ---
function handleSimulationTypeChange() {
const selectedType = elements.simulationTypeSelect.value;
appState.currentSimulationType = selectedType;
logger.info(`切换仿真类型为: ${getSimulationTypeName(selectedType)}`);
// 隐藏所有参数组
elements.structuralParams.style.display = 'none';
elements.fluidParams.style.display = 'none';
elements.emParams.style.display = 'none';
// 显示对应的参数组
switch (selectedType) {
case 'structural':
elements.structuralParams.style.display = 'block';
elements.legendTitle.textContent = '应力 (MPa)'; // 更新图例标题
break;
case 'fluid':
elements.fluidParams.style.display = 'block';
elements.legendTitle.textContent = '速度 (m/s)'; // 更新图例标题
break;
case 'electromagnetic':
elements.emParams.style.display = 'block';
elements.legendTitle.textContent = '磁场强度 (T)'; // 更新图例标题
break;
case 'coupled':
// 耦合分析可能需要显示多个参数组,或者有特殊的参数组
elements.structuralParams.style.display = 'block'; // 示例:显示结构
elements.fluidParams.style.display = 'block'; // 示例:显示流体
elements.legendTitle.textContent = '综合物理量'; // 更新图例标题
logger.warn("耦合分析参数面板显示待实现。");
break;
}
// 更新结果选项卡的可视状态(例如,如果不是流体分析,禁用流场选项卡)
updateResultTabsAvailability();
}
function getSimulationTypeName(typeKey) {
switch(typeKey) {
case 'structural': return '结构分析';
case 'fluid': return '流体分析';
case 'electromagnetic': return '电磁场分析';
case 'coupled': return '耦合分析';
default: return '未知类型';
}
}
function setDefaultParameters() {
appState.defaultParameters = {
structural: {
materialType: elements.materialType.value,
density: elements.density.value,
youngsModulus: elements.youngsModulus.value,
poissonRatio: elements.poissonRatio.value,
windSpeed: elements.windSpeed.value,
turbulenceIntensity: elements.turbulenceIntensity.value,
rotationSpeed: elements.rotationSpeed.value,
},
fluid: {
fluidType: elements.fluidType.value,
fluidDensity: elements.fluidDensity.value,
fluidViscosity: elements.fluidViscosity.value,
inletVelocity: elements.inletVelocity.value,
turbulenceModel: elements.turbulenceModel.value,
},
em: {
conductivity: elements.conductivity.value,
permeability: elements.permeability.value,
frequency: elements.frequency.value,
emFieldStrength: elements.emFieldStrength.value,
},
settings: {
meshDensity: elements.meshDensity.value,
timeStep: elements.timeStep.value,
totalTime: elements.totalTime.value,
}
};
// 初始化当前参数状态
appState.parameters = JSON.parse(JSON.stringify(appState.defaultParameters)); // 深拷贝
}
function resetParametersToDefault() {
for (const category in appState.defaultParameters) {
for (const param in appState.defaultParameters[category]) {
const element = elements[param]; // 假设 element ID 和参数名一致
if (element) {
element.value = appState.defaultParameters[category][param];
// 更新 appState.parameters
updateParameter(category, param, element.value, false); // 不记录日志
}
}
}
// 特别处理 simulationTypeSelect 的重置(如果需要)
elements.simulationTypeSelect.value = 'structural'; // 或者存储默认值
handleSimulationTypeChange(); // 触发面板更新
}
function updateParameter(category, paramName, value, log = true) {
if (appState.parameters[category]) {
appState.parameters[category][paramName] = value;
if (log) {
logger.info(`参数更新: [${category}] ${paramName} = ${value}`);
}
} else {
logger.warn(`尝试更新未知参数类别: ${category}`);
}
}
// --- 结果处理 ---
function handleResultTabChange(tabId) {
appState.activeResultTab = tabId;
// 移除所有按钮和面板的 active 类
elements.resultTabBtns.forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.results-tabs .tab-pane').forEach(pane => pane.classList.remove('active'));
// 添加 active 类到当前按钮和面板
const currentBtn = document.querySelector(`.results-tabs .tab-btn[data-tab="${tabId}"]`);
const currentPane = document.getElementById(`${tabId}-tab`); // HTML中ID是 xxx-tab
if (currentBtn) currentBtn.classList.add('active');
if (currentPane) currentPane.classList.add('active');
logger.info(`切换到结果选项卡: ${tabId}`);
// 可能需要根据选项卡更新可视化 (例如改变颜色映射或显示的物理量)
updateVisualizationForTab(tabId);
}
function updateResultTabsAvailability() {
// 根据当前仿真类型启用/禁用结果选项卡
const isStructural = appState.currentSimulationType === 'structural' || appState.currentSimulationType === 'coupled';
const isFluid = appState.currentSimulationType === 'fluid' || appState.currentSimulationType === 'coupled';
const isEM = appState.currentSimulationType === 'electromagnetic' || appState.currentSimulationType === 'coupled';
const stressBtn = document.querySelector(`.results-tabs .tab-btn[data-tab="stress"]`);
const flowBtn = document.querySelector(`.results-tabs .tab-btn[data-tab="flow"]`);
const emBtn = document.querySelector(`.results-tabs .tab-btn[data-tab="em-field"]`);
if(stressBtn) stressBtn.style.display = isStructural ? 'inline-block' : 'none';
if(flowBtn) flowBtn.style.display = isFluid ? 'inline-block' : 'none';
if(emBtn) emBtn.style.display = isEM ? 'inline-block' : 'none';
// 如果当前激活的tab被禁用了,切换到一个可用的tab
if (!isStructural && appState.activeResultTab === 'stress') handleResultTabChange('flow'); // 切换到流体(如果可用)
if (!isFluid && appState.activeResultTab === 'flow') handleResultTabChange('stress'); // 切换到结构(如果可用)
if (!isEM && appState.activeResultTab === 'em-field') handleResultTabChange('stress'); // 切换到结构(如果可用)
// 如果所有特定tab都不可用(例如刚切换到耦合或特定类型),确保至少有一个tab是active的
const activeBtn = document.querySelector('.results-tabs .tab-btn.active');
if (!activeBtn || activeBtn.style.display === 'none') {
if(isStructural) handleResultTabChange('stress');
else if(isFluid) handleResultTabChange('flow');
else if(isEM) handleResultTabChange('em-field');
}
}
function generateMockResults() {
// 根据仿真类型生成不同的模拟结果
const results = {};
const stressMax = 50 + Math.random() * 150; // 50 - 200 MPa
const displacementMax = (0.5 + Math.random() * 2) * (parseFloat(appState.parameters.structural?.windSpeed || 10) / 10); // 0.5 - 2.5 mm (与风速相关)
const factor = 1.5 + Math.random() * 3; // 1.5 - 4.5
results.structural = {
maxStress: stressMax.toFixed(1),
minStress: (stressMax * (0.05 + Math.random() * 0.1)).toFixed(1), // 最小应力是最大应力的5%-15%
avgStress: (stressMax * (0.3 + Math.random() * 0.2)).toFixed(1), // 平均应力是最大应力的30%-50%
maxDisplacement: displacementMax.toFixed(2),
safetyFactor: factor.toFixed(2),
safetyStatus: factor < 2 ? 'danger' : (factor < 3 ? 'warning' : 'safe'),
stressData: Array.from({ length: 10 }, () => Math.random() * stressMax) // 模拟图表数据
};
const velocityMax = parseFloat(appState.parameters.fluid?.inletVelocity || 12) * (1 + Math.random() * 0.5); // 最大速度是入口速度的1-1.5倍
const pressureMax = 100 + Math.random() * 500; // 100 - 600 Pa
results.fluid = {
maxVelocity: velocityMax.toFixed(1),
maxPressure: pressureMax.toFixed(0),
liftCoefficient: (0.5 + Math.random() * 0.8).toFixed(3),
dragCoefficient: (0.05 + Math.random() * 0.1).toFixed(3),
velocityData: Array.from({ length: 10 }, () => Math.random() * velocityMax) // 模拟图表数据
};
const magFieldMax = (0.1 + Math.random() * 1.5).toFixed(2); // 0.1 - 1.6 T
const elecFieldMax = (1000 + Math.random() * 9000).toFixed(0); // 1k - 10k V/m
results.em = {
maxMagneticField: magFieldMax,
maxElectricField: elecFieldMax,
currentDensity: (1e4 + Math.random() * 9e4).toExponential(2), // 1e4 - 1e5 A/m^2
emFieldData: Array.from({ length: 10 }, () => Math.random() * magFieldMax) // 模拟图表数据
};
appState.results = results; // 将结果存入状态
}
function updateResultDisplay() {
if (!appState.results) {
logger.warn("没有仿真结果可供显示。");
return;
}
// 更新结构分析结果
if (appState.results.structural) {
const struct = appState.results.structural;
elements.maxStress.textContent = `${struct.maxStress} MPa`;
elements.minStress.textContent = `${struct.minStress} MPa`;
elements.avgStress.textContent = `${struct.avgStress} MPa`;
elements.maxDisplacement.textContent = `${struct.maxDisplacement} mm`;
elements.safetyFactor.textContent = struct.safetyFactor;
elements.safetyStatus.textContent = struct.safetyStatus === 'safe' ? '安全' : (struct.safetyStatus === 'warning' ? '警告' : '危险');
elements.safetyStatus.className = `safety-status ${struct.safetyStatus}`; // 更新类以改变颜色
}
// 更新流体分析结果
if (appState.results.fluid) {
const fluid = appState.results.fluid;
elements.maxVelocity.textContent = `${fluid.maxVelocity} m/s`;
elements.maxPressure.textContent = `${fluid.maxPressure} Pa`;
elements.liftCoefficient.textContent = fluid.liftCoefficient;
elements.dragCoefficient.textContent = fluid.dragCoefficient;
}
// 更新电磁分析结果
if (appState.results.em) {
const em = appState.results.em;
elements.maxMagneticField.textContent = `${em.maxMagneticField} T`;
elements.maxElectricField.textContent = `${em.maxElectricField} V/m`;
elements.currentDensity.textContent = `${em.currentDensity} A/m²`;
}
}
function clearResultsDisplay() {
elements.maxStress.textContent = `0 MPa`;
elements.minStress.textContent = `0 MPa`;
elements.avgStress.textContent = `0 MPa`;
elements.maxDisplacement.textContent = `0 mm`;
elements.safetyFactor.textContent = 'N/A';
elements.safetyStatus.textContent = '未计算';
elements.safetyStatus.className = 'safety-status';
elements.maxVelocity.textContent = '0 m/s';
elements.maxPressure.textContent = '0 Pa';
elements.liftCoefficient.textContent = '0';
elements.dragCoefficient.textContent = '0';
elements.maxMagneticField.textContent = '0 T';
elements.maxElectricField.textContent = '0 V/m';
elements.currentDensity.textContent = '0 A/m²';
appState.results = null; // 清除存储的结果
}
function updateVisualizationForTab(tabId) {
// 根据当前结果选项卡更新3D视图(例如颜色映射)
logger.info(`更新3D可视化以反映 ${tabId} 结果 (功能待实现)`);
// 示例:改变图例和可能的颜色映射
let legendMin = 0, legendMax = 100;
let colorMapFunction = (value) => new THREE.Color(0xffffff); // 默认白色
if (tabId === 'stress' && appState.results?.structural) {
legendMin = parseFloat(appState.results.structural.minStress);
legendMax = parseFloat(appState.results.structural.maxStress);
elements.legendTitle.textContent = '应力 (MPa)';
// 定义应力颜色映射函数 (例如,从蓝到红)
colorMapFunction = (value) => {
const t = Math.max(0, Math.min(1, (value - legendMin) / (legendMax - legendMin || 1)));
return new THREE.Color().setHSL(0.7 * (1 - t), 1, 0.5); // HSL: 0.7=蓝, 0=红
};
} else if (tabId === 'flow' && appState.results?.fluid) {
legendMin = 0;
legendMax = parseFloat(appState.results.fluid.maxVelocity);
elements.legendTitle.textContent = '速度 (m/s)';
// 定义速度颜色映射函数 (例如,从冷色到暖色)
colorMapFunction = (value) => {
const t = Math.max(0, Math.min(1, (value - legendMin) / (legendMax - legendMin || 1)));
return new THREE.Color().setHSL(0.6 - t * 0.6, 1, 0.5); // HSL: 0.6=青, 0=红
};
} else if (tabId === 'em-field' && appState.results?.em) {
legendMin = 0;
legendMax = parseFloat(appState.results.em.maxMagneticField);
elements.legendTitle.textContent = '磁场强度 (T)';
// 定义磁场颜色映射函数
colorMapFunction = (value) => {
const t = Math.max(0, Math.min(1, (value - legendMin) / (legendMax - legendMin || 1)));
return new THREE.Color().setHSL(0.15 + t * 0.4, 1, 0.5); // HSL: 0.15=黄绿, 0.55=蓝紫
};
}
// 更新图例标签
if (elements.legendLabels) {
elements.legendLabels.innerHTML = `
<span>${legendMin.toFixed(1)}</span>
<span>${((legendMin + legendMax) / 2).toFixed(1)}</span>
<span>${legendMax.toFixed(1)}</span>
`;
}
// 应用颜色映射到模型 (这里需要更复杂的逻辑来将数据映射到顶点颜色)
applyColorMapToModel(colorMapFunction);
}
function applyColorMapToModel(colorMapFunction) {
// 这是一个非常简化的示例,实际应用需要顶点数据和结果数据
modelGroup.traverse((child) => {
if (child.isMesh && child.material && !child.material.wireframe) {
// 模拟一个随机值来应用颜色映射
const randomValueForColor = Math.random() * 150; // 假设一个范围
const color = colorMapFunction(randomValueForColor);
if (Array.isArray(child.material)) {
child.material.forEach(mat => {
if (mat.isMeshStandardMaterial) mat.color.copy(color);
});
} else if (child.material.isMeshStandardMaterial) {
child.material.color.copy(color);
}
}
});
}
// --- 图表处理 ---
function initializeCharts() {
if (typeof Chart === 'undefined') return; // 如果Chart.js未加载,则不初始化
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { beginAtZero: true }
},
plugins: { legend: { display: false } }
};
if (elements.stressChartCanvas) {
chartInstances.stress = new Chart(elements.stressChartCanvas.getContext('2d'), {
type: 'bar',
data: { labels: [], datasets: [{ label: '应力', data: [], backgroundColor: 'rgba(255, 99, 132, 0.6)' }] },
options: chartOptions
});
}
if (elements.velocityChartCanvas) {
chartInstances.velocity = new Chart(elements.velocityChartCanvas.getContext('2d'), {
type: 'line',
data: { labels: [], datasets: [{ label: '速度', data: [], borderColor: 'rgba(54, 162, 235, 1)', tension: 0.1 }] },
options: chartOptions
});
}
if (elements.emFieldChartCanvas) {
chartInstances.emField = new Chart(elements.emFieldChartCanvas.getContext('2d'), {
type: 'radar', // 雷达图示例
data: {
labels: ['点1', '点2', '点3', '点4', '点5'],
datasets: [{ label: '磁场强度', data: [], backgroundColor: 'rgba(255, 206, 86, 0.2)', borderColor: 'rgba(255, 206, 86, 1)', borderWidth: 1 }]
},
options: { maintainAspectRatio: false, scales: { r: { beginAtZero: true } } }
});
}
}
function updateCharts() {
if (typeof Chart === 'undefined' || !appState.results) return;
if (chartInstances.stress && appState.results.structural) {
const stressData = appState.results.structural.stressData || [];
chartInstances.stress.data.labels = stressData.map((_, i) => `区域 ${i + 1}`);
chartInstances.stress.data.datasets[0].data = stressData;
chartInstances.stress.update();
}
if (chartInstances.velocity && appState.results.fluid) {
const velocityData = appState.results.fluid.velocityData || [];
chartInstances.velocity.data.labels = velocityData.map((_, i) => `时间点 ${i + 1}`);
chartInstances.velocity.data.datasets[0].data = velocityData;
chartInstances.velocity.update();
}
if (chartInstances.emField && appState.results.em) {
const emData = appState.results.em.emFieldData || [0,0,0,0,0]; // 确保有5个数据点
chartInstances.emField.data.datasets[0].data = emData.slice(0, 5); // 取前5个
chartInstances.emField.update();
}
}
function clearCharts() {
if (typeof Chart === 'undefined') return;
for (const key in chartInstances) {
chartInstances[key].data.labels = [];
chartInstances[key].data.datasets[0].data = [];
chartInstances[key].update();
}
}
// --- 状态栏与报告 ---
function updateStatus(text, statusClass) { // statusClass: 'ready', 'running', 'success', 'error'
elements.simulationStatus.textContent = text;
elements.simulationStatus.className = `status-${statusClass}`;
}
function updateMemoryUsage() {
// 模拟内存变化
if (appState.isSimulationRunning) {
appState.memoryUsage += (Math.random() * 5 - 2); // 运行时内存波动
} else {
appState.memoryUsage -= Math.random() * 1; // 非运行时缓慢减少
}
appState.memoryUsage = Math.max(30, Math.min(500, appState.memoryUsage)); // 限制在30-500MB
elements.memoryUsage.textContent = `${appState.memoryUsage.toFixed(1)} MB`;
}
function addReportItem(type, message) { // type: 'info', 'warning', 'error', 'success'
const item = document.createElement('div');
item.className = 'report-item';
const timeSpan = document.createElement('span');
timeSpan.className = 'report-time';
timeSpan.textContent = new Date().toLocaleTimeString();
const messageSpan = document.createElement('span');
messageSpan.className = `report-message ${type}`;
messageSpan.textContent = message;
item.appendChild(timeSpan);
item.appendChild(messageSpan);
elements.reportContent.appendChild(item);
// 滚动到底部
elements.reportContent.scrollTop = elements.reportContent.scrollHeight;
}
// --- 模型树交互 (示例) ---
function handleModelTreeClick(itemElement) {
// 移除旧的 active 类
if (appState.activeModelItem) {
appState.activeModelItem.classList.remove('active');
}
// 添加 active 类到新项
itemElement.classList.add('active');
appState.activeModelItem = itemElement;
const itemName = itemElement.textContent.trim();
logger.info(`在模型树中选择了: ${itemName}`);
// 可以在这里添加逻辑,例如在3D视图中高亮对应的部件
highlightModelPart(itemName);
}
function highlightModelPart(partName) {
logger.info(`高亮显示模型部件: ${partName} (功能待实现)`);
// 实际实现需要根据 partName 查找 3D 对象并应用高亮效果 (例如改变材质或添加轮廓线)
modelGroup.traverse((child) => {
if (child.isMesh) {
// 简单的基于名称的匹配(可能不准确)
const nameMatch = partName.toLowerCase().includes('叶片') && child.geometry.type === 'BoxGeometry' ||
partName.toLowerCase().includes('塔架') && child.geometry.type === 'CylinderGeometry' && child.position.y < 10 ||
partName.toLowerCase().includes('轮毂') && child.geometry.type === 'CylinderGeometry' && child.position.y >= 10;
if (nameMatch) {
// 临时高亮:改变颜色
if (child.userData.originalColor === undefined) {
child.userData.originalColor = child.material.color.clone();
}
child.material.color.set(0xffaa00); // 设置为橙色高亮
child.material.needsUpdate = true;
} else if(child.userData.originalColor) {
// 恢复非选中对象的颜色
child.material.color.copy(child.userData.originalColor);
child.material.needsUpdate = true;
delete child.userData.originalColor; // 清除标记
}
}
});
}