10、复杂曲面设计与建模组件 - /设计与仿真组件/complex-surface-modeling

76个工业组件库示例汇总

复杂曲面设计与建模组件

这是一个交互式的 Web 组件,旨在模拟航空/汽车行业中复杂曲面(如飞机发动机短舱)的设计与气动外形优化的初步概念可视化。

功能特点

  • 参数化建模: 用户可以通过滑块实时调整关键几何参数(如进/出气口半径、长度、曲率因子),即时更新 3D 模型。
  • 3D 可视化: 使用 Three.js 渲染可交互的 3D 模型,支持旋转、缩放和平移视图。
  • 苹果科技风格界面: 采用简洁、优雅的界面设计风格。
  • 模拟优化动画: 点击"开始优化"按钮,可以观察到一个模拟的曲面形态变化过程,概念性地展示优化迭代。
  • 线框/实体切换: 方便用户查看模型的几何结构。
  • 概念气流模拟: 可选择显示或隐藏模拟的气流线,用于初步理解气流与外形的相互作用(仅为视觉概念)。
  • 响应式布局: 界面布局能自动适应不同的屏幕尺寸。

如何使用

  1. 打开 index.html: 在现代的网页浏览器中打开 设计与仿真组件/complex-surface-modeling/index.html 文件。
  2. 调整参数: 在左侧的"参数配置"面板中,拖动滑块来改变模型的几何形状。3D 视图会实时更新。
  3. 交互视图: 在右侧的 3D 视图区域,使用鼠标进行以下操作:
    • 旋转: 按住鼠标左键拖动。
    • 缩放: 滚动鼠标滚轮。
    • 平移: 按住鼠标右键(或 Ctrl/Cmd + 左键)拖动。
  4. 模拟优化: 点击"开始优化 (模拟)"按钮,观察模型曲率的平滑变化动画。
  5. 视图控制:
    • 点击"重置视图"恢复到默认相机位置。
    • 点击"切换线框"在实体模型和线框模型之间切换。
  6. 模拟气流: 点击"模拟气流 (概念)"按钮显示/隐藏概念性的气流线。

文件结构

  • index.html: 组件的 HTML 结构,包含用户界面布局和元素。
  • styles.css: 定义组件外观和布局的 CSS 样式表,采用苹果科技风格。
  • script.js: 核心 JavaScript 代码,负责:
    • Three.js 场景设置与渲染。
    • 参数化曲面模型的生成 (LatheGeometry)。
    • 处理用户输入与交互(滑块、按钮、鼠标控制)。
    • 实现模拟优化动画和概念气流效果。
  • README.md: 本文件,提供组件的说明。

技术栈

  • HTML5
  • CSS3 (Flexbox, CSS Variables)
  • JavaScript (ES6+)
  • Three.js (r128) for 3D rendering
  • Three.js OrbitControls for camera interaction

注意事项

  • 这是一个概念演示组件,其"优化"和"气流模拟"功能仅为视觉效果,不包含真实的物理计算或 CFD 仿真
  • 模型的几何形状生成基于简化的曲线,可根据需要替换为更复杂的数学描述(如 NURBS)。
  • 性能:对于非常复杂的模型或大量的参数更新,实时重新生成模型可能会影响性能,未来可考虑优化策略。

效果展示

10、复杂曲面设计与建模组件 - /设计与仿真组件/complex-surface-modeling_第1张图片

源码

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 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

/* 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

// 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}

`
; } });

你可能感兴趣的:(工业应用组件库,3d,前端,仿真)