// 伺服驱动选型工具 - 核心计算逻辑 class ServoSizer { constructor() { this.currentType = 'ballscrew'; this.chart = null; // 确保在 DOM 完全加载后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { this.init(); }); } else { this.init(); } } init() { // 检查必要的 DOM 元素是否存在 const requiredElements = [ 'transmissionType', 'dynamicParameters', 'calculateBtn', 'resetBtn', 'exportBtn', 'resultsDisplay' ]; for (const elementId of requiredElements) { if (!document.getElementById(elementId)) { console.error(`Required element #${elementId} not found`); return; } } this.bindEvents(); this.updateParametersForm(); this.loadDefaults(); } bindEvents() { // 传动类型切换 const transmissionSelect = document.getElementById('transmissionType'); if (transmissionSelect) { transmissionSelect.addEventListener('change', (e) => { this.currentType = e.target.value; this.updateParametersForm(); this.clearResults(); }); } // 计算按钮 const calculateBtn = document.getElementById('calculateBtn'); if (calculateBtn) { calculateBtn.addEventListener('click', () => { this.calculate(); }); } // 重置按钮 const resetBtn = document.getElementById('resetBtn'); if (resetBtn) { resetBtn.addEventListener('click', () => { this.resetForm(); }); } // 导出按钮 const exportBtn = document.getElementById('exportBtn'); if (exportBtn) { exportBtn.addEventListener('click', () => { this.exportResults(); }); } // 保存配置按钮 const saveConfigBtn = document.getElementById('saveConfigBtn'); if (saveConfigBtn) { saveConfigBtn.addEventListener('click', () => { this.saveConfiguration(); }); } // 加载配置按钮 const loadConfigBtn = document.getElementById('loadConfigBtn'); if (loadConfigBtn) { loadConfigBtn.addEventListener('click', () => { this.loadConfiguration(); }); } } validateInput(input) { const value = parseFloat(input.value); const min = parseFloat(input.min) || -Infinity; const max = parseFloat(input.max) || Infinity; const step = parseFloat(input.step) || 1; // 检查是否为空值 if (input.value.trim() === '') { input.classList.add('error'); this.showValidationMessage(input, '此字段为必填项'); return false; } // 检查数值有效性 if (isNaN(value)) { input.classList.add('error'); this.showValidationMessage(input, '请输入有效数字'); return false; } // 检查范围 if (value < min || value > max) { input.classList.add('error'); this.showValidationMessage(input, `值应在 ${min} 到 ${max} 之间`); return false; } // 检查步长(可选) if (step > 0 && Math.abs((value - min) % step) > 1e-10) { // 对于小数步长,允许一定的精度误差 const roundedValue = Math.round(value / step) * step; if (Math.abs(value - roundedValue) > 1e-10) { input.classList.add('error'); this.showValidationMessage(input, `值应为 ${step} 的倍数`); return false; } } input.classList.remove('error'); this.hideValidationMessage(input); return true; } showValidationMessage(input, message) { // 移除现有的验证消息 this.hideValidationMessage(input); // 创建验证消息元素 const messageEl = document.createElement('div'); messageEl.className = 'validation-message'; messageEl.textContent = message; messageEl.style.cssText = ` color: #e74c3c; font-size: 12px; margin-top: 5px; display: block; `; // 插入到输入框后面 input.parentNode.insertBefore(messageEl, input.nextSibling); } hideValidationMessage(input) { const existingMessage = input.parentNode.querySelector('.validation-message'); if (existingMessage) { existingMessage.remove(); } } loadDefaults() { // 默认值已经在HTML中设置 } // 获取特定传动类型的参数表单 getParameterForm(type) { const forms = { ballscrew: `

丝杠参数

`, timingBelt: `

同步带参数

`, gearbox: `

减速机参数

`, rotaryTable: `

转盘参数

`, winder: `

收放卷参数

`, directDrive: `

直接驱动参数

`, combined: `

组合传动参数

` }; return forms[type] || forms.ballscrew; } updateParametersForm() { const dynamicParams = document.getElementById('dynamicParameters'); if (dynamicParams) { dynamicParams.innerHTML = this.getParameterForm(this.currentType); // 绑定输入验证 document.querySelectorAll('#dynamicParameters input[type="number"]').forEach(input => { input.addEventListener('input', () => { this.validateInput(input); }); }); } } // 安全的数值计算函数 safeNumber(value, defaultValue = 0) { const num = parseFloat(value); return isNaN(num) || !isFinite(num) ? defaultValue : num; } // 丝杠计算 calculateBallScrew(params) { const { loadMass, screwDiameter, screwLead, frictionCoeff, efficiency, maxSpeed, accelerationTime, travelDistance } = params; // 转换为标准单位 const lead = this.safeNumber(screwLead, 5) / 1000; // mm to m const diameter = this.safeNumber(screwDiameter, 20) / 1000; // mm to m const travel = this.safeNumber(travelDistance, 500) / 1000; // mm to m const mass = this.safeNumber(loadMass, 10); const speed = this.safeNumber(maxSpeed, 100); const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001); // 避免除零 const friction = this.safeNumber(frictionCoeff, 0.01); const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); // 避免除零 // 1. 负载转矩计算 const gravity = 9.81; const frictionForce = mass * gravity * friction; const frictionTorque = (frictionForce * diameter) / 2; // 推力转矩 const thrustTorque = (mass * gravity * lead) / (2 * Math.PI); const totalTorque = (thrustTorque + frictionTorque) / eff; // 2. 转速计算 const maxRpm = (speed * 60) / (lead * 1000); // mm/s to rpm // 3. 惯量计算 // 丝杠惯量 (实心圆柱体) const screwInertia = (Math.PI * 7800 * Math.pow(diameter, 4) * travel) / 32; // 负载折算到电机轴的惯量 const loadInertia = mass * Math.pow(lead / (2 * Math.PI), 2); const totalInertia = Math.max(screwInertia + loadInertia, 1e-6); // 避免零惯量 // 4. 加速度转矩 const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime; const accelTorque = totalInertia * angularAccel; // 5. 峰值转矩 const peakTorque = totalTorque + accelTorque; // 6. 功率计算 const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60; return { speedRange: { min: 0, max: Math.max(maxRpm, 0) }, torque: { continuous: Math.max(totalTorque, 0), peak: Math.max(peakTorque, 0) }, inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 }, power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 } }; } // 同步带计算 calculateTimingBelt(params) { const { loadMass, pulleyDiameter, frictionCoeff, efficiency, maxSpeed, accelerationTime } = params; const radius = this.safeNumber(pulleyDiameter, 50) / 2000; // mm to m const mass = this.safeNumber(loadMass, 5); const speed = this.safeNumber(maxSpeed, 200); const accelTime = Math.max(this.safeNumber(accelerationTime, 0.3), 0.001); const friction = this.safeNumber(frictionCoeff, 0.01); const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); // 负载转矩 const gravity = 9.81; const frictionForce = mass * gravity * friction; const loadTorque = (mass * gravity * radius + frictionForce * radius) / eff; // 转速 const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(pulleyDiameter, 50)); // 惯量计算 - 更精确的模型 // 滑轮惯量 (实心圆柱) - 假设滑轮材料为铝(密度2700 kg/m³),厚度5mm const pulleyThickness = 0.005; // 5mm const pulleyDensity = 2700; // 铝的密度 kg/m³ const pulleyVolume = Math.PI * Math.pow(radius, 2) * pulleyThickness; const pulleyMass = pulleyDensity * pulleyVolume; const pulleyInertia = 0.5 * pulleyMass * Math.pow(radius, 2); // 负载惯量 const loadInertia = mass * Math.pow(radius, 2); const totalInertia = Math.max(pulleyInertia + loadInertia, 1e-6); // 加速度转矩 const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime; const accelTorque = totalInertia * angularAccel; const peakTorque = loadTorque + accelTorque; const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60; return { speedRange: { min: 0, max: Math.max(maxRpm, 0) }, torque: { continuous: Math.max(loadTorque, 0), peak: Math.max(peakTorque, 0) }, inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 }, power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 } }; } // 减速机计算 calculateGearbox(params) { const { loadInertia, loadTorque, gearRatio, efficiency, maxSpeed, accelerationTime } = params; const inertia = this.safeNumber(loadInertia, 0.01); const torque = this.safeNumber(loadTorque, 5); const ratio = Math.max(this.safeNumber(gearRatio, 10), 0.01); const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); const speed = this.safeNumber(maxSpeed, 100); const accelTime = Math.max(this.safeNumber(accelerationTime, 0.2), 0.001); // 折算到电机侧 const reflectedInertia = inertia / Math.pow(ratio, 2); const reflectedTorque = torque / (ratio * eff); // 电机转速 const motorRpm = speed * ratio; // 加速度转矩 const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime; const accelTorque = Math.max(reflectedInertia, 1e-6) * angularAccel; const peakTorque = reflectedTorque + accelTorque; const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60; return { speedRange: { min: 0, max: Math.max(motorRpm, 0) }, torque: { continuous: Math.max(reflectedTorque, 0), peak: Math.max(peakTorque, 0) }, inertia: { motor: 0.001, load: Math.max(reflectedInertia, 1e-6), ratio: Math.max(reflectedInertia, 1e-6) / 0.001 }, power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 } }; } // 转盘计算 calculateRotaryTable(params) { const { tableMass, tableRadius, frictionCoeff, efficiency, maxSpeed, accelerationTime } = params; const mass = this.safeNumber(tableMass, 20); const radius = this.safeNumber(tableRadius, 150) / 1000; // mm to m const friction = this.safeNumber(frictionCoeff, 0.01); const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); const speed = this.safeNumber(maxSpeed, 60); const accelTime = Math.max(this.safeNumber(accelerationTime, 1.0), 0.001); // 转盘惯量 (实心圆盘) const tableInertia = 0.5 * mass * Math.pow(radius, 2); // 摩擦转矩 const gravity = 9.81; const frictionTorque = mass * gravity * friction * radius; // 转速 const maxRpm = speed; // 加速度转矩 const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime; const accelTorque = Math.max(tableInertia, 1e-6) * angularAccel; const peakTorque = (frictionTorque + accelTorque) / eff; const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60; return { speedRange: { min: 0, max: Math.max(maxRpm, 0) }, torque: { continuous: Math.max(frictionTorque / eff, 0), peak: Math.max(peakTorque, 0) }, inertia: { motor: 0.001, load: Math.max(tableInertia, 1e-6), ratio: Math.max(tableInertia, 1e-6) / 0.001 }, power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 } }; } // 收放卷计算 calculateWinder(params) { const { materialDensity, materialWidth, initialDiameter, finalDiameter, lineSpeed, tension, accelerationTime } = params; const density = this.safeNumber(materialDensity, 7800); const width = this.safeNumber(materialWidth, 100) / 1000; const initialRadius = this.safeNumber(initialDiameter, 50) / 2000; // mm to m const finalRadius = this.safeNumber(finalDiameter, 200) / 2000; const speed = this.safeNumber(lineSpeed, 100); const tensionVal = this.safeNumber(tension, 50); const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001); // 卷筒惯量 (空心圆柱) const finalMass = density * Math.PI * (Math.pow(finalRadius, 2) - Math.pow(initialRadius, 2)) * width; const finalInertia = 0.5 * Math.max(finalMass, 0.001) * (Math.pow(finalRadius, 2) + Math.pow(initialRadius, 2)); // 张力转矩 const tensionTorque = tensionVal * finalRadius; // 转速范围 const minRpm = (speed * 60) / (Math.PI * this.safeNumber(finalDiameter, 200)); const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(initialDiameter, 50)); // 加速度转矩 (使用最大惯量) const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime; const accelTorque = Math.max(finalInertia, 1e-6) * angularAccel; const peakTorque = tensionTorque + accelTorque; const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60; return { speedRange: { min: Math.max(minRpm, 0), max: Math.max(maxRpm, 0) }, torque: { continuous: Math.max(tensionTorque, 0), peak: Math.max(peakTorque, 0) }, inertia: { motor: 0.001, load: Math.max(finalInertia, 1e-6), ratio: Math.max(finalInertia, 1e-6) / 0.001 }, power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 } }; } // 直接驱动计算 calculateDirectDrive(params) { const { loadInertia, frictionTorque, maxSpeed, accelerationTime } = params; const inertia = this.safeNumber(loadInertia, 0.005); const friction = this.safeNumber(frictionTorque, 0.1); const speed = this.safeNumber(maxSpeed, 300); const accelTime = Math.max(this.safeNumber(accelerationTime, 0.1), 0.001); // 直接驱动,无传动比 const motorRpm = speed; // 加速度转矩 const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime; const accelTorque = Math.max(inertia, 1e-6) * angularAccel; const peakTorque = friction + accelTorque; const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60; return { speedRange: { min: 0, max: Math.max(motorRpm, 0) }, torque: { continuous: Math.max(friction, 0), peak: Math.max(peakTorque, 0) }, inertia: { motor: 0.001, load: Math.max(inertia, 1e-6), ratio: Math.max(inertia, 1e-6) / 0.001 }, power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 } }; } // 组合传动计算(简化版) calculateCombined(params) { const { combinedLoadMass, maxSpeed, accelerationTime, frictionCoeff, efficiency } = params; const mass = this.safeNumber(combinedLoadMass, 15); const speed = this.safeNumber(maxSpeed, 80); const accelTime = Math.max(this.safeNumber(accelerationTime, 0.4), 0.001); const friction = this.safeNumber(frictionCoeff, 0.01); const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); const gravity = 9.81; const loadTorque = mass * gravity * 0.1; // 简化假设 const maxRpm = speed; const loadInertia = mass * 0.01; // 简化假设 const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime; const accelTorque = Math.max(loadInertia, 1e-6) * angularAccel; const continuousTorque = loadTorque / eff; const peakTorque = (loadTorque + accelTorque) / eff; const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60; return { speedRange: { min: 0, max: Math.max(maxRpm, 0) }, torque: { continuous: Math.max(continuousTorque, 0), peak: Math.max(peakTorque, 0) }, inertia: { motor: 0.001, load: Math.max(loadInertia, 1e-6), ratio: Math.max(loadInertia, 1e-6) / 0.001 }, power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 } }; } getFormValues() { const values = {}; // 获取所有输入值 document.querySelectorAll('#dynamicParameters input, #dynamicParameters select').forEach(input => { const id = input.id; if (input.type === 'number') { values[id] = this.safeNumber(input.value, 0); } else { values[id] = input.value; } }); // 获取通用参数 const safetyFactorEl = document.getElementById('safetyFactor'); const inertiaRatioEl = document.getElementById('inertiaRatio'); if (safetyFactorEl) values.safetyFactor = this.safeNumber(safetyFactorEl.value, 1.5); if (inertiaRatioEl) values.inertiaRatio = this.safeNumber(inertiaRatioEl.value, 10); return values; } calculate() { const params = this.getFormValues(); // 验证所有输入 let isValid = true; document.querySelectorAll('input[type="number"]').forEach(input => { if (!this.validateInput(input)) { isValid = false; } }); if (!isValid) { this.showAlert('请检查输入参数是否有效', 'error'); return; } let results; try { switch (this.currentType) { case 'ballscrew': results = this.calculateBallScrew(params); break; case 'timingBelt': results = this.calculateTimingBelt(params); break; case 'gearbox': results = this.calculateGearbox(params); break; case 'rotaryTable': results = this.calculateRotaryTable(params); break; case 'winder': results = this.calculateWinder(params); break; case 'directDrive': results = this.calculateDirectDrive(params); break; case 'combined': results = this.calculateCombined(params); break; default: throw new Error('未知的传动类型'); } // 应用安全系数 results.torque.continuous *= params.safetyFactor; results.torque.peak *= params.safetyFactor; results.power.rated *= params.safetyFactor; // 检查惯量比警告 const inertiaWarning = results.inertia.ratio > params.inertiaRatio; this.displayResults(results, inertiaWarning); this.updateChart(results); this.showAlert('计算完成!', 'success'); } catch (error) { console.error('计算错误:', error); this.showAlert('计算出错: ' + error.message, 'error'); } } displayResults(results, inertiaWarning) { const resultsDiv = document.getElementById('resultsDisplay'); const warningDiv = document.getElementById('warningMessage'); const errorDiv = document.getElementById('errorMessage'); if (!resultsDiv) return; // 隐藏错误消息 if (errorDiv) errorDiv.classList.add('hidden'); resultsDiv.innerHTML = `
转速范围 (RPM)
${results.speedRange.min.toFixed(1)} - ${results.speedRange.max.toFixed(1)}
连续转矩 (N·m)
${results.torque.continuous.toFixed(3)}
峰值转矩 (N·m)
${results.torque.peak.toFixed(3)}
负载惯量 (kg·m²)
${results.inertia.load.toExponential(3)}
惯量比
${results.inertia.ratio.toFixed(1)}
额定功率 (W)
${results.power.rated.toFixed(1)}
`; // 显示惯量比警告 if (warningDiv) { if (inertiaWarning) { warningDiv.textContent = `⚠️ 警告:惯量比 (${results.inertia.ratio.toFixed(1)}) 超过允许值,建议选择更大惯量的电机或添加减速机。`; warningDiv.classList.remove('hidden'); } else { warningDiv.classList.add('hidden'); } } const resultsCard = document.getElementById('resultsCard'); if (resultsCard) resultsCard.style.display = 'block'; } updateChart(results) { const chartCanvas = document.getElementById('performanceChart'); if (!chartCanvas) return; // 检查 Chart.js 是否可用 if (typeof Chart === 'undefined') { console.warn('Chart.js not loaded, skipping chart rendering'); return; } const ctx = chartCanvas.getContext('2d'); // 销毁现有图表 if (this.chart) { this.chart.destroy(); } // 创建新图表 this.chart = new Chart(ctx, { type: 'bar', data: { labels: ['连续转矩', '峰值转矩', '额定功率'], datasets: [{ label: '性能指标', data: [ results.torque.continuous, results.torque.peak, results.power.rated ], backgroundColor: [ 'rgba(54, 162, 235, 0.8)', 'rgba(255, 99, 132, 0.8)', 'rgba(75, 192, 192, 0.8)' ], borderColor: [ 'rgba(54, 162, 235, 1)', 'rgba(255, 99, 132, 1)', 'rgba(75, 192, 192, 1)' ], borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, title: { display: true, text: '数值' } } }, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(context) { let label = context.dataset.label || ''; if (label) { label += ': '; } if (context.parsed.y !== null) { label += context.parsed.y.toFixed(3); if (context.dataIndex === 2) { label += ' W'; } else { label += ' N·m'; } } return label; } } } } } }); } clearResults() { const resultsDiv = document.getElementById('resultsDisplay'); const warningDiv = document.getElementById('warningMessage'); const errorDiv = document.getElementById('errorMessage'); if (resultsDiv) { resultsDiv.innerHTML = '

请输入参数并点击计算获取结果

'; } if (warningDiv) warningDiv.classList.add('hidden'); if (errorDiv) errorDiv.classList.add('hidden'); // 销毁图表 if (this.chart) { this.chart.destroy(); this.chart = null; } } resetForm() { // 重置所有输入框 document.querySelectorAll('input[type="number"]').forEach(input => { input.value = input.defaultValue || ''; input.classList.remove('error'); }); // 重置选择框 const transmissionSelect = document.getElementById('transmissionType'); if (transmissionSelect) { transmissionSelect.selectedIndex = 0; this.currentType = 'ballscrew'; this.updateParametersForm(); } this.clearResults(); this.showAlert('表单已重置', 'info'); } exportResults() { const resultsDiv = document.getElementById('resultsDisplay'); if (!resultsDiv || resultsDiv.querySelector('.placeholder')) { this.showAlert('请先进行计算', 'warning'); return; } const resultsText = resultsDiv.innerText; const blob = new Blob([resultsText], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `servo_sizing_results_${new Date().toISOString().slice(0, 10)}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showAlert('结果已导出', 'success'); } showAlert(message, type = 'info') { const alertContainer = document.getElementById('alertContainer'); if (!alertContainer) return; const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type}`; alertDiv.textContent = message; alertContainer.appendChild(alertDiv); setTimeout(() => { alertDiv.remove(); }, 3000); } } // 主题切换功能 function toggleTheme() { const body = document.body; const themeToggle = document.getElementById('themeToggle'); if (body.classList.contains('dark-mode')) { body.classList.remove('dark-mode'); localStorage.setItem('theme', 'light'); themeToggle.innerHTML = '🌙'; } else { body.classList.add('dark-mode'); localStorage.setItem('theme', 'dark'); themeToggle.innerHTML = '☀️'; } } // 初始化主题 function initTheme() { const savedTheme = localStorage.getItem('theme') || 'light'; const body = document.body; const themeToggle = document.getElementById('themeToggle'); if (savedTheme === 'dark') { body.classList.add('dark-mode'); if (themeToggle) themeToggle.innerHTML = '☀️'; } else { body.classList.remove('dark-mode'); if (themeToggle) themeToggle.innerHTML = '🌙'; } } // 初始化应用 - 确保在 DOM 加载完成后执行 document.addEventListener('DOMContentLoaded', () => { new ServoSizer(); initTheme(); // 绑定主题切换按钮事件 const themeToggle = document.getElementById('themeToggle'); if (themeToggle) { themeToggle.addEventListener('click', toggleTheme); } });