76个工业组件库示例汇总
这是一个交互式的 Web 组件,旨在模拟航空/汽车行业中复杂曲面(如飞机发动机短舱)的设计与气动外形优化的初步概念可视化。
index.html
: 在现代的网页浏览器中打开 设计与仿真组件/complex-surface-modeling/index.html
文件。index.html
: 组件的 HTML 结构,包含用户界面布局和元素。styles.css
: 定义组件外观和布局的 CSS 样式表,采用苹果科技风格。script.js
: 核心 JavaScript 代码,负责:
LatheGeometry
)。README.md
: 本文件,提供组件的说明。<!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 for camera interaction -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<div class="modeling-container">
<header class="app-header">
<h1>复杂曲面设计与建模</h1>
<p>应用于飞机发动机气动外形优化</p>
</header>
<div class="main-content-area">
<!-- 左侧参数与控制面板 -->
<aside class="controls-panel">
<h2>参数配置 & 优化</h2>
<div class="parameter-section">
<h3>基础外形控制</h3>
<label for="inletRadius">进气口半径:</label>
<input type="range" id="inletRadius" name="inletRadius" min="0.5" max="2.0" value="1.0" step="0.05">
<span class="param-value" id="inletRadiusValue">1.0</span>
<label for="outletRadius">出气口半径:</label>
<input type="range" id="outletRadius" name="outletRadius" min="0.3" max="1.5" value="0.8" step="0.05">
<span class="param-value" id="outletRadiusValue">0.8</span>
<label for="nacelleLength">短舱长度:</label>
<input type="range" id="nacelleLength" name="nacelleLength" min="3.0" max="8.0" value="5.0" step="0.1">
<span class="param-value" id="nacelleLengthValue">5.0</span>
<label for="curvatureFactor">曲率因子:</label>
<input type="range" id="curvatureFactor" name="curvatureFactor" min="0.1" max="1.0" value="0.5" step="0.05">
<span class="param-value" id="curvatureFactorValue">0.5</span>
</div>
<div class="parameter-section">
<h3>优化目标</h3>
<label for="optimizeTarget">优化目标:</label>
<select id="optimizeTarget" name="optimizeTarget">
<option value="dragReduction">最小化阻力</option>
<option value="noiseReduction" disabled>最小化噪声</option>
<option value="efficiencyMax" disabled>最大化效率</option>
</select>
</div>
<div class="action-buttons">
<button id="optimizeButton">开始优化 (模拟)</button>
<button id="resetViewButton">重置视图</button>
<button id="toggleWireframeButton">切换线框</button>
<button id="showAirflowButton">模拟气流 (概念)</button>
</div>
<div class="status-display">
<h3>状态</h3>
<p id="statusText">准备就绪。请调整参数或开始优化。</p>
</div>
</aside>
<!-- 右侧 3D 可视化区域 -->
<main class="visualization-area">
<div id="rendererContainer"></div>
</main>
</div>
<footer class="app-footer">
<p>航空/汽车行业数字化建模组件</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>
/* styles.css - Complex Surface Modeling Component */
:root {
--primary-bg: #ffffff;
--secondary-bg: #f5f5f7;
--controls-bg: #e8e8ed;
--text-primary: #1d1d1f;
--text-secondary: #515154;
--accent-blue: #007aff;
--accent-blue-hover: #005ec4;
--border-color: #d2d2d7;
--shadow-color: rgba(0, 0, 0, 0.08);
--apple-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body {
font-family: var(--apple-font);
margin: 0;
background-color: var(--secondary-bg);
color: var(--text-primary);
font-size: 14px; /* Slightly smaller base font size */
line-height: 1.5;
overflow-x: hidden; /* Prevent horizontal scroll */
}
.modeling-container {
width: 100%; /* Take full width */
max-width: 100%; /* Ensure it doesn't exceed viewport */
min-height: calc(100vh - 40px); /* Fill viewport height minus potential padding */
display: flex;
flex-direction: column;
background-color: var(--primary-bg);
box-sizing: border-box;
}
.app-header {
background-color: var(--primary-bg);
text-align: center;
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
}
.app-header h1 {
margin: 0 0 5px 0;
font-size: 1.8em;
font-weight: 600;
color: var(--text-primary);
}
.app-header p {
margin: 0;
color: var(--text-secondary);
font-size: 0.9em;
}
.main-content-area {
flex-grow: 1; /* Allow this area to fill remaining space */
display: flex;
width: 100%;
}
.controls-panel {
width: 320px; /* Fixed width for controls */
flex-shrink: 0; /* Prevent shrinking */
background-color: var(--controls-bg);
padding: 20px;
border-right: 1px solid var(--border-color);
overflow-y: auto; /* Allow scrolling if content exceeds height */
box-sizing: border-box;
max-height: calc(100vh - 100px); /* Limit height, adjust 100px based on header/footer */
}
.controls-panel h2 {
margin-top: 0;
margin-bottom: 25px;
font-size: 1.3em;
font-weight: 600;
color: var(--text-primary);
border-bottom: 1px solid #c8c8cc;
padding-bottom: 10px;
}
.parameter-section {
margin-bottom: 30px;
}
.parameter-section h3 {
margin-top: 0;
margin-bottom: 15px;
font-size: 1.0em;
font-weight: 600;
color: var(--text-secondary);
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: var(--text-primary);
font-size: 0.95em;
}
input[type="range"] {
width: 100%;
height: 4px;
cursor: pointer;
appearance: none;
background: #dcdce0;
border-radius: 4px;
outline: none;
margin-bottom: 5px; /* Space before value */
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
background: var(--accent-blue);
border-radius: 50%;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
background: var(--accent-blue);
border-radius: 50%;
cursor: pointer;
border: none;
}
.param-value {
display: inline-block;
margin-bottom: 15px;
font-size: 0.9em;
color: var(--text-secondary);
}
select {
width: 100%;
padding: 8px 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
background-color: var(--primary-bg);
font-family: inherit;
font-size: 0.95em;
margin-bottom: 20px;
appearance: none; /* Remove default arrow */
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%208l5%205%205-5z%22%20fill%3D%22%23515154%22%2F%3E%3C%2Fsvg%3E');
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 12px;
}
.action-buttons button {
display: block; /* Stack buttons */
width: 100%;
padding: 10px 15px;
margin-bottom: 10px;
font-size: 0.95em;
font-weight: 500;
color: #fff;
background-color: var(--accent-blue);
border: none;
border-radius: 6px;
cursor: pointer;
text-align: center;
transition: background-color 0.2s ease, box-shadow 0.2s ease;
}
.action-buttons button:hover {
background-color: var(--accent-blue-hover);
box-shadow: 0 2px 5px rgba(0, 122, 255, 0.2);
}
.action-buttons button:disabled {
background-color: #b0b0b5;
cursor: not-allowed;
box-shadow: none;
}
/* Style specific buttons */
#resetViewButton, #toggleWireframeButton, #showAirflowButton {
background-color: #6c757d;
transition: background-color 0.2s ease;
}
#resetViewButton:hover, #toggleWireframeButton:hover, #showAirflowButton:hover {
background-color: #5a6268;
box-shadow: none;
}
.status-display {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #c8c8cc;
}
.status-display h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 1.0em;
font-weight: 600;
color: var(--text-secondary);
}
#statusText {
font-size: 0.9em;
color: var(--text-primary);
min-height: 3em; /* Reserve some space */
}
.visualization-area {
flex-grow: 1; /* Take remaining width */
position: relative; /* Needed for absolute positioning of canvas container */
background-color: var(--secondary-bg); /* Match body background */
overflow: hidden; /* Prevent renderer overflow */
}
#rendererContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.app-footer {
text-align: center;
padding: 10px 20px;
border-top: 1px solid var(--border-color);
background-color: var(--primary-bg);
color: var(--text-secondary);
font-size: 0.85em;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.main-content-area {
flex-direction: column; /* Stack vertically */
}
.controls-panel {
width: 100%; /* Full width */
border-right: none;
border-bottom: 1px solid var(--border-color);
max-height: 50vh; /* Limit height more strictly */
}
.visualization-area {
height: 50vh; /* Set fixed height for viz area */
min-height: 300px;
}
.app-header h1 {
font-size: 1.5em;
}
}
// script.js - Complex Surface Modeling Component
document.addEventListener('DOMContentLoaded', () => {
// --- DOM Elements ---
const rendererContainer = document.getElementById('rendererContainer');
const inletRadiusSlider = document.getElementById('inletRadius');
const outletRadiusSlider = document.getElementById('outletRadius');
const nacelleLengthSlider = document.getElementById('nacelleLength');
const curvatureFactorSlider = document.getElementById('curvatureFactor');
const inletRadiusValueSpan = document.getElementById('inletRadiusValue');
const outletRadiusValueSpan = document.getElementById('outletRadiusValue');
const nacelleLengthValueSpan = document.getElementById('nacelleLengthValue');
const curvatureFactorValueSpan = document.getElementById('curvatureFactorValue');
const optimizeButton = document.getElementById('optimizeButton');
const resetViewButton = document.getElementById('resetViewButton');
const toggleWireframeButton = document.getElementById('toggleWireframeButton');
const showAirflowButton = document.getElementById('showAirflowButton');
const statusText = document.getElementById('statusText');
// --- Three.js Setup ---
let scene, camera, renderer, controls, surfaceMesh, material, wireframeMaterial, airflowLines;
let isWireframe = false;
let isAnimating = false;
function initThreeJS() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf5f5f7); // Match body background
// Camera
const aspect = rendererContainer.clientWidth / rendererContainer.clientHeight;
camera = new THREE.PerspectiveCamera(50, aspect, 0.1, 1000);
camera.position.set(5, 4, 10);
camera.lookAt(scene.position);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(rendererContainer.clientWidth, rendererContainer.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
rendererContainer.appendChild(renderer.domElement);
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 15, 5);
scene.add(directionalLight);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.1;
controls.rotateSpeed = 0.7;
controls.zoomSpeed = 0.8;
// Materials
material = new THREE.MeshStandardMaterial({
color: 0xcccccc, // Metallic gray
metalness: 0.6,
roughness: 0.4,
side: THREE.DoubleSide
});
wireframeMaterial = new THREE.MeshBasicMaterial({
color: 0x007aff, // Blue wireframe
wireframe: true,
transparent: true,
opacity: 0.7
});
// Initial Surface Creation
createSurface();
// Start animation loop
animate();
}
// --- Surface Generation ---
function createSurface() {
if (surfaceMesh) {
scene.remove(surfaceMesh);
surfaceMesh.geometry.dispose(); // Dispose old geometry
}
const inletRadius = parseFloat(inletRadiusSlider.value);
const outletRadius = parseFloat(outletRadiusSlider.value);
const length = parseFloat(nacelleLengthSlider.value);
const curvature = parseFloat(curvatureFactorSlider.value);
// Use LatheGeometry to create a shape by revolving a profile curve
const points = [];
const segments = 20; // Number of points along the profile
for (let i = 0; i <= segments; i++) {
const t = i / segments; // Parameter from 0 to 1
const x = -length / 2 + t * length; // Position along the length
// Simple curve: linear interpolation + sine wave for curvature
// Adjust the curve function for more realistic shapes
const radius = inletRadius + (outletRadius - inletRadius) * t + Math.sin(t * Math.PI) * curvature * (inletRadius + outletRadius) / 3;
points.push(new THREE.Vector2(radius, x)); // THREE.LatheGeometry uses (radius, y)
}
const radialSegments = 40;
const geometry = new THREE.LatheGeometry(points, radialSegments);
surfaceMesh = new THREE.Mesh(geometry, material);
scene.add(surfaceMesh);
updateWireframeVisibility(); // Apply wireframe if active
updateStatus("外形已根据参数更新。");
}
// --- Animation & Rendering Loop ---
function animate() {
requestAnimationFrame(animate);
controls.update(); // Required for damping
renderer.render(scene, camera);
}
// --- Event Listeners ---
function setupEventListeners() {
// Parameter sliders
[inletRadiusSlider, outletRadiusSlider, nacelleLengthSlider, curvatureFactorSlider].forEach(slider => {
slider.addEventListener('input', handleSliderChange);
});
// Action Buttons
optimizeButton.addEventListener('click', simulateOptimization);
resetViewButton.addEventListener('click', resetCameraView);
toggleWireframeButton.addEventListener('click', toggleWireframe);
showAirflowButton.addEventListener('click', simulateAirflow);
// Window Resize
window.addEventListener('resize', onWindowResize);
}
function handleSliderChange(event) {
const valueSpan = document.getElementById(event.target.id + 'Value');
if (valueSpan) {
valueSpan.textContent = parseFloat(event.target.value).toFixed(2);
}
// Regenerate surface only on 'change' (mouse up) or less frequently for performance?
// For now, update on 'input' for immediate feedback
if (!isAnimating) { // Avoid updates during animations
createSurface();
}
}
// --- Button Actions ---
function simulateOptimization() {
if (isAnimating) return;
isAnimating = true;
optimizeButton.disabled = true;
updateStatus("开始模拟优化过程...");
// Simple animation: smoothly change curvatureFactor to a target value (e.g., 0.2)
const startValue = parseFloat(curvatureFactorSlider.value);
const targetValue = 0.2 + Math.random() * 0.2; // Random target near optimal
const duration = 1500; // ms
let startTime = null;
function optimizationStep(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = 0.5 - 0.5 * Math.cos(progress * Math.PI); // Ease in/out
const currentValue = startValue + (targetValue - startValue) * easedProgress;
curvatureFactorSlider.value = currentValue;
curvatureFactorValueSpan.textContent = currentValue.toFixed(2);
createSurface(); // Update mesh in each step
if (progress < 1) {
requestAnimationFrame(optimizationStep);
} else {
updateStatus(`优化模拟完成。最终曲率因子: ${currentValue.toFixed(2)}`);
isAnimating = false;
optimizeButton.disabled = false;
}
}
requestAnimationFrame(optimizationStep);
}
function resetCameraView() {
controls.reset();
camera.position.set(5, 4, 10);
camera.lookAt(scene.position);
controls.update();
updateStatus("视图已重置。");
}
function toggleWireframe() {
isWireframe = !isWireframe;
updateWireframeVisibility();
toggleWireframeButton.textContent = isWireframe ? "显示实体" : "切换线框";
updateStatus(isWireframe ? "线框模式已启用。" : "线框模式已禁用。");
}
function updateWireframeVisibility() {
if (!surfaceMesh) return;
if (isWireframe) {
surfaceMesh.material = wireframeMaterial;
} else {
surfaceMesh.material = material;
}
}
function simulateAirflow() {
if (airflowLines) {
scene.remove(airflowLines);
airflowLines = null;
showAirflowButton.textContent = "模拟气流 (概念)";
updateStatus("气流线已移除。");
return;
}
updateStatus("正在生成概念气流线...");
showAirflowButton.textContent = "移除气流线";
// --- Conceptual Airflow Lines (Simple Example) ---
const linesGroup = new THREE.Group();
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00aaff, transparent: true, opacity: 0.6 });
const numLines = 15;
const length = parseFloat(nacelleLengthSlider.value);
const inletR = parseFloat(inletRadiusSlider.value);
for (let i = 0; i < numLines; i++) {
const startRadius = inletR * 0.8 * (i / (numLines -1)); // Start near center
const points = [];
points.push(new THREE.Vector3(0, startRadius, -length * 0.7)); // Start in front
// Simple path - slightly bending inwards then outwards
points.push(new THREE.Vector3(0, startRadius * 0.95, -length * 0.4));
points.push(new THREE.Vector3(0, startRadius * 0.9, 0));
points.push(new THREE.Vector3(0, startRadius * 1.1, length * 0.4));
points.push(new THREE.Vector3(0, startRadius * 1.2, length * 0.7)); // End behind
const curve = new THREE.CatmullRomCurve3(points);
const curvePoints = curve.getPoints(50);
const geometry = new THREE.BufferGeometry().setFromPoints(curvePoints);
const line = new THREE.Line(geometry, lineMaterial);
// Rotate lines around the z-axis (assuming engine points along z)
// Adjust rotation axis if needed. Let's assume engine points along Y.
const angle = (i / numLines) * Math.PI * 2 * 0.9 + Math.PI * 0.05; // Spread lines around
line.rotation.y = angle;
linesGroup.add(line);
}
airflowLines = linesGroup;
scene.add(airflowLines);
updateStatus("概念气流线已显示。");
}
// --- Utility Functions ---
function updateStatus(message) {
statusText.textContent = message;
console.log("Status:", message);
}
function onWindowResize() {
if (!renderer || !camera) return;
const width = rendererContainer.clientWidth;
const height = rendererContainer.clientHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
// --- Initialization Call ---
try {
initThreeJS();
setupEventListeners();
updateStatus("复杂曲面建模组件初始化成功。");
} catch (error) {
console.error("初始化失败:", error);
updateStatus(`错误: ${error.message}`);
rendererContainer.innerHTML = `无法加载3D视图。错误:
${error.message}`;
}
});