11、参数化三维产品设计组件 - /设计与仿真组件/parametric-3d-product-design

news2025/5/13 13:29:48

76个工业组件库示例汇总

参数化三维产品设计组件 (注塑模具与公差分析)

概述

这是一个交互式的 Web 组件,旨在演示简单的三维零件(如带凸台的方块)的参数化设计过程,并结合注塑模具设计(如开模动画)与公差分析(如可视化公差带)的概念性可视化。

主要功能

  • 参数化零件建模:
    • 通过滑块实时修改零件的基础尺寸(宽度、高度、深度)。
    • 通过滑块实时修改特征(圆柱凸台)的尺寸(直径、高度)和相对位置。
  • 实时 3D 可视化:
    • 使用 Three.js 渲染零件模型。
    • 支持通过鼠标进行视图交互(旋转、缩放、平移)。
  • 概念性分析与模拟:
    • 公差可视化: 切换显示尺寸公差(最小/最大包络盒)和位置公差(特征允许范围)的理论边界。
    • 模拟开模: 播放简化的动画,展示模具上下型腔分离的过程。
  • 界面与风格:
    • 采用苹果科技风格,界面简洁直观。
    • 响应式布局,适应不同屏幕尺寸。

如何使用

  1. 打开页面: 在浏览器中打开 index.html 文件。
  2. 调整参数:
    • 在左侧面板的 “基础零件尺寸”“特征: 圆柱凸台” 部分,拖动滑块调整零件的几何参数。
    • 3D 模型会在释放滑块后更新。
  3. 交互视图:
    • 在右侧 3D 视图区,按住鼠标 左键拖动 进行旋转。
    • 滚动鼠标滚轮 进行缩放。
    • 按住鼠标 右键 (或 Ctrl/Cmd + 左键) 拖动 进行平移。
  4. 公差分析 (概念):
    • “公差分析” 部分调整公差值滑块。
    • 点击 “显示/隐藏公差范围” 按钮,切换显示红/绿/蓝色透明几何体,表示理论上的公差带。
  5. 模拟开模 (概念):
    • 点击 “模拟开模 (概念)” 按钮,观看模具分离动画(零件会暂时隐藏)。
    • 再次点击(按钮变为 “复位模具”) 可观看模具闭合动画,并重新显示零件。
  6. 重置视图: 点击 “重置视图” 按钮,将相机恢复到默认位置和朝向。

文件结构

parametric-3d-product-design/
├── index.html         # HTML 页面结构
├── styles.css         # CSS 样式定义
├── script.js          # JavaScript 交互与3D逻辑
└── README.md          # 本说明文件

技术栈

  • HTML5 / CSS3 (Flexbox, CSS Variables)
  • JavaScript (ES6+)
  • Three.js (r128)
  • Three.js OrbitControls

重要提示

  • 概念演示: 本组件主要用于演示原理,并非精确的工程工具。
  • 公差可视化: 仅为理论边界的概念性展示,不执行 实际的公差叠加或统计分析。
  • 开模模拟: 动画效果高度简化,不涉及真实模具的复杂结构(如滑块、顶针、分型面细节等)。
  • 几何模型: 零件由简单的几何体构成,未使用 CSG (构造实体几何) 进行精确合并,可能存在视觉穿插。如需精确模型,可考虑引入相关库。
  • 性能: 频繁更新参数(尤其在公差可视化开启时)可能影响性能。

效果展示

在这里插入图片描述

源码

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/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
    <!-- Optional: Add CSG library if needed for complex boolean operations -->
    <!-- <script src="path/to/three-csg.js"></script> -->
</head>
<body>
    <div class="product-design-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="partWidth">宽度 (X):</label>
                    <input type="range" id="partWidth" name="partWidth" min="20" max="100" value="50" step="1">
                    <span class="param-value" id="partWidthValue">50</span> mm

                    <label for="partHeight">高度 (Y):</label>
                    <input type="range" id="partHeight" name="partHeight" min="10" max="80" value="30" step="1">
                    <span class="param-value" id="partHeightValue">30</span> mm

                    <label for="partDepth">深度 (Z):</label>
                    <input type="range" id="partDepth" name="partDepth" min="20" max="100" value="40" step="1">
                    <span class="param-value" id="partDepthValue">40</span> mm
                </div>

                <div class="parameter-section">
                    <h3>特征: 圆柱凸台 (Boss)</h3>
                    <label for="bossDiameter">直径:</label>
                    <input type="range" id="bossDiameter" name="bossDiameter" min="5" max="25" value="15" step="0.5">
                    <span class="param-value" id="bossDiameterValue">15.0</span> mm

                    <label for="bossHeight">高度:</label>
                    <input type="range" id="bossHeight" name="bossHeight" min="2" max="20" value="10" step="0.5">
                    <span class="param-value" id="bossHeightValue">10.0</span> mm

                    <label for="bossPosX">X 位置 (%):</label> <!-- Position relative to width -->
                    <input type="range" id="bossPosX" name="bossPosX" min="10" max="90" value="50" step="1">
                    <span class="param-value" id="bossPosXValue">50</span> %

                    <label for="bossPosZ">Z 位置 (%):</label> <!-- Position relative to depth -->
                    <input type="range" id="bossPosZ" name="bossPosZ" min="10" max="90" value="50" step="1">
                    <span class="param-value" id="bossPosZValue">50</span> %
                </div>

                 <div class="parameter-section">
                    <h3>公差分析 (概念)</h3>
                     <label for="dimensionTolerance">尺寸公差 (+/-):</label>
                    <input type="range" id="dimensionTolerance" name="dimensionTolerance" min="0.0" max="1.0" value="0.1" step="0.05">
                    <span class="param-value" id="dimensionToleranceValue">0.10</span> mm

                    <label for="positionTolerance">位置公差 (圆域 +/-):</label> <!-- Tolerance zone for boss position -->
                    <input type="range" id="positionTolerance" name="positionTolerance" min="0.0" max="0.5" value="0.05" step="0.01">
                    <span class="param-value" id="positionToleranceValue">0.05</span> mm
                 </div>

                <div class="action-buttons">
                    <button id="analyzeToleranceButton">显示/隐藏公差范围</button>
                    <button id="simulateMoldButton">模拟开模 (概念)</button>
                    <button id="resetViewButton">重置视图</button>
                </div>

                 <div class="status-display">
                    <h3>状态</h3>
                    <p id="statusText">准备就绪。请调整参数。</p>
                 </div>
            </aside>

            <!-- 右侧 3D 可视化区域 -->
            <main class="visualization-area">
                <div id="rendererContainer"></div>
                <!-- Optional: Maybe add overlay for tolerance info -->
            </main>
        </div>

        <footer class="app-footer">
            <p>注塑模具设计与公差分析辅助组件</p>
        </footer>
    </div>

    <script src="script.js"></script>
</body>
</html> 

styles.css

/* styles.css - Parametric 3D Product Design 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;
    --tolerance-color-min: rgba(255, 0, 0, 0.3); /* Red tint for min */
    --tolerance-color-max: rgba(0, 255, 0, 0.3); /* Green tint for max */
}

body {
    font-family: var(--apple-font);
    margin: 0;
    background-color: var(--secondary-bg);
    color: var(--text-primary);
    font-size: 14px;
    line-height: 1.5;
    overflow-x: hidden;
}

.product-design-container {
    width: 100%;
    max-width: 100%;
    min-height: calc(100vh - 40px);
    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;
    display: flex;
    width: 100%;
}

.controls-panel {
    width: 340px; /* Slightly wider for more controls */
    flex-shrink: 0;
    background-color: var(--controls-bg);
    padding: 20px;
    border-right: 1px solid var(--border-color);
    overflow-y: auto;
    box-sizing: border-box;
    max-height: calc(100vh - 100px); /* 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: 25px; /* Slightly less margin */
}

.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: 3px; /* Less space */
}
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-right: 5px; /* Space before unit */
    font-size: 0.9em;
    color: var(--text-secondary);
    min-width: 30px; /* Align values a bit */
    text-align: right;
}

/* Style the unit text after span */
.parameter-section > span + span {
     font-size: 0.85em;
     color: var(--text-secondary);
     margin-left: -2px; /* Pull unit closer */
     margin-bottom: 15px;
     display: inline-block;
}
.parameter-section label + input + span + span {
     margin-bottom: 15px; /* Add bottom margin after unit */
}

select {
    /* ... (same as previous component) ... */
    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;
    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;
    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 */
#analyzeToleranceButton, #simulateMoldButton {
    background-color: #5856d6; /* Purple accent */
    transition: background-color 0.2s ease;
}
#analyzeToleranceButton:hover, #simulateMoldButton:hover {
    background-color: #4341a0;
    box-shadow: 0 2px 5px rgba(88, 86, 214, 0.2);;
}

#resetViewButton {
    background-color: #6c757d;
}
#resetViewButton: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;
}

.visualization-area {
    flex-grow: 1;
    position: relative;
    background-color: var(--secondary-bg);
    overflow: hidden;
}

#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;
    }

    .controls-panel {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid var(--border-color);
        max-height: 60vh; /* Allow a bit more height for controls */
    }

    .visualization-area {
       height: 40vh;
       min-height: 250px;
    }

    .app-header h1 {
        font-size: 1.5em;
    }
} 

script.js

// script.js - Parametric 3D Product Design Component

document.addEventListener('DOMContentLoaded', () => {
    // --- DOM Elements ---
    const rendererContainer = document.getElementById('rendererContainer');
    const partWidthSlider = document.getElementById('partWidth');
    const partHeightSlider = document.getElementById('partHeight');
    const partDepthSlider = document.getElementById('partDepth');
    const bossDiameterSlider = document.getElementById('bossDiameter');
    const bossHeightSlider = document.getElementById('bossHeight');
    const bossPosXSlider = document.getElementById('bossPosX');
    const bossPosZSlider = document.getElementById('bossPosZ');
    const dimensionToleranceSlider = document.getElementById('dimensionTolerance');
    const positionToleranceSlider = document.getElementById('positionTolerance');

    const partWidthValueSpan = document.getElementById('partWidthValue');
    const partHeightValueSpan = document.getElementById('partHeightValue');
    const partDepthValueSpan = document.getElementById('partDepthValue');
    const bossDiameterValueSpan = document.getElementById('bossDiameterValue');
    const bossHeightValueSpan = document.getElementById('bossHeightValue');
    const bossPosXValueSpan = document.getElementById('bossPosXValue');
    const bossPosZValueSpan = document.getElementById('bossPosZValue');
    const dimensionToleranceValueSpan = document.getElementById('dimensionToleranceValue');
    const positionToleranceValueSpan = document.getElementById('positionToleranceValue');

    const analyzeToleranceButton = document.getElementById('analyzeToleranceButton');
    const simulateMoldButton = document.getElementById('simulateMoldButton');
    const resetViewButton = document.getElementById('resetViewButton');
    const statusText = document.getElementById('statusText');

    // --- Three.js Setup ---
    let scene, camera, renderer, controls, partGroup, baseMesh, bossMesh;
    let toleranceVizGroup, dimTolMeshMin, dimTolMeshMax, posTolViz;
    let moldHalfTop, moldHalfBottom; // For mold simulation
    let material, toleranceMaterialMin, toleranceMaterialMax, positionToleranceMaterial;
    let isToleranceVisible = false;
    let isMoldOpen = false;
    let isAnimating = false;

    // Conversion factor (e.g., if sliders are mm, and Three.js unit is meters)
    // Let's work directly in 'mm' like units in Three.js for simplicity here.
    const scaleFactor = 1;

    function initThreeJS() {
        // Scene
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xf5f5f7);

        // Camera
        const aspect = rendererContainer.clientWidth / rendererContainer.clientHeight;
        camera = new THREE.PerspectiveCamera(50, aspect, 1, 2000); // Adjusted near/far
        camera.position.set(100, 80, 150); // Adjusted for 'mm' scale
        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 keyLight = new THREE.DirectionalLight(0xffffff, 0.6);
        keyLight.position.set(-50, 80, 50);
        scene.add(keyLight);
        const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
        fillLight.position.set(50, 40, -30);
        scene.add(fillLight);

        // Controls
        controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.1;

        // Materials
        material = new THREE.MeshStandardMaterial({
            color: 0x99999f, // Lighter gray plastic
            metalness: 0.1,
            roughness: 0.6,
            polygonOffset: true, // Helps prevent z-fighting with tolerance viz
            polygonOffsetFactor: 1,
            polygonOffsetUnits: 1
        });

        // Tolerance Materials (Semi-transparent)
        toleranceMaterialMin = new THREE.MeshBasicMaterial({
            color: 0xff0000,
            transparent: true,
            opacity: 0.2,
            side: THREE.DoubleSide,
            depthWrite: false // Render after main object
        });
        toleranceMaterialMax = new THREE.MeshBasicMaterial({
            color: 0x00ff00,
            transparent: true,
            opacity: 0.2,
            side: THREE.DoubleSide,
            depthWrite: false
        });
         positionToleranceMaterial = new THREE.MeshBasicMaterial({
            color: 0x0000ff,
            transparent: true,
            opacity: 0.3,
            side: THREE.DoubleSide,
            depthWrite: false
        });

        // Part Group
        partGroup = new THREE.Group();
        scene.add(partGroup);

        // Tolerance Visualization Group
        toleranceVizGroup = new THREE.Group();
        toleranceVizGroup.visible = false;
        scene.add(toleranceVizGroup);

        // Initial Part Creation
        createPart();

        // Start animation loop
        animate();
    }

    // --- Parametric Part Generation ---
    function createPart() {
        // Clear previous part within the group
        while (partGroup.children.length > 0) {
            const child = partGroup.children[0];
            partGroup.remove(child);
            if (child.geometry) child.geometry.dispose();
        }

        // Get parameters
        const width = parseFloat(partWidthSlider.value) * scaleFactor;
        const height = parseFloat(partHeightSlider.value) * scaleFactor;
        const depth = parseFloat(partDepthSlider.value) * scaleFactor;
        const bossD = parseFloat(bossDiameterSlider.value) * scaleFactor;
        const bossH = parseFloat(bossHeightSlider.value) * scaleFactor;
        const bossPosXRatio = parseFloat(bossPosXSlider.value) / 100;
        const bossPosZRatio = parseFloat(bossPosZSlider.value) / 100;

        // Create Base Box
        const baseGeom = new THREE.BoxGeometry(width, height, depth);
        baseMesh = new THREE.Mesh(baseGeom, material);
        // Position base so its bottom is at y=0
        baseMesh.position.y = height / 2;
        partGroup.add(baseMesh);

        // Create Boss (Cylinder)
        const bossGeom = new THREE.CylinderGeometry(bossD / 2, bossD / 2, bossH, 32);
        bossMesh = new THREE.Mesh(bossGeom, material);

        // Position Boss on top surface of the base
        // Calculate position relative to the base center
        const bossX = (bossPosXRatio - 0.5) * width;
        const bossZ = (bossPosZRatio - 0.5) * depth;
        bossMesh.position.set(bossX, height + bossH / 2, bossZ); // Y is base height + half boss height
        bossMesh.rotation.x = 0; // Ensure cylinder is upright
        partGroup.add(bossMesh);

        // --- Update Tolerance Visualizations ---
        // (Do this separately, only when needed, to avoid recreating on every param change)
        if (isToleranceVisible) {
             createToleranceVisualization(); // Recreate tolerance based on new nominal part
        }

        updateStatus("零件模型已更新。");
    }

    // --- Tolerance Visualization ---
    function createToleranceVisualization() {
        // Clear previous visualization
        while (toleranceVizGroup.children.length > 0) {
            const child = toleranceVizGroup.children[0];
            toleranceVizGroup.remove(child);
            if (child.geometry) child.geometry.dispose();
            // Dispose materials if specific to tolerance viz? No, using shared ones.
        }

        const dimTol = parseFloat(dimensionToleranceSlider.value) * scaleFactor;
        const posTol = parseFloat(positionToleranceSlider.value) * scaleFactor;

        // Get current nominal dimensions
        const width = parseFloat(partWidthSlider.value) * scaleFactor;
        const height = parseFloat(partHeightSlider.value) * scaleFactor;
        const depth = parseFloat(partDepthSlider.value) * scaleFactor;
        const bossD = parseFloat(bossDiameterSlider.value) * scaleFactor;
        const bossH = parseFloat(bossHeightSlider.value) * scaleFactor;
        const bossX = (parseFloat(bossPosXSlider.value)/100 - 0.5) * width;
        const bossZ = (parseFloat(bossPosZSlider.value)/100 - 0.5) * depth;
        const bossNominalY = height + bossH / 2;

        // 1. Dimension Tolerance (Min/Max Boxes for Base) - Conceptual
        const baseMinGeom = new THREE.BoxGeometry(width - 2 * dimTol, height - 2 * dimTol, depth - 2 * dimTol);
        dimTolMeshMin = new THREE.Mesh(baseMinGeom, toleranceMaterialMin);
        dimTolMeshMin.position.y = (height - 2 * dimTol) / 2; // Adjust position for new height
        toleranceVizGroup.add(dimTolMeshMin);

        const baseMaxGeom = new THREE.BoxGeometry(width + 2 * dimTol, height + 2 * dimTol, depth + 2 * dimTol);
        dimTolMeshMax = new THREE.Mesh(baseMaxGeom, toleranceMaterialMax);
        dimTolMeshMax.position.y = (height + 2 * dimTol) / 2;
        toleranceVizGroup.add(dimTolMeshMax);

        // 2. Position Tolerance (Cylindrical Zone for Boss Centerline) - Conceptual
        // Create a thin cylinder representing the tolerance zone diameter
        const posTolGeom = new THREE.CylinderGeometry(posTol, posTol, height + bossH + dimTol * 2, 32); // Height spans base + boss + tolerance
        posTolViz = new THREE.Mesh(posTolGeom, positionToleranceMaterial);
        // Position it at the nominal boss X, Z, centered vertically within its height
        posTolViz.position.set(bossX, (height + bossH + dimTol*2) / 2, bossZ);
        toleranceVizGroup.add(posTolViz);

        updateStatus("公差范围已更新。");
    }

    // --- Animation & Rendering Loop ---
    function animate() {
        requestAnimationFrame(animate);
        controls.update();
        renderer.render(scene, camera);
    }

    // --- Event Listeners ---
    function setupEventListeners() {
        // Parameter Sliders
        const sliders = [partWidthSlider, partHeightSlider, partDepthSlider,
                         bossDiameterSlider, bossHeightSlider, bossPosXSlider, bossPosZSlider,
                         dimensionToleranceSlider, positionToleranceSlider];
        sliders.forEach(slider => {
            slider.addEventListener('input', handleSliderInput);
            slider.addEventListener('change', handleSliderChange); // Update part only on release
        });

        // Action Buttons
        analyzeToleranceButton.addEventListener('click', toggleToleranceAnalysis);
        simulateMoldButton.addEventListener('click', simulateMoldOpening);
        resetViewButton.addEventListener('click', resetCameraView);

        // Window Resize
        window.addEventListener('resize', onWindowResize);
    }

    function handleSliderInput(event) {
        const sliderId = event.target.id;
        const valueSpan = document.getElementById(sliderId + 'Value');
        let value = parseFloat(event.target.value);
        let unit = '';

        // Determine unit and formatting
        if (sliderId.includes('Tolerance')) {
            valueSpan.textContent = value.toFixed(2);
            unit = 'mm';
        } else if (sliderId.includes('Pos')) {
             valueSpan.textContent = value.toFixed(0);
             unit = '%';
        } else if (sliderId.includes('Diameter') || sliderId.includes('bossHeight')) {
            valueSpan.textContent = value.toFixed(1);
            unit = 'mm';
        } else {
            valueSpan.textContent = value.toFixed(0);
            unit = 'mm';
        }

        // Find the next sibling span (if exists) to update unit? No, hardcoded in HTML.

        // Live update for tolerance sliders if tolerance is visible
        if (isToleranceVisible && (sliderId.includes('Tolerance') || sliderId.includes('Pos'))) {
           createToleranceVisualization();
        }
    }

    function handleSliderChange(event) {
         // Recreate the main part geometry only when slider drag finishes
         if (!isAnimating && !event.target.id.includes('Tolerance') && !event.target.id.includes('Pos')) {
             createPart();
         } else if (!isAnimating && (event.target.id.includes('Pos'))) {
             // If only position changed, update part and tolerance if visible
             createPart();
         }
         // Tolerance slider changes already handled live in input if visible
    }


    // --- Button Actions ---
    function toggleToleranceAnalysis() {
        isToleranceVisible = !isToleranceVisible;
        if (isToleranceVisible) {
            createToleranceVisualization();
            toleranceVizGroup.visible = true;
            analyzeToleranceButton.textContent = "隐藏公差范围";
            updateStatus("显示公差范围 (概念性)。");
        } else {
            toleranceVizGroup.visible = false;
            analyzeToleranceButton.textContent = "显示公差范围";
            updateStatus("公差范围已隐藏。");
        }
    }

    function simulateMoldOpening() {
        if (isAnimating) return;
        isAnimating = true;
        simulateMoldButton.disabled = true;
        resetViewButton.disabled = true;
        analyzeToleranceButton.disabled = true;

        // Simple simulation: create two halves and move them apart
        if (!moldHalfTop || !moldHalfBottom) {
            // Create conceptual mold halves (simple boxes)
            const width = parseFloat(partWidthSlider.value) * scaleFactor + 20; // Mold bigger than part
            const height = parseFloat(partHeightSlider.value) * scaleFactor / 2 + 20;
            const depth = parseFloat(partDepthSlider.value) * scaleFactor + 20;
            const moldMaterial = new THREE.MeshStandardMaterial({ color: 0x555555, metalness: 0.8, roughness: 0.5 });

            const moldGeom = new THREE.BoxGeometry(width, height, depth);
            moldHalfBottom = new THREE.Mesh(moldGeom, moldMaterial);
            moldHalfTop = new THREE.Mesh(moldGeom, moldMaterial);

            // Position halves relative to the part's center plane (y=height/2)
            const partHeight = parseFloat(partHeightSlider.value) * scaleFactor;
            moldHalfBottom.position.y = partHeight/2 - height/2; // Center of bottom mold half at part center plane - half mold height
            moldHalfTop.position.y = partHeight/2 + height/2;    // Center of top mold half at part center plane + half mold height

            scene.add(moldHalfBottom);
            scene.add(moldHalfTop);
            partGroup.visible = false; // Hide original part
        }

        const targetSeparation = parseFloat(partHeightSlider.value) * scaleFactor * 1.5;
        const startYTop = moldHalfTop.position.y;
        const startYBottom = moldHalfBottom.position.y;
        const targetYTop = startYTop + targetSeparation / 2;
        const targetYBottom = startYBottom - targetSeparation / 2;
        const duration = 1000; // ms
        let startTime = null;

        function moldStep(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);

            moldHalfTop.position.y = startYTop + (targetYTop - startYTop) * easedProgress;
            moldHalfBottom.position.y = startYBottom + (targetYBottom - startYBottom) * easedProgress;

            if (progress < 1) {
                requestAnimationFrame(moldStep);
            } else {
                 updateStatus("模拟开模完成。再次点击可复位。");
                 isAnimating = false;
                 simulateMoldButton.disabled = false;
                 resetViewButton.disabled = false;
                 analyzeToleranceButton.disabled = false;
                 isMoldOpen = true;
                 simulateMoldButton.textContent = "复位模具";
            }
        }

        function resetMold() {
             // Animation to close the mold
             isAnimating = true;
             simulateMoldButton.disabled = true;
             resetViewButton.disabled = true;
             analyzeToleranceButton.disabled = true;

             const currentYTop = moldHalfTop.position.y;
             const currentYBottom = moldHalfBottom.position.y;
             // Calculate original positions correctly based on part height and mold height
             const partHeight = parseFloat(partHeightSlider.value) * scaleFactor;
             const moldHeight = parseFloat(partHeightSlider.value) * scaleFactor / 2 + 20;
             const originalYTop = partHeight / 2 + moldHeight / 2;
             const originalYBottom = partHeight / 2 - moldHeight / 2;
             const durationClose = 800;
             let startTimeClose = null;

             function closeStep(timestamp) {
                 if (!startTimeClose) startTimeClose = timestamp;
                 const elapsed = timestamp - startTimeClose;
                 const progress = Math.min(elapsed / durationClose, 1);
                 const easedProgress = 0.5 - 0.5 * Math.cos(progress * Math.PI);

                 moldHalfTop.position.y = currentYTop + (originalYTop - currentYTop) * easedProgress;
                 moldHalfBottom.position.y = currentYBottom + (originalYBottom - currentYBottom) * easedProgress;

                 if (progress < 1) {
                     requestAnimationFrame(closeStep);
                 } else {
                     scene.remove(moldHalfTop);
                     scene.remove(moldHalfBottom);
                     moldHalfTop = null;
                     moldHalfBottom = null;
                     partGroup.visible = true; // Show part again
                     updateStatus("模具已复位。");
                     isAnimating = false;
                     simulateMoldButton.disabled = false;
                     resetViewButton.disabled = false;
                     analyzeToleranceButton.disabled = false;
                     isMoldOpen = false;
                     simulateMoldButton.textContent = "模拟开模 (概念)";
                 }
             }
             requestAnimationFrame(closeStep);
        }

        if (isMoldOpen) {
            resetMold();
        } else {
             updateStatus("模拟开模过程...");
             requestAnimationFrame(moldStep);
        }
    }

    function resetCameraView() {
        controls.reset();
        // Adjust position based on current part size? Or fixed reset?
        const currentHeight = parseFloat(partHeightSlider.value) * scaleFactor;
        camera.position.set(100, 80 + currentHeight/2, 150);
        camera.lookAt(0, currentHeight / 2, 0); // Look at center of base
        controls.update();
        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 = `<p style='color: red; padding: 20px;'>无法加载3D视图。错误: ${error.message}</p>`;
    }
}); 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2373884.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenAI 30 亿收购 Windsurf:AI 编程助手风口已至

导语: 各位开发者同仁、产品经理伙伴们,从2024年起,一场由AI驱动的研发范式革命已然来临。Cursor等AI代码编辑器凭借与大语言模型的深度集成,正以前所未有的态势挑战,甚至颠覆着IntelliJ、VS Code等传统IDE的固有疆域。根据OpenRouter的API使用数据,Anthropic的Claude 3.…

【linux】倒计时小程序、进度条小程序及其puls版本

小编个人主页详情<—请点击 小编个人gitee代码仓库<—请点击 linux系列专栏<—请点击 倘若命中无此运&#xff0c;孤身亦可登昆仑&#xff0c;送给屏幕面前的读者朋友们和小编自己! 目录 前言一、知识铺垫1. 回车换行2. 缓冲区 二、倒计时小程序1. 实现 三、进度条小…

物流无人机结构与载货设计分析!

一、物流无人机的结构与载货设计模块运行方式 1.结构设计特点 垂直起降与固定翼结合&#xff1a;针对复杂地形&#xff08;如山区、城市&#xff09;需求&#xff0c;采用垂直起降&#xff08;VTOL&#xff09;与固定翼结合的复合布局&#xff0c;例如“天马”H型无人机&am…

【MySQL】表空间结构 - 从何为表空间到段页详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

[特殊字符] 免税商品优选购物商城系统 | Java + SpringBoot + Vue | 前后端分离实战项目分享

一、项目简介 本项目为一款功能完备的 免税商品优选购物商城系统&#xff0c;采用 Java 后端 Vue 前端的主流前后端分离架构&#xff0c;支持用户、商家、管理员三类角色&#xff0c;满足商品浏览、下单、商家管理、后台运营等多项功能&#xff0c;适用于实际部署或作为毕业设…

图像处理基础与图像变换

一、目的 通过本次实验&#xff0c;加深对数字图像的理解&#xff0c;熟悉MATLAB中的有关函数&#xff1b;应用DCT对图像进行变换&#xff1b;熟悉图像常见的统计指标&#xff0c;实现图像几何变换的基本方法。 二、内容与设计思想 1、实验内容&#xff1a;选择两幅图像&…

并发笔记-锁(一)

文章目录 1. 基本问题与锁的概念 (The Basic Idea)2. 锁的API与Pthreads (Lock API and Pthreads)3. 构建锁的挑战与评估标准 (Building A Lock & Evaluating Locks)4. 早期/简单的锁实现尝试及其问题 (Early/Simple Attempts)4.1 控制中断 (Controlling Interrupts)4.2 仅…

【Bootstrap V4系列】学习入门教程之 组件-媒体对象(Media object)

Bootstrap V4系列 学习入门教程之 组件-媒体对象&#xff08;Media object&#xff09; 媒体对象&#xff08;Media object&#xff09;一、Example二、Nesting 嵌套三、Alignment 对齐四、Order 顺序五、Media list 媒体列表 媒体对象&#xff08;Media object&#xff09; B…

2025数字中国创新大赛-数字安全赛道数据安全产业积分争夺赛决赛Writeup

文章目录 综合场景赛-模型环境安全-3综合场景赛-数据识别与审计-1综合场景赛-数据识别与审计-2综合场景赛-数据识别与审计-3 有需要题目附件的师傅&#xff0c;可以联系我发送 综合场景赛-模型环境安全-3 upload文件嵌套了多个png图片字节数据&#xff0c;使用foremost直接分离…

无法更新Google Chrome的解决问题

解决问题&#xff1a;原文链接&#xff1a;【百分百成功】Window 10 Google Chrome无法启动更新检查&#xff08;错误代码为1&#xff1a;0x80004005&#xff09; google谷歌chrome浏览器无法更新Chrome无法更新至最新版本&#xff1f; 下载了 就是更新Google Chrome了

数字孪生市场格局生变:中国2025年规模214亿,工业制造领域占比超40%

一、技术深度解析&#xff1a;数字孪生的核心技术栈与演进 1. 从镜像到自治&#xff1a;数字孪生技术架构跃迁 三维重建突破&#xff1a;LiDAR点云精度达2cm&#xff0c;无人机测深刷新频率5Hz&#xff0c;支撑杭州城市大脑内涝预警模型提前6小时预测。AI算法融合&#xff1a…

全球首款无限时长电影生成模型SkyReels-V2本地部署教程:视频时长无限制!

一、简介 SkyReels-V2 模型集成了多模态大语言模型&#xff08;MLLM&#xff09;、多阶段预训练、强化学习以及创新的扩散强迫&#xff08;Diffusion-forcing&#xff09;框架&#xff0c;实现了在提示词遵循、视觉质量、运动动态以及视频时长等方面的全面突破。通过扩散强迫框…

颠覆性技术革命:CAD DWG图形瓦片化实战指南

摘要 CAD DWG图形瓦片化技术通过金字塔模型构建多分辨率地图体系&#xff0c;实现海量工程图纸的Web高效可视化。本文系统解析栅格瓦片与矢量瓦片的技术原理&#xff0c;详细对比两者在生成效率、样式自由度和客户端性能等维度的差异&#xff0c;并结合工程建设、工业设计和智…

不换设备秒通信,PROFINET转Ethercat网关混合生产线集成配置详解

在汽车制造中&#xff0c;连接Profinet控制的PLC&#xff08;如西门子S7&#xff09;与EtherCAT伺服驱动器&#xff08;如倍福AX5000&#xff09;&#xff0c;实现运动控制同步。 在汽车制造的混合生产线集成中&#xff0c;实现西门子S7 PLC与倍福AX5000 EtherCAT伺服驱动器的…

c++STL-string的使用

这里写自定义目录标题 string的使用string写成类模板的原因string的版本举例构造、析构函数和赋值重载构造函数和析构函数operator Iterators迭代器begin和endrbegin和rendcbegin和cend&#xff0c;crbegin和crend&#xff08;c11&#xff09; capacity容量有关函数不同编译器下…

UNet网络 图像分割模型学习

UNet 由Ronneberger等人于2015年提出&#xff0c;专门针对医学图像分割任务&#xff0c;解决了早期卷积网络在小样本数据下的效率问题和细节丢失难题。 一 核心创新 1.1对称编码器-解码器结构 实现上下文信息与高分辨率细节的双向融合 如图所示&#xff1a;编码器进行了4步&…

使用 SHAP 进行特征交互检测:揭示变量之间的复杂依赖关系

我们将探讨如何使用 SHAP&#xff08;SHapley 加法解释&#xff09;来检测和可视化机器学习模型中的特征交互。了解特征组合如何影响模型预测对于构建更透明、更准确的模型至关重要。SHAP 有助于揭示这些复杂的依赖关系&#xff0c;并使从业者能够以更有意义的方式解释模型决策…

Python-MCPInspector调试

Python-MCPInspector调试 使用FastMCP开发MCPServer&#xff0c;熟悉【McpServer编码过程】【MCPInspector调试方法】-> 可以这样理解&#xff1a;只编写一个McpServer&#xff0c;然后使用MCPInspector作为McpClient进行McpServer的调试 1-核心知识点 1-熟悉【McpServer编…

Java设计模式-策略模式(行为型)

策略模式详解 一、策略模式概述 1.1 基本概念 策略模式是一种行为型设计模式&#xff0c;它主要用于处理算法的不同变体。其核心思想是将算法的定义与使用分离开来&#xff0c;把一系列具体的算法封装成独立的策略类&#xff0c;这些策略类实现相同的策略接口。客户端可以在…

html body 设置heigth 100%,body内元素设置margin-top出滚动条(margin 重叠问题)

今天在用移动端的时候发现个问题&#xff0c;html,body 设置 height&#xff1a;100% 会出现纵向滚动条 <!DOCTYPE html> <html> <head> <title>html5</title> <style> html, body {height: 100%; } * {margin: 0;padding: 0; } </sty…