script.js 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. // 伺服驱动选型工具 - 核心计算逻辑
  2. class ServoSizer {
  3. constructor() {
  4. this.currentType = 'ballscrew';
  5. this.chart = null;
  6. // 确保在 DOM 完全加载后初始化
  7. if (document.readyState === 'loading') {
  8. document.addEventListener('DOMContentLoaded', () => {
  9. this.init();
  10. });
  11. } else {
  12. this.init();
  13. }
  14. }
  15. init() {
  16. // 检查必要的 DOM 元素是否存在
  17. const requiredElements = [
  18. 'transmissionType', 'dynamicParameters', 'calculateBtn',
  19. 'resetBtn', 'exportBtn', 'resultsDisplay'
  20. ];
  21. for (const elementId of requiredElements) {
  22. if (!document.getElementById(elementId)) {
  23. console.error(`Required element #${elementId} not found`);
  24. return;
  25. }
  26. }
  27. this.bindEvents();
  28. this.updateParametersForm();
  29. this.loadDefaults();
  30. }
  31. bindEvents() {
  32. // 传动类型切换
  33. const transmissionSelect = document.getElementById('transmissionType');
  34. if (transmissionSelect) {
  35. transmissionSelect.addEventListener('change', (e) => {
  36. this.currentType = e.target.value;
  37. this.updateParametersForm();
  38. this.clearResults();
  39. });
  40. }
  41. // 计算按钮
  42. const calculateBtn = document.getElementById('calculateBtn');
  43. if (calculateBtn) {
  44. calculateBtn.addEventListener('click', () => {
  45. this.calculate();
  46. });
  47. }
  48. // 重置按钮
  49. const resetBtn = document.getElementById('resetBtn');
  50. if (resetBtn) {
  51. resetBtn.addEventListener('click', () => {
  52. this.resetForm();
  53. });
  54. }
  55. // 导出按钮
  56. const exportBtn = document.getElementById('exportBtn');
  57. if (exportBtn) {
  58. exportBtn.addEventListener('click', () => {
  59. this.exportResults();
  60. });
  61. }
  62. // 保存配置按钮
  63. const saveConfigBtn = document.getElementById('saveConfigBtn');
  64. if (saveConfigBtn) {
  65. saveConfigBtn.addEventListener('click', () => {
  66. this.saveConfiguration();
  67. });
  68. }
  69. // 加载配置按钮
  70. const loadConfigBtn = document.getElementById('loadConfigBtn');
  71. if (loadConfigBtn) {
  72. loadConfigBtn.addEventListener('click', () => {
  73. this.loadConfiguration();
  74. });
  75. }
  76. }
  77. validateInput(input) {
  78. const value = parseFloat(input.value);
  79. const min = parseFloat(input.min) || -Infinity;
  80. const max = parseFloat(input.max) || Infinity;
  81. const step = parseFloat(input.step) || 1;
  82. // 检查是否为空值
  83. if (input.value.trim() === '') {
  84. input.classList.add('error');
  85. this.showValidationMessage(input, '此字段为必填项');
  86. return false;
  87. }
  88. // 检查数值有效性
  89. if (isNaN(value)) {
  90. input.classList.add('error');
  91. this.showValidationMessage(input, '请输入有效数字');
  92. return false;
  93. }
  94. // 检查范围
  95. if (value < min || value > max) {
  96. input.classList.add('error');
  97. this.showValidationMessage(input, `值应在 ${min} 到 ${max} 之间`);
  98. return false;
  99. }
  100. // 检查步长(可选)
  101. if (step > 0 && Math.abs((value - min) % step) > 1e-10) {
  102. // 对于小数步长,允许一定的精度误差
  103. const roundedValue = Math.round(value / step) * step;
  104. if (Math.abs(value - roundedValue) > 1e-10) {
  105. input.classList.add('error');
  106. this.showValidationMessage(input, `值应为 ${step} 的倍数`);
  107. return false;
  108. }
  109. }
  110. input.classList.remove('error');
  111. this.hideValidationMessage(input);
  112. return true;
  113. }
  114. showValidationMessage(input, message) {
  115. // 移除现有的验证消息
  116. this.hideValidationMessage(input);
  117. // 创建验证消息元素
  118. const messageEl = document.createElement('div');
  119. messageEl.className = 'validation-message';
  120. messageEl.textContent = message;
  121. messageEl.style.cssText = `
  122. color: #e74c3c;
  123. font-size: 12px;
  124. margin-top: 5px;
  125. display: block;
  126. `;
  127. // 插入到输入框后面
  128. input.parentNode.insertBefore(messageEl, input.nextSibling);
  129. }
  130. hideValidationMessage(input) {
  131. const existingMessage = input.parentNode.querySelector('.validation-message');
  132. if (existingMessage) {
  133. existingMessage.remove();
  134. }
  135. }
  136. loadDefaults() {
  137. // 默认值已经在HTML中设置
  138. }
  139. // 获取特定传动类型的参数表单
  140. getParameterForm(type) {
  141. const forms = {
  142. ballscrew: `
  143. <h4>丝杠参数</h4>
  144. <div class="form-group">
  145. <label for="screwDiameter">丝杠直径 (mm):</label>
  146. <input type="number" id="screwDiameter" class="form-control" value="20" min="1" step="0.1">
  147. </div>
  148. <div class="form-group">
  149. <label for="screwLead">丝杠导程 (mm):</label>
  150. <input type="number" id="screwLead" class="form-control" value="5" min="0.1" step="0.1">
  151. </div>
  152. <div class="form-group">
  153. <label for="travelDistance">行程距离 (mm):</label>
  154. <input type="number" id="travelDistance" class="form-control" value="500" min="1" step="1">
  155. </div>
  156. <div class="form-group">
  157. <label for="loadMass">负载质量 (kg):</label>
  158. <input type="number" id="loadMass" class="form-control" value="10" min="0.1" step="0.1">
  159. </div>
  160. <div class="form-group">
  161. <label for="maxSpeed">最大速度 (mm/s):</label>
  162. <input type="number" id="maxSpeed" class="form-control" value="100" min="0.1" step="0.1">
  163. </div>
  164. <div class="form-group">
  165. <label for="accelerationTime">加速时间 (s):</label>
  166. <input type="number" id="accelerationTime" class="form-control" value="0.5" min="0.01" step="0.01">
  167. </div>
  168. <div class="form-group">
  169. <label for="frictionCoeff">摩擦系数:</label>
  170. <input type="number" id="frictionCoeff" class="form-control" value="0.01" min="0" max="1" step="0.001">
  171. </div>
  172. <div class="form-group">
  173. <label for="efficiency">传动效率:</label>
  174. <input type="number" id="efficiency" class="form-control" value="0.9" min="0.1" max="1" step="0.01">
  175. </div>
  176. `,
  177. timingBelt: `
  178. <h4>同步带参数</h4>
  179. <div class="form-group">
  180. <label for="pulleyDiameter">滑轮直径 (mm):</label>
  181. <input type="number" id="pulleyDiameter" class="form-control" value="50" min="1" step="0.1">
  182. </div>
  183. <div class="form-group">
  184. <label for="loadMass">负载质量 (kg):</label>
  185. <input type="number" id="loadMass" class="form-control" value="5" min="0.1" step="0.1">
  186. </div>
  187. <div class="form-group">
  188. <label for="maxSpeed">最大线速度 (mm/s):</label>
  189. <input type="number" id="maxSpeed" class="form-control" value="200" min="0.1" step="0.1">
  190. </div>
  191. <div class="form-group">
  192. <label for="accelerationTime">加速时间 (s):</label>
  193. <input type="number" id="accelerationTime" class="form-control" value="0.3" min="0.01" step="0.01">
  194. </div>
  195. <div class="form-group">
  196. <label for="frictionCoeff">摩擦系数:</label>
  197. <input type="number" id="frictionCoeff" class="form-control" value="0.02" min="0" max="1" step="0.001">
  198. </div>
  199. <div class="form-group">
  200. <label for="efficiency">传动效率:</label>
  201. <input type="number" id="efficiency" class="form-control" value="0.95" min="0.1" max="1" step="0.01">
  202. </div>
  203. `,
  204. gearbox: `
  205. <h4>减速机参数</h4>
  206. <div class="form-group">
  207. <label for="gearRatio">减速比:</label>
  208. <input type="number" id="gearRatio" class="form-control" value="10" min="0.1" step="0.1">
  209. </div>
  210. <div class="form-group">
  211. <label for="loadInertia">负载惯量 (kg·m²):</label>
  212. <input type="number" id="loadInertia" class="form-control" value="0.01" min="0.0001" step="0.0001">
  213. </div>
  214. <div class="form-group">
  215. <label for="loadTorque">负载转矩 (N·m):</label>
  216. <input type="number" id="loadTorque" class="form-control" value="5" min="0.1" step="0.1">
  217. </div>
  218. <div class="form-group">
  219. <label for="maxSpeed">输出轴最大转速 (RPM):</label>
  220. <input type="number" id="maxSpeed" class="form-control" value="100" min="0.1" step="0.1">
  221. </div>
  222. <div class="form-group">
  223. <label for="accelerationTime">加速时间 (s):</label>
  224. <input type="number" id="accelerationTime" class="form-control" value="0.2" min="0.01" step="0.01">
  225. </div>
  226. <div class="form-group">
  227. <label for="efficiency">传动效率:</label>
  228. <input type="number" id="efficiency" class="form-control" value="0.9" min="0.1" max="1" step="0.01">
  229. </div>
  230. `,
  231. rotaryTable: `
  232. <h4>转盘参数</h4>
  233. <div class="form-group">
  234. <label for="tableMass">转盘质量 (kg):</label>
  235. <input type="number" id="tableMass" class="form-control" value="20" min="0.1" step="0.1">
  236. </div>
  237. <div class="form-group">
  238. <label for="tableRadius">转盘半径 (mm):</label>
  239. <input type="number" id="tableRadius" class="form-control" value="150" min="1" step="0.1">
  240. </div>
  241. <div class="form-group">
  242. <label for="maxSpeed">最大转速 (RPM):</label>
  243. <input type="number" id="maxSpeed" class="form-control" value="60" min="0.1" step="0.1">
  244. </div>
  245. <div class="form-group">
  246. <label for="accelerationTime">加速时间 (s):</label>
  247. <input type="number" id="accelerationTime" class="form-control" value="1.0" min="0.01" step="0.01">
  248. </div>
  249. <div class="form-group">
  250. <label for="frictionCoeff">摩擦系数:</label>
  251. <input type="number" id="frictionCoeff" class="form-control" value="0.01" min="0" max="1" step="0.001">
  252. </div>
  253. <div class="form-group">
  254. <label for="efficiency">传动效率:</label>
  255. <input type="number" id="efficiency" class="form-control" value="0.95" min="0.1" max="1" step="0.01">
  256. </div>
  257. `,
  258. winder: `
  259. <h4>收放卷参数</h4>
  260. <div class="form-group">
  261. <label for="materialDensity">材料密度 (kg/m³):</label>
  262. <input type="number" id="materialDensity" class="form-control" value="7800" min="100" step="100">
  263. </div>
  264. <div class="form-group">
  265. <label for="materialWidth">材料宽度 (mm):</label>
  266. <input type="number" id="materialWidth" class="form-control" value="100" min="1" step="1">
  267. </div>
  268. <div class="form-group">
  269. <label for="initialDiameter">初始直径 (mm):</label>
  270. <input type="number" id="initialDiameter" class="form-control" value="50" min="1" step="0.1">
  271. </div>
  272. <div class="form-group">
  273. <label for="finalDiameter">最终直径 (mm):</label>
  274. <input type="number" id="finalDiameter" class="form-control" value="200" min="1" step="0.1">
  275. </div>
  276. <div class="form-group">
  277. <label for="lineSpeed">线速度 (mm/s):</label>
  278. <input type="number" id="lineSpeed" class="form-control" value="100" min="0.1" step="0.1">
  279. </div>
  280. <div class="form-group">
  281. <label for="tension">张力 (N):</label>
  282. <input type="number" id="tension" class="form-control" value="50" min="0.1" step="0.1">
  283. </div>
  284. <div class="form-group">
  285. <label for="accelerationTime">加速时间 (s):</label>
  286. <input type="number" id="accelerationTime" class="form-control" value="0.5" min="0.01" step="0.01">
  287. </div>
  288. <div class="form-group">
  289. <label for="efficiency">传动效率:</label>
  290. <input type="number" id="efficiency" class="form-control" value="0.85" min="0.1" max="1" step="0.01">
  291. </div>
  292. `,
  293. directDrive: `
  294. <h4>直接驱动参数</h4>
  295. <div class="form-group">
  296. <label for="loadInertia">负载惯量 (kg·m²):</label>
  297. <input type="number" id="loadInertia" class="form-control" value="0.005" min="0.0001" step="0.0001">
  298. </div>
  299. <div class="form-group">
  300. <label for="frictionTorque">摩擦转矩 (N·m):</label>
  301. <input type="number" id="frictionTorque" class="form-control" value="0.1" min="0.001" step="0.001">
  302. </div>
  303. <div class="form-group">
  304. <label for="maxSpeed">最大转速 (RPM):</label>
  305. <input type="number" id="maxSpeed" class="form-control" value="300" min="0.1" step="0.1">
  306. </div>
  307. <div class="form-group">
  308. <label for="accelerationTime">加速时间 (s):</label>
  309. <input type="number" id="accelerationTime" class="form-control" value="0.1" min="0.01" step="0.01">
  310. </div>
  311. <!-- 直接驱动通常没有传动效率和摩擦系数,使用默认值 -->
  312. <input type="hidden" id="frictionCoeff" value="0.0">
  313. <input type="hidden" id="efficiency" value="1.0">
  314. `,
  315. combined: `
  316. <h4>组合传动参数</h4>
  317. <div class="form-group">
  318. <label for="primaryType">主要传动类型:</label>
  319. <select id="primaryType" class="form-control">
  320. <option value="ballscrew">丝杠</option>
  321. <option value="timingBelt">同步带</option>
  322. <option value="gearbox">减速机</option>
  323. </select>
  324. </div>
  325. <div class="form-group">
  326. <label for="secondaryType">次要传动类型:</label>
  327. <select id="secondaryType" class="form-control">
  328. <option value="gearbox">减速机</option>
  329. <option value="timingBelt">同步带</option>
  330. </select>
  331. </div>
  332. <div class="form-group">
  333. <label for="combinedLoadMass">组合负载质量 (kg):</label>
  334. <input type="number" id="combinedLoadMass" class="form-control" value="15" min="0.1" step="0.1">
  335. </div>
  336. <div class="form-group">
  337. <label for="maxSpeed">最大速度:</label>
  338. <input type="number" id="maxSpeed" class="form-control" value="80" min="0.1" step="0.1">
  339. </div>
  340. <div class="form-group">
  341. <label for="accelerationTime">加速时间 (s):</label>
  342. <input type="number" id="accelerationTime" class="form-control" value="0.4" min="0.01" step="0.01">
  343. </div>
  344. <div class="form-group">
  345. <label for="frictionCoeff">摩擦系数:</label>
  346. <input type="number" id="frictionCoeff" class="form-control" value="0.015" min="0" max="1" step="0.001">
  347. </div>
  348. <div class="form-group">
  349. <label for="efficiency">传动效率:</label>
  350. <input type="number" id="efficiency" class="form-control" value="0.85" min="0.1" max="1" step="0.01">
  351. </div>
  352. `
  353. };
  354. return forms[type] || forms.ballscrew;
  355. }
  356. updateParametersForm() {
  357. const dynamicParams = document.getElementById('dynamicParameters');
  358. if (dynamicParams) {
  359. dynamicParams.innerHTML = this.getParameterForm(this.currentType);
  360. // 绑定输入验证
  361. document.querySelectorAll('#dynamicParameters input[type="number"]').forEach(input => {
  362. input.addEventListener('input', () => {
  363. this.validateInput(input);
  364. });
  365. });
  366. }
  367. }
  368. // 安全的数值计算函数
  369. safeNumber(value, defaultValue = 0) {
  370. const num = parseFloat(value);
  371. return isNaN(num) || !isFinite(num) ? defaultValue : num;
  372. }
  373. // 丝杠计算
  374. calculateBallScrew(params) {
  375. const {
  376. loadMass, screwDiameter, screwLead, frictionCoeff, efficiency,
  377. maxSpeed, accelerationTime, travelDistance
  378. } = params;
  379. // 转换为标准单位
  380. const lead = this.safeNumber(screwLead, 5) / 1000; // mm to m
  381. const diameter = this.safeNumber(screwDiameter, 20) / 1000; // mm to m
  382. const travel = this.safeNumber(travelDistance, 500) / 1000; // mm to m
  383. const mass = this.safeNumber(loadMass, 10);
  384. const speed = this.safeNumber(maxSpeed, 100);
  385. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001); // 避免除零
  386. const friction = this.safeNumber(frictionCoeff, 0.01);
  387. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); // 避免除零
  388. // 1. 负载转矩计算
  389. const gravity = 9.81;
  390. const frictionForce = mass * gravity * friction;
  391. const frictionTorque = (frictionForce * diameter) / 2;
  392. // 推力转矩
  393. const thrustTorque = (mass * gravity * lead) / (2 * Math.PI);
  394. const totalTorque = (thrustTorque + frictionTorque) / eff;
  395. // 2. 转速计算
  396. const maxRpm = (speed * 60) / (lead * 1000); // mm/s to rpm
  397. // 3. 惯量计算
  398. // 丝杠惯量 (实心圆柱体)
  399. const screwInertia = (Math.PI * 7800 * Math.pow(diameter, 4) * travel) / 32;
  400. // 负载折算到电机轴的惯量
  401. const loadInertia = mass * Math.pow(lead / (2 * Math.PI), 2);
  402. const totalInertia = Math.max(screwInertia + loadInertia, 1e-6); // 避免零惯量
  403. // 4. 加速度转矩
  404. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  405. const accelTorque = totalInertia * angularAccel;
  406. // 5. 峰值转矩
  407. const peakTorque = totalTorque + accelTorque;
  408. // 6. 功率计算
  409. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  410. return {
  411. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  412. torque: { continuous: Math.max(totalTorque, 0), peak: Math.max(peakTorque, 0) },
  413. inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 },
  414. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  415. };
  416. }
  417. // 同步带计算
  418. calculateTimingBelt(params) {
  419. const {
  420. loadMass, pulleyDiameter, frictionCoeff, efficiency,
  421. maxSpeed, accelerationTime
  422. } = params;
  423. const radius = this.safeNumber(pulleyDiameter, 50) / 2000; // mm to m
  424. const mass = this.safeNumber(loadMass, 5);
  425. const speed = this.safeNumber(maxSpeed, 200);
  426. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.3), 0.001);
  427. const friction = this.safeNumber(frictionCoeff, 0.01);
  428. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  429. // 负载转矩
  430. const gravity = 9.81;
  431. const frictionForce = mass * gravity * friction;
  432. const loadTorque = (mass * gravity * radius + frictionForce * radius) / eff;
  433. // 转速
  434. const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(pulleyDiameter, 50));
  435. // 惯量计算 - 更精确的模型
  436. // 滑轮惯量 (实心圆柱) - 假设滑轮材料为铝(密度2700 kg/m³),厚度5mm
  437. const pulleyThickness = 0.005; // 5mm
  438. const pulleyDensity = 2700; // 铝的密度 kg/m³
  439. const pulleyVolume = Math.PI * Math.pow(radius, 2) * pulleyThickness;
  440. const pulleyMass = pulleyDensity * pulleyVolume;
  441. const pulleyInertia = 0.5 * pulleyMass * Math.pow(radius, 2);
  442. // 负载惯量
  443. const loadInertia = mass * Math.pow(radius, 2);
  444. const totalInertia = Math.max(pulleyInertia + loadInertia, 1e-6);
  445. // 加速度转矩
  446. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  447. const accelTorque = totalInertia * angularAccel;
  448. const peakTorque = loadTorque + accelTorque;
  449. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  450. return {
  451. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  452. torque: { continuous: Math.max(loadTorque, 0), peak: Math.max(peakTorque, 0) },
  453. inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 },
  454. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  455. };
  456. }
  457. // 减速机计算
  458. calculateGearbox(params) {
  459. const {
  460. loadInertia, loadTorque, gearRatio, efficiency,
  461. maxSpeed, accelerationTime
  462. } = params;
  463. const inertia = this.safeNumber(loadInertia, 0.01);
  464. const torque = this.safeNumber(loadTorque, 5);
  465. const ratio = Math.max(this.safeNumber(gearRatio, 10), 0.01);
  466. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  467. const speed = this.safeNumber(maxSpeed, 100);
  468. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.2), 0.001);
  469. // 折算到电机侧
  470. const reflectedInertia = inertia / Math.pow(ratio, 2);
  471. const reflectedTorque = torque / (ratio * eff);
  472. // 电机转速
  473. const motorRpm = speed * ratio;
  474. // 加速度转矩
  475. const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime;
  476. const accelTorque = Math.max(reflectedInertia, 1e-6) * angularAccel;
  477. const peakTorque = reflectedTorque + accelTorque;
  478. const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60;
  479. return {
  480. speedRange: { min: 0, max: Math.max(motorRpm, 0) },
  481. torque: { continuous: Math.max(reflectedTorque, 0), peak: Math.max(peakTorque, 0) },
  482. inertia: { motor: 0.001, load: Math.max(reflectedInertia, 1e-6), ratio: Math.max(reflectedInertia, 1e-6) / 0.001 },
  483. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  484. };
  485. }
  486. // 转盘计算
  487. calculateRotaryTable(params) {
  488. const {
  489. tableMass, tableRadius, frictionCoeff, efficiency,
  490. maxSpeed, accelerationTime
  491. } = params;
  492. const mass = this.safeNumber(tableMass, 20);
  493. const radius = this.safeNumber(tableRadius, 150) / 1000; // mm to m
  494. const friction = this.safeNumber(frictionCoeff, 0.01);
  495. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  496. const speed = this.safeNumber(maxSpeed, 60);
  497. const accelTime = Math.max(this.safeNumber(accelerationTime, 1.0), 0.001);
  498. // 转盘惯量 (实心圆盘)
  499. const tableInertia = 0.5 * mass * Math.pow(radius, 2);
  500. // 摩擦转矩
  501. const gravity = 9.81;
  502. const frictionTorque = mass * gravity * friction * radius;
  503. // 转速
  504. const maxRpm = speed;
  505. // 加速度转矩
  506. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  507. const accelTorque = Math.max(tableInertia, 1e-6) * angularAccel;
  508. const peakTorque = (frictionTorque + accelTorque) / eff;
  509. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  510. return {
  511. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  512. torque: { continuous: Math.max(frictionTorque / eff, 0), peak: Math.max(peakTorque, 0) },
  513. inertia: { motor: 0.001, load: Math.max(tableInertia, 1e-6), ratio: Math.max(tableInertia, 1e-6) / 0.001 },
  514. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  515. };
  516. }
  517. // 收放卷计算
  518. calculateWinder(params) {
  519. const {
  520. materialDensity, materialWidth, initialDiameter, finalDiameter,
  521. lineSpeed, tension, accelerationTime
  522. } = params;
  523. const density = this.safeNumber(materialDensity, 7800);
  524. const width = this.safeNumber(materialWidth, 100) / 1000;
  525. const initialRadius = this.safeNumber(initialDiameter, 50) / 2000; // mm to m
  526. const finalRadius = this.safeNumber(finalDiameter, 200) / 2000;
  527. const speed = this.safeNumber(lineSpeed, 100);
  528. const tensionVal = this.safeNumber(tension, 50);
  529. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001);
  530. // 卷筒惯量 (空心圆柱)
  531. const finalMass = density * Math.PI * (Math.pow(finalRadius, 2) - Math.pow(initialRadius, 2)) * width;
  532. const finalInertia = 0.5 * Math.max(finalMass, 0.001) * (Math.pow(finalRadius, 2) + Math.pow(initialRadius, 2));
  533. // 张力转矩
  534. const tensionTorque = tensionVal * finalRadius;
  535. // 转速范围
  536. const minRpm = (speed * 60) / (Math.PI * this.safeNumber(finalDiameter, 200));
  537. const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(initialDiameter, 50));
  538. // 加速度转矩 (使用最大惯量)
  539. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  540. const accelTorque = Math.max(finalInertia, 1e-6) * angularAccel;
  541. const peakTorque = tensionTorque + accelTorque;
  542. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  543. return {
  544. speedRange: { min: Math.max(minRpm, 0), max: Math.max(maxRpm, 0) },
  545. torque: { continuous: Math.max(tensionTorque, 0), peak: Math.max(peakTorque, 0) },
  546. inertia: { motor: 0.001, load: Math.max(finalInertia, 1e-6), ratio: Math.max(finalInertia, 1e-6) / 0.001 },
  547. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  548. };
  549. }
  550. // 直接驱动计算
  551. calculateDirectDrive(params) {
  552. const {
  553. loadInertia, frictionTorque, maxSpeed, accelerationTime
  554. } = params;
  555. const inertia = this.safeNumber(loadInertia, 0.005);
  556. const friction = this.safeNumber(frictionTorque, 0.1);
  557. const speed = this.safeNumber(maxSpeed, 300);
  558. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.1), 0.001);
  559. // 直接驱动,无传动比
  560. const motorRpm = speed;
  561. // 加速度转矩
  562. const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime;
  563. const accelTorque = Math.max(inertia, 1e-6) * angularAccel;
  564. const peakTorque = friction + accelTorque;
  565. const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60;
  566. return {
  567. speedRange: { min: 0, max: Math.max(motorRpm, 0) },
  568. torque: { continuous: Math.max(friction, 0), peak: Math.max(peakTorque, 0) },
  569. inertia: { motor: 0.001, load: Math.max(inertia, 1e-6), ratio: Math.max(inertia, 1e-6) / 0.001 },
  570. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  571. };
  572. }
  573. // 组合传动计算(简化版)
  574. calculateCombined(params) {
  575. const {
  576. combinedLoadMass, maxSpeed, accelerationTime, frictionCoeff, efficiency
  577. } = params;
  578. const mass = this.safeNumber(combinedLoadMass, 15);
  579. const speed = this.safeNumber(maxSpeed, 80);
  580. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.4), 0.001);
  581. const friction = this.safeNumber(frictionCoeff, 0.01);
  582. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  583. const gravity = 9.81;
  584. const loadTorque = mass * gravity * 0.1; // 简化假设
  585. const maxRpm = speed;
  586. const loadInertia = mass * 0.01; // 简化假设
  587. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  588. const accelTorque = Math.max(loadInertia, 1e-6) * angularAccel;
  589. const continuousTorque = loadTorque / eff;
  590. const peakTorque = (loadTorque + accelTorque) / eff;
  591. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  592. return {
  593. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  594. torque: { continuous: Math.max(continuousTorque, 0), peak: Math.max(peakTorque, 0) },
  595. inertia: { motor: 0.001, load: Math.max(loadInertia, 1e-6), ratio: Math.max(loadInertia, 1e-6) / 0.001 },
  596. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  597. };
  598. }
  599. getFormValues() {
  600. const values = {};
  601. // 获取所有输入值
  602. document.querySelectorAll('#dynamicParameters input, #dynamicParameters select').forEach(input => {
  603. const id = input.id;
  604. if (input.type === 'number') {
  605. values[id] = this.safeNumber(input.value, 0);
  606. } else {
  607. values[id] = input.value;
  608. }
  609. });
  610. // 获取通用参数
  611. const safetyFactorEl = document.getElementById('safetyFactor');
  612. const inertiaRatioEl = document.getElementById('inertiaRatio');
  613. if (safetyFactorEl) values.safetyFactor = this.safeNumber(safetyFactorEl.value, 1.5);
  614. if (inertiaRatioEl) values.inertiaRatio = this.safeNumber(inertiaRatioEl.value, 10);
  615. return values;
  616. }
  617. calculate() {
  618. const params = this.getFormValues();
  619. // 验证所有输入
  620. let isValid = true;
  621. document.querySelectorAll('input[type="number"]').forEach(input => {
  622. if (!this.validateInput(input)) {
  623. isValid = false;
  624. }
  625. });
  626. if (!isValid) {
  627. this.showAlert('请检查输入参数是否有效', 'error');
  628. return;
  629. }
  630. let results;
  631. try {
  632. switch (this.currentType) {
  633. case 'ballscrew':
  634. results = this.calculateBallScrew(params);
  635. break;
  636. case 'timingBelt':
  637. results = this.calculateTimingBelt(params);
  638. break;
  639. case 'gearbox':
  640. results = this.calculateGearbox(params);
  641. break;
  642. case 'rotaryTable':
  643. results = this.calculateRotaryTable(params);
  644. break;
  645. case 'winder':
  646. results = this.calculateWinder(params);
  647. break;
  648. case 'directDrive':
  649. results = this.calculateDirectDrive(params);
  650. break;
  651. case 'combined':
  652. results = this.calculateCombined(params);
  653. break;
  654. default:
  655. throw new Error('未知的传动类型');
  656. }
  657. // 应用安全系数
  658. results.torque.continuous *= params.safetyFactor;
  659. results.torque.peak *= params.safetyFactor;
  660. results.power.rated *= params.safetyFactor;
  661. // 检查惯量比警告
  662. const inertiaWarning = results.inertia.ratio > params.inertiaRatio;
  663. this.displayResults(results, inertiaWarning);
  664. this.updateChart(results);
  665. this.showAlert('计算完成!', 'success');
  666. } catch (error) {
  667. console.error('计算错误:', error);
  668. this.showAlert('计算出错: ' + error.message, 'error');
  669. }
  670. }
  671. displayResults(results, inertiaWarning) {
  672. const resultsDiv = document.getElementById('resultsDisplay');
  673. const warningDiv = document.getElementById('warningMessage');
  674. const errorDiv = document.getElementById('errorMessage');
  675. if (!resultsDiv) return;
  676. // 隐藏错误消息
  677. if (errorDiv) errorDiv.classList.add('hidden');
  678. resultsDiv.innerHTML = `
  679. <div class="results-grid">
  680. <div class="result-card">
  681. <div class="result-label">转速范围 (RPM)</div>
  682. <div class="result-value">${results.speedRange.min.toFixed(1)} - ${results.speedRange.max.toFixed(1)}</div>
  683. </div>
  684. <div class="result-card">
  685. <div class="result-label">连续转矩 (N·m)</div>
  686. <div class="result-value">${results.torque.continuous.toFixed(3)}</div>
  687. </div>
  688. <div class="result-card">
  689. <div class="result-label">峰值转矩 (N·m)</div>
  690. <div class="result-value">${results.torque.peak.toFixed(3)}</div>
  691. </div>
  692. <div class="result-card">
  693. <div class="result-label">负载惯量 (kg·m²)</div>
  694. <div class="result-value">${results.inertia.load.toExponential(3)}</div>
  695. </div>
  696. <div class="result-card">
  697. <div class="result-label">惯量比</div>
  698. <div class="result-value">${results.inertia.ratio.toFixed(1)}</div>
  699. </div>
  700. <div class="result-card">
  701. <div class="result-label">额定功率 (W)</div>
  702. <div class="result-value">${results.power.rated.toFixed(1)}</div>
  703. </div>
  704. </div>
  705. `;
  706. // 显示惯量比警告
  707. if (warningDiv) {
  708. if (inertiaWarning) {
  709. warningDiv.textContent = `⚠️ 警告:惯量比 (${results.inertia.ratio.toFixed(1)}) 超过允许值,建议选择更大惯量的电机或添加减速机。`;
  710. warningDiv.classList.remove('hidden');
  711. } else {
  712. warningDiv.classList.add('hidden');
  713. }
  714. }
  715. const resultsCard = document.getElementById('resultsCard');
  716. if (resultsCard) resultsCard.style.display = 'block';
  717. }
  718. updateChart(results) {
  719. const chartCanvas = document.getElementById('performanceChart');
  720. if (!chartCanvas) return;
  721. // 检查 Chart.js 是否可用
  722. if (typeof Chart === 'undefined') {
  723. console.warn('Chart.js not loaded, skipping chart rendering');
  724. return;
  725. }
  726. const ctx = chartCanvas.getContext('2d');
  727. // 销毁现有图表
  728. if (this.chart) {
  729. this.chart.destroy();
  730. }
  731. // 创建新图表
  732. this.chart = new Chart(ctx, {
  733. type: 'bar',
  734. data: {
  735. labels: ['连续转矩', '峰值转矩', '额定功率'],
  736. datasets: [{
  737. label: '性能指标',
  738. data: [
  739. results.torque.continuous,
  740. results.torque.peak,
  741. results.power.rated
  742. ],
  743. backgroundColor: [
  744. 'rgba(54, 162, 235, 0.8)',
  745. 'rgba(255, 99, 132, 0.8)',
  746. 'rgba(75, 192, 192, 0.8)'
  747. ],
  748. borderColor: [
  749. 'rgba(54, 162, 235, 1)',
  750. 'rgba(255, 99, 132, 1)',
  751. 'rgba(75, 192, 192, 1)'
  752. ],
  753. borderWidth: 1
  754. }]
  755. },
  756. options: {
  757. responsive: true,
  758. maintainAspectRatio: false,
  759. scales: {
  760. y: {
  761. beginAtZero: true,
  762. title: {
  763. display: true,
  764. text: '数值'
  765. }
  766. }
  767. },
  768. plugins: {
  769. legend: {
  770. display: false
  771. },
  772. tooltip: {
  773. callbacks: {
  774. label: function(context) {
  775. let label = context.dataset.label || '';
  776. if (label) {
  777. label += ': ';
  778. }
  779. if (context.parsed.y !== null) {
  780. label += context.parsed.y.toFixed(3);
  781. if (context.dataIndex === 2) {
  782. label += ' W';
  783. } else {
  784. label += ' N·m';
  785. }
  786. }
  787. return label;
  788. }
  789. }
  790. }
  791. }
  792. }
  793. });
  794. }
  795. clearResults() {
  796. const resultsDiv = document.getElementById('resultsDisplay');
  797. const warningDiv = document.getElementById('warningMessage');
  798. const errorDiv = document.getElementById('errorMessage');
  799. if (resultsDiv) {
  800. resultsDiv.innerHTML = '<p class="placeholder">请输入参数并点击计算获取结果</p>';
  801. }
  802. if (warningDiv) warningDiv.classList.add('hidden');
  803. if (errorDiv) errorDiv.classList.add('hidden');
  804. // 销毁图表
  805. if (this.chart) {
  806. this.chart.destroy();
  807. this.chart = null;
  808. }
  809. }
  810. resetForm() {
  811. // 重置所有输入框
  812. document.querySelectorAll('input[type="number"]').forEach(input => {
  813. input.value = input.defaultValue || '';
  814. input.classList.remove('error');
  815. });
  816. // 重置选择框
  817. const transmissionSelect = document.getElementById('transmissionType');
  818. if (transmissionSelect) {
  819. transmissionSelect.selectedIndex = 0;
  820. this.currentType = 'ballscrew';
  821. this.updateParametersForm();
  822. }
  823. this.clearResults();
  824. this.showAlert('表单已重置', 'info');
  825. }
  826. exportResults() {
  827. const resultsDiv = document.getElementById('resultsDisplay');
  828. if (!resultsDiv || resultsDiv.querySelector('.placeholder')) {
  829. this.showAlert('请先进行计算', 'warning');
  830. return;
  831. }
  832. const resultsText = resultsDiv.innerText;
  833. const blob = new Blob([resultsText], { type: 'text/plain' });
  834. const url = URL.createObjectURL(blob);
  835. const a = document.createElement('a');
  836. a.href = url;
  837. a.download = `servo_sizing_results_${new Date().toISOString().slice(0, 10)}.txt`;
  838. document.body.appendChild(a);
  839. a.click();
  840. document.body.removeChild(a);
  841. URL.revokeObjectURL(url);
  842. this.showAlert('结果已导出', 'success');
  843. }
  844. showAlert(message, type = 'info') {
  845. const alertContainer = document.getElementById('alertContainer');
  846. if (!alertContainer) return;
  847. const alertDiv = document.createElement('div');
  848. alertDiv.className = `alert alert-${type}`;
  849. alertDiv.textContent = message;
  850. alertContainer.appendChild(alertDiv);
  851. setTimeout(() => {
  852. alertDiv.remove();
  853. }, 3000);
  854. }
  855. }
  856. // 主题切换功能
  857. function toggleTheme() {
  858. const body = document.body;
  859. const themeToggle = document.getElementById('themeToggle');
  860. if (body.classList.contains('dark-mode')) {
  861. body.classList.remove('dark-mode');
  862. localStorage.setItem('theme', 'light');
  863. themeToggle.innerHTML = '<span class="theme-icon">🌙</span>';
  864. } else {
  865. body.classList.add('dark-mode');
  866. localStorage.setItem('theme', 'dark');
  867. themeToggle.innerHTML = '<span class="theme-icon">☀️</span>';
  868. }
  869. }
  870. // 初始化主题
  871. function initTheme() {
  872. const savedTheme = localStorage.getItem('theme') || 'light';
  873. const body = document.body;
  874. const themeToggle = document.getElementById('themeToggle');
  875. if (savedTheme === 'dark') {
  876. body.classList.add('dark-mode');
  877. if (themeToggle) themeToggle.innerHTML = '<span class="theme-icon">☀️</span>';
  878. } else {
  879. body.classList.remove('dark-mode');
  880. if (themeToggle) themeToggle.innerHTML = '<span class="theme-icon">🌙</span>';
  881. }
  882. }
  883. // 初始化应用 - 确保在 DOM 加载完成后执行
  884. document.addEventListener('DOMContentLoaded', () => {
  885. new ServoSizer();
  886. initTheme();
  887. // 绑定主题切换按钮事件
  888. const themeToggle = document.getElementById('themeToggle');
  889. if (themeToggle) {
  890. themeToggle.addEventListener('click', toggleTheme);
  891. }
  892. });