script.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  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. `,
  169. timingBelt: `
  170. <h4>同步带参数</h4>
  171. <div class="form-group">
  172. <label for="pulleyDiameter">滑轮直径 (mm):</label>
  173. <input type="number" id="pulleyDiameter" class="form-control" value="50" min="1" step="0.1">
  174. </div>
  175. <div class="form-group">
  176. <label for="loadMass">负载质量 (kg):</label>
  177. <input type="number" id="loadMass" class="form-control" value="5" min="0.1" step="0.1">
  178. </div>
  179. <div class="form-group">
  180. <label for="maxSpeed">最大线速度 (mm/s):</label>
  181. <input type="number" id="maxSpeed" class="form-control" value="200" min="0.1" step="0.1">
  182. </div>
  183. <div class="form-group">
  184. <label for="accelerationTime">加速时间 (s):</label>
  185. <input type="number" id="accelerationTime" class="form-control" value="0.3" min="0.01" step="0.01">
  186. </div>
  187. `,
  188. gearbox: `
  189. <h4>减速机参数</h4>
  190. <div class="form-group">
  191. <label for="gearRatio">减速比:</label>
  192. <input type="number" id="gearRatio" class="form-control" value="10" min="0.1" step="0.1">
  193. </div>
  194. <div class="form-group">
  195. <label for="loadInertia">负载惯量 (kg·m²):</label>
  196. <input type="number" id="loadInertia" class="form-control" value="0.01" min="0.0001" step="0.0001">
  197. </div>
  198. <div class="form-group">
  199. <label for="loadTorque">负载转矩 (N·m):</label>
  200. <input type="number" id="loadTorque" class="form-control" value="5" min="0.1" step="0.1">
  201. </div>
  202. <div class="form-group">
  203. <label for="maxSpeed">输出轴最大转速 (RPM):</label>
  204. <input type="number" id="maxSpeed" class="form-control" value="100" min="0.1" step="0.1">
  205. </div>
  206. <div class="form-group">
  207. <label for="accelerationTime">加速时间 (s):</label>
  208. <input type="number" id="accelerationTime" class="form-control" value="0.2" min="0.01" step="0.01">
  209. </div>
  210. `,
  211. rotaryTable: `
  212. <h4>转盘参数</h4>
  213. <div class="form-group">
  214. <label for="tableMass">转盘质量 (kg):</label>
  215. <input type="number" id="tableMass" class="form-control" value="20" min="0.1" step="0.1">
  216. </div>
  217. <div class="form-group">
  218. <label for="tableRadius">转盘半径 (mm):</label>
  219. <input type="number" id="tableRadius" class="form-control" value="150" min="1" step="0.1">
  220. </div>
  221. <div class="form-group">
  222. <label for="maxSpeed">最大转速 (RPM):</label>
  223. <input type="number" id="maxSpeed" class="form-control" value="60" min="0.1" step="0.1">
  224. </div>
  225. <div class="form-group">
  226. <label for="accelerationTime">加速时间 (s):</label>
  227. <input type="number" id="accelerationTime" class="form-control" value="1.0" min="0.01" step="0.01">
  228. </div>
  229. `,
  230. winder: `
  231. <h4>收放卷参数</h4>
  232. <div class="form-group">
  233. <label for="materialDensity">材料密度 (kg/m³):</label>
  234. <input type="number" id="materialDensity" class="form-control" value="7800" min="100" step="100">
  235. </div>
  236. <div class="form-group">
  237. <label for="materialWidth">材料宽度 (mm):</label>
  238. <input type="number" id="materialWidth" class="form-control" value="100" min="1" step="1">
  239. </div>
  240. <div class="form-group">
  241. <label for="initialDiameter">初始直径 (mm):</label>
  242. <input type="number" id="initialDiameter" class="form-control" value="50" min="1" step="0.1">
  243. </div>
  244. <div class="form-group">
  245. <label for="finalDiameter">最终直径 (mm):</label>
  246. <input type="number" id="finalDiameter" class="form-control" value="200" min="1" step="0.1">
  247. </div>
  248. <div class="form-group">
  249. <label for="lineSpeed">线速度 (mm/s):</label>
  250. <input type="number" id="lineSpeed" class="form-control" value="100" min="0.1" step="0.1">
  251. </div>
  252. <div class="form-group">
  253. <label for="tension">张力 (N):</label>
  254. <input type="number" id="tension" class="form-control" value="50" min="0.1" step="0.1">
  255. </div>
  256. <div class="form-group">
  257. <label for="accelerationTime">加速时间 (s):</label>
  258. <input type="number" id="accelerationTime" class="form-control" value="0.5" min="0.01" step="0.01">
  259. </div>
  260. `,
  261. directDrive: `
  262. <h4>直接驱动参数</h4>
  263. <div class="form-group">
  264. <label for="loadInertia">负载惯量 (kg·m²):</label>
  265. <input type="number" id="loadInertia" class="form-control" value="0.005" min="0.0001" step="0.0001">
  266. </div>
  267. <div class="form-group">
  268. <label for="frictionTorque">摩擦转矩 (N·m):</label>
  269. <input type="number" id="frictionTorque" class="form-control" value="0.1" min="0.001" step="0.001">
  270. </div>
  271. <div class="form-group">
  272. <label for="maxSpeed">最大转速 (RPM):</label>
  273. <input type="number" id="maxSpeed" class="form-control" value="300" min="0.1" step="0.1">
  274. </div>
  275. <div class="form-group">
  276. <label for="accelerationTime">加速时间 (s):</label>
  277. <input type="number" id="accelerationTime" class="form-control" value="0.1" min="0.01" step="0.01">
  278. </div>
  279. `,
  280. combined: `
  281. <h4>组合传动参数</h4>
  282. <div class="form-group">
  283. <label for="primaryType">主要传动类型:</label>
  284. <select id="primaryType" class="form-control">
  285. <option value="ballscrew">滚珠丝杠</option>
  286. <option value="timingBelt">同步带</option>
  287. <option value="gearbox">减速机</option>
  288. </select>
  289. </div>
  290. <div class="form-group">
  291. <label for="secondaryType">次要传动类型:</label>
  292. <select id="secondaryType" class="form-control">
  293. <option value="gearbox">减速机</option>
  294. <option value="timingBelt">同步带</option>
  295. </select>
  296. </div>
  297. <div class="form-group">
  298. <label for="combinedLoadMass">组合负载质量 (kg):</label>
  299. <input type="number" id="combinedLoadMass" class="form-control" value="15" min="0.1" step="0.1">
  300. </div>
  301. <div class="form-group">
  302. <label for="maxSpeed">最大速度:</label>
  303. <input type="number" id="maxSpeed" class="form-control" value="80" min="0.1" step="0.1">
  304. </div>
  305. <div class="form-group">
  306. <label for="accelerationTime">加速时间 (s):</label>
  307. <input type="number" id="accelerationTime" class="form-control" value="0.4" min="0.01" step="0.01">
  308. </div>
  309. `
  310. };
  311. return forms[type] || forms.ballscrew;
  312. }
  313. updateParametersForm() {
  314. const dynamicParams = document.getElementById('dynamicParameters');
  315. if (dynamicParams) {
  316. dynamicParams.innerHTML = this.getParameterForm(this.currentType);
  317. // 绑定输入验证
  318. document.querySelectorAll('#dynamicParameters input[type="number"]').forEach(input => {
  319. input.addEventListener('input', () => {
  320. this.validateInput(input);
  321. });
  322. });
  323. }
  324. }
  325. // 安全的数值计算函数
  326. safeNumber(value, defaultValue = 0) {
  327. const num = parseFloat(value);
  328. return isNaN(num) || !isFinite(num) ? defaultValue : num;
  329. }
  330. // 滚珠丝杠计算
  331. calculateBallScrew(params) {
  332. const {
  333. loadMass, screwDiameter, screwLead, frictionCoeff, efficiency,
  334. maxSpeed, accelerationTime, travelDistance
  335. } = params;
  336. // 转换为标准单位
  337. const lead = this.safeNumber(screwLead, 5) / 1000; // mm to m
  338. const diameter = this.safeNumber(screwDiameter, 20) / 1000; // mm to m
  339. const travel = this.safeNumber(travelDistance, 500) / 1000; // mm to m
  340. const mass = this.safeNumber(loadMass, 10);
  341. const speed = this.safeNumber(maxSpeed, 100);
  342. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001); // 避免除零
  343. const friction = this.safeNumber(frictionCoeff, 0.01);
  344. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01); // 避免除零
  345. // 1. 负载转矩计算
  346. const gravity = 9.81;
  347. const frictionForce = mass * gravity * friction;
  348. const frictionTorque = (frictionForce * diameter) / 2;
  349. // 推力转矩
  350. const thrustTorque = (mass * gravity * lead) / (2 * Math.PI);
  351. const totalTorque = (thrustTorque + frictionTorque) / eff;
  352. // 2. 转速计算
  353. const maxRpm = (speed * 60) / (lead * 1000); // mm/s to rpm
  354. // 3. 惯量计算
  355. // 滚珠丝杠惯量 (实心圆柱体)
  356. const screwInertia = (Math.PI * 7800 * Math.pow(diameter, 4) * travel) / 32;
  357. // 负载折算到电机轴的惯量
  358. const loadInertia = mass * Math.pow(lead / (2 * Math.PI), 2);
  359. const totalInertia = Math.max(screwInertia + loadInertia, 1e-6); // 避免零惯量
  360. // 4. 加速度转矩
  361. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  362. const accelTorque = totalInertia * angularAccel;
  363. // 5. 峰值转矩
  364. const peakTorque = totalTorque + accelTorque;
  365. // 6. 功率计算
  366. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  367. return {
  368. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  369. torque: { continuous: Math.max(totalTorque, 0), peak: Math.max(peakTorque, 0) },
  370. inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 },
  371. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  372. };
  373. }
  374. // 同步带计算
  375. calculateTimingBelt(params) {
  376. const {
  377. loadMass, pulleyDiameter, frictionCoeff, efficiency,
  378. maxSpeed, accelerationTime
  379. } = params;
  380. const radius = this.safeNumber(pulleyDiameter, 50) / 2000; // mm to m
  381. const mass = this.safeNumber(loadMass, 5);
  382. const speed = this.safeNumber(maxSpeed, 200);
  383. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.3), 0.001);
  384. const friction = this.safeNumber(frictionCoeff, 0.01);
  385. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  386. // 负载转矩
  387. const gravity = 9.81;
  388. const frictionForce = mass * gravity * friction;
  389. const loadTorque = (mass * gravity * radius + frictionForce * radius) / eff;
  390. // 转速
  391. const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(pulleyDiameter, 50));
  392. // 惯量计算 - 更精确的模型
  393. // 滑轮惯量 (实心圆柱) - 假设滑轮材料为铝(密度2700 kg/m³),厚度5mm
  394. const pulleyThickness = 0.005; // 5mm
  395. const pulleyDensity = 2700; // 铝的密度 kg/m³
  396. const pulleyVolume = Math.PI * Math.pow(radius, 2) * pulleyThickness;
  397. const pulleyMass = pulleyDensity * pulleyVolume;
  398. const pulleyInertia = 0.5 * pulleyMass * Math.pow(radius, 2);
  399. // 负载惯量
  400. const loadInertia = mass * Math.pow(radius, 2);
  401. const totalInertia = Math.max(pulleyInertia + loadInertia, 1e-6);
  402. // 加速度转矩
  403. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  404. const accelTorque = totalInertia * angularAccel;
  405. const peakTorque = loadTorque + accelTorque;
  406. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  407. return {
  408. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  409. torque: { continuous: Math.max(loadTorque, 0), peak: Math.max(peakTorque, 0) },
  410. inertia: { motor: 0.001, load: totalInertia, ratio: totalInertia / 0.001 },
  411. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  412. };
  413. }
  414. // 减速机计算
  415. calculateGearbox(params) {
  416. const {
  417. loadInertia, loadTorque, gearRatio, efficiency,
  418. maxSpeed, accelerationTime
  419. } = params;
  420. const inertia = this.safeNumber(loadInertia, 0.01);
  421. const torque = this.safeNumber(loadTorque, 5);
  422. const ratio = Math.max(this.safeNumber(gearRatio, 10), 0.01);
  423. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  424. const speed = this.safeNumber(maxSpeed, 100);
  425. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.2), 0.001);
  426. // 折算到电机侧
  427. const reflectedInertia = inertia / Math.pow(ratio, 2);
  428. const reflectedTorque = torque / (ratio * eff);
  429. // 电机转速
  430. const motorRpm = speed * ratio;
  431. // 加速度转矩
  432. const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime;
  433. const accelTorque = Math.max(reflectedInertia, 1e-6) * angularAccel;
  434. const peakTorque = reflectedTorque + accelTorque;
  435. const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60;
  436. return {
  437. speedRange: { min: 0, max: Math.max(motorRpm, 0) },
  438. torque: { continuous: Math.max(reflectedTorque, 0), peak: Math.max(peakTorque, 0) },
  439. inertia: { motor: 0.001, load: Math.max(reflectedInertia, 1e-6), ratio: Math.max(reflectedInertia, 1e-6) / 0.001 },
  440. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  441. };
  442. }
  443. // 转盘计算
  444. calculateRotaryTable(params) {
  445. const {
  446. tableMass, tableRadius, frictionCoeff, efficiency,
  447. maxSpeed, accelerationTime
  448. } = params;
  449. const mass = this.safeNumber(tableMass, 20);
  450. const radius = this.safeNumber(tableRadius, 150) / 1000; // mm to m
  451. const friction = this.safeNumber(frictionCoeff, 0.01);
  452. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  453. const speed = this.safeNumber(maxSpeed, 60);
  454. const accelTime = Math.max(this.safeNumber(accelerationTime, 1.0), 0.001);
  455. // 转盘惯量 (实心圆盘)
  456. const tableInertia = 0.5 * mass * Math.pow(radius, 2);
  457. // 摩擦转矩
  458. const gravity = 9.81;
  459. const frictionTorque = mass * gravity * friction * radius;
  460. // 转速
  461. const maxRpm = speed;
  462. // 加速度转矩
  463. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  464. const accelTorque = Math.max(tableInertia, 1e-6) * angularAccel;
  465. const peakTorque = (frictionTorque + accelTorque) / eff;
  466. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  467. return {
  468. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  469. torque: { continuous: Math.max(frictionTorque / eff, 0), peak: Math.max(peakTorque, 0) },
  470. inertia: { motor: 0.001, load: Math.max(tableInertia, 1e-6), ratio: Math.max(tableInertia, 1e-6) / 0.001 },
  471. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  472. };
  473. }
  474. // 收放卷计算
  475. calculateWinder(params) {
  476. const {
  477. materialDensity, materialWidth, initialDiameter, finalDiameter,
  478. lineSpeed, tension, accelerationTime
  479. } = params;
  480. const density = this.safeNumber(materialDensity, 7800);
  481. const width = this.safeNumber(materialWidth, 100) / 1000;
  482. const initialRadius = this.safeNumber(initialDiameter, 50) / 2000; // mm to m
  483. const finalRadius = this.safeNumber(finalDiameter, 200) / 2000;
  484. const speed = this.safeNumber(lineSpeed, 100);
  485. const tensionVal = this.safeNumber(tension, 50);
  486. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.5), 0.001);
  487. // 卷筒惯量 (空心圆柱)
  488. const finalMass = density * Math.PI * (Math.pow(finalRadius, 2) - Math.pow(initialRadius, 2)) * width;
  489. const finalInertia = 0.5 * Math.max(finalMass, 0.001) * (Math.pow(finalRadius, 2) + Math.pow(initialRadius, 2));
  490. // 张力转矩
  491. const tensionTorque = tensionVal * finalRadius;
  492. // 转速范围
  493. const minRpm = (speed * 60) / (Math.PI * this.safeNumber(finalDiameter, 200));
  494. const maxRpm = (speed * 60) / (Math.PI * this.safeNumber(initialDiameter, 50));
  495. // 加速度转矩 (使用最大惯量)
  496. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  497. const accelTorque = Math.max(finalInertia, 1e-6) * angularAccel;
  498. const peakTorque = tensionTorque + accelTorque;
  499. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  500. return {
  501. speedRange: { min: Math.max(minRpm, 0), max: Math.max(maxRpm, 0) },
  502. torque: { continuous: Math.max(tensionTorque, 0), peak: Math.max(peakTorque, 0) },
  503. inertia: { motor: 0.001, load: Math.max(finalInertia, 1e-6), ratio: Math.max(finalInertia, 1e-6) / 0.001 },
  504. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  505. };
  506. }
  507. // 直接驱动计算
  508. calculateDirectDrive(params) {
  509. const {
  510. loadInertia, frictionTorque, maxSpeed, accelerationTime
  511. } = params;
  512. const inertia = this.safeNumber(loadInertia, 0.005);
  513. const friction = this.safeNumber(frictionTorque, 0.1);
  514. const speed = this.safeNumber(maxSpeed, 300);
  515. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.1), 0.001);
  516. // 直接驱动,无传动比
  517. const motorRpm = speed;
  518. // 加速度转矩
  519. const angularAccel = (Math.max(motorRpm, 0) * 2 * Math.PI / 60) / accelTime;
  520. const accelTorque = Math.max(inertia, 1e-6) * angularAccel;
  521. const peakTorque = friction + accelTorque;
  522. const power = (Math.max(peakTorque, 0) * Math.max(motorRpm, 0) * 2 * Math.PI) / 60;
  523. return {
  524. speedRange: { min: 0, max: Math.max(motorRpm, 0) },
  525. torque: { continuous: Math.max(friction, 0), peak: Math.max(peakTorque, 0) },
  526. inertia: { motor: 0.001, load: Math.max(inertia, 1e-6), ratio: Math.max(inertia, 1e-6) / 0.001 },
  527. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  528. };
  529. }
  530. // 组合传动计算(简化版)
  531. calculateCombined(params) {
  532. const {
  533. combinedLoadMass, maxSpeed, accelerationTime, frictionCoeff, efficiency
  534. } = params;
  535. const mass = this.safeNumber(combinedLoadMass, 15);
  536. const speed = this.safeNumber(maxSpeed, 80);
  537. const accelTime = Math.max(this.safeNumber(accelerationTime, 0.4), 0.001);
  538. const friction = this.safeNumber(frictionCoeff, 0.01);
  539. const eff = Math.max(this.safeNumber(efficiency, 0.9), 0.01);
  540. const gravity = 9.81;
  541. const loadTorque = mass * gravity * 0.1; // 简化假设
  542. const maxRpm = speed;
  543. const loadInertia = mass * 0.01; // 简化假设
  544. const angularAccel = (Math.max(maxRpm, 0) * 2 * Math.PI / 60) / accelTime;
  545. const accelTorque = Math.max(loadInertia, 1e-6) * angularAccel;
  546. const continuousTorque = loadTorque / eff;
  547. const peakTorque = (loadTorque + accelTorque) / eff;
  548. const power = (Math.max(peakTorque, 0) * Math.max(maxRpm, 0) * 2 * Math.PI) / 60;
  549. return {
  550. speedRange: { min: 0, max: Math.max(maxRpm, 0) },
  551. torque: { continuous: Math.max(continuousTorque, 0), peak: Math.max(peakTorque, 0) },
  552. inertia: { motor: 0.001, load: Math.max(loadInertia, 1e-6), ratio: Math.max(loadInertia, 1e-6) / 0.001 },
  553. power: { required: Math.max(power, 0), rated: Math.max(power, 0) * 1.2 }
  554. };
  555. }
  556. getFormValues() {
  557. const values = {};
  558. // 获取所有输入值
  559. document.querySelectorAll('#dynamicParameters input, #dynamicParameters select').forEach(input => {
  560. const id = input.id;
  561. if (input.type === 'number') {
  562. values[id] = this.safeNumber(input.value, 0);
  563. } else {
  564. values[id] = input.value;
  565. }
  566. });
  567. // 获取通用参数
  568. const safetyFactorEl = document.getElementById('safetyFactor');
  569. const inertiaRatioEl = document.getElementById('inertiaRatio');
  570. const frictionCoeffEl = document.getElementById('frictionCoeff');
  571. const efficiencyEl = document.getElementById('efficiency');
  572. if (safetyFactorEl) values.safetyFactor = this.safeNumber(safetyFactorEl.value, 1.5);
  573. if (inertiaRatioEl) values.inertiaRatio = this.safeNumber(inertiaRatioEl.value, 10);
  574. if (frictionCoeffEl) values.frictionCoeff = this.safeNumber(frictionCoeffEl.value, 0.01);
  575. if (efficiencyEl) values.efficiency = this.safeNumber(efficiencyEl.value, 0.9);
  576. return values;
  577. }
  578. calculate() {
  579. const params = this.getFormValues();
  580. // 验证所有输入
  581. let isValid = true;
  582. document.querySelectorAll('input[type="number"]').forEach(input => {
  583. if (!this.validateInput(input)) {
  584. isValid = false;
  585. }
  586. });
  587. if (!isValid) {
  588. this.showAlert('请检查输入参数是否有效', 'error');
  589. return;
  590. }
  591. let results;
  592. try {
  593. switch (this.currentType) {
  594. case 'ballscrew':
  595. results = this.calculateBallScrew(params);
  596. break;
  597. case 'timingBelt':
  598. results = this.calculateTimingBelt(params);
  599. break;
  600. case 'gearbox':
  601. results = this.calculateGearbox(params);
  602. break;
  603. case 'rotaryTable':
  604. results = this.calculateRotaryTable(params);
  605. break;
  606. case 'winder':
  607. results = this.calculateWinder(params);
  608. break;
  609. case 'directDrive':
  610. results = this.calculateDirectDrive(params);
  611. break;
  612. case 'combined':
  613. results = this.calculateCombined(params);
  614. break;
  615. default:
  616. throw new Error('未知的传动类型');
  617. }
  618. // 应用安全系数
  619. results.torque.continuous *= params.safetyFactor;
  620. results.torque.peak *= params.safetyFactor;
  621. results.power.rated *= params.safetyFactor;
  622. // 检查惯量比警告
  623. const inertiaWarning = results.inertia.ratio > params.inertiaRatio;
  624. this.displayResults(results, inertiaWarning);
  625. this.updateChart(results);
  626. this.showAlert('计算完成!', 'success');
  627. } catch (error) {
  628. console.error('计算错误:', error);
  629. this.showAlert('计算出错: ' + error.message, 'error');
  630. }
  631. }
  632. displayResults(results, inertiaWarning) {
  633. const resultsDiv = document.getElementById('resultsDisplay');
  634. const warningDiv = document.getElementById('warningMessage');
  635. const errorDiv = document.getElementById('errorMessage');
  636. if (!resultsDiv) return;
  637. // 隐藏错误消息
  638. if (errorDiv) errorDiv.classList.add('hidden');
  639. resultsDiv.innerHTML = `
  640. <div class="results-grid">
  641. <div class="result-card">
  642. <div class="result-label">转速范围 (RPM)</div>
  643. <div class="result-value">${results.speedRange.min.toFixed(1)} - ${results.speedRange.max.toFixed(1)}</div>
  644. </div>
  645. <div class="result-card">
  646. <div class="result-label">连续转矩 (N·m)</div>
  647. <div class="result-value">${results.torque.continuous.toFixed(3)}</div>
  648. </div>
  649. <div class="result-card">
  650. <div class="result-label">峰值转矩 (N·m)</div>
  651. <div class="result-value">${results.torque.peak.toFixed(3)}</div>
  652. </div>
  653. <div class="result-card">
  654. <div class="result-label">负载惯量 (kg·m²)</div>
  655. <div class="result-value">${results.inertia.load.toExponential(3)}</div>
  656. </div>
  657. <div class="result-card">
  658. <div class="result-label">惯量比</div>
  659. <div class="result-value">${results.inertia.ratio.toFixed(1)}</div>
  660. </div>
  661. <div class="result-card">
  662. <div class="result-label">额定功率 (W)</div>
  663. <div class="result-value">${results.power.rated.toFixed(1)}</div>
  664. </div>
  665. </div>
  666. `;
  667. // 显示惯量比警告
  668. if (warningDiv) {
  669. if (inertiaWarning) {
  670. warningDiv.textContent = `⚠️ 警告:惯量比 (${results.inertia.ratio.toFixed(1)}) 超过允许值,建议选择更大惯量的电机或添加减速机。`;
  671. warningDiv.classList.remove('hidden');
  672. } else {
  673. warningDiv.classList.add('hidden');
  674. }
  675. }
  676. const resultsCard = document.getElementById('resultsCard');
  677. if (resultsCard) resultsCard.style.display = 'block';
  678. }
  679. updateChart(results) {
  680. const chartCanvas = document.getElementById('performanceChart');
  681. if (!chartCanvas) return;
  682. // 检查 Chart.js 是否可用
  683. if (typeof Chart === 'undefined') {
  684. console.warn('Chart.js not loaded, skipping chart rendering');
  685. return;
  686. }
  687. const ctx = chartCanvas.getContext('2d');
  688. // 销毁现有图表
  689. if (this.chart) {
  690. this.chart.destroy();
  691. }
  692. // 创建新图表
  693. this.chart = new Chart(ctx, {
  694. type: 'bar',
  695. data: {
  696. labels: ['连续转矩', '峰值转矩', '额定功率'],
  697. datasets: [{
  698. label: '性能指标',
  699. data: [
  700. results.torque.continuous,
  701. results.torque.peak,
  702. results.power.rated
  703. ],
  704. backgroundColor: [
  705. 'rgba(54, 162, 235, 0.8)',
  706. 'rgba(255, 99, 132, 0.8)',
  707. 'rgba(75, 192, 192, 0.8)'
  708. ],
  709. borderColor: [
  710. 'rgba(54, 162, 235, 1)',
  711. 'rgba(255, 99, 132, 1)',
  712. 'rgba(75, 192, 192, 1)'
  713. ],
  714. borderWidth: 1
  715. }]
  716. },
  717. options: {
  718. responsive: true,
  719. maintainAspectRatio: false,
  720. scales: {
  721. y: {
  722. beginAtZero: true,
  723. title: {
  724. display: true,
  725. text: '数值'
  726. }
  727. }
  728. },
  729. plugins: {
  730. legend: {
  731. display: false
  732. },
  733. tooltip: {
  734. callbacks: {
  735. label: function(context) {
  736. let label = context.dataset.label || '';
  737. if (label) {
  738. label += ': ';
  739. }
  740. if (context.parsed.y !== null) {
  741. label += context.parsed.y.toFixed(3);
  742. if (context.dataIndex === 2) {
  743. label += ' W';
  744. } else {
  745. label += ' N·m';
  746. }
  747. }
  748. return label;
  749. }
  750. }
  751. }
  752. }
  753. }
  754. });
  755. }
  756. clearResults() {
  757. const resultsDiv = document.getElementById('resultsDisplay');
  758. const warningDiv = document.getElementById('warningMessage');
  759. const errorDiv = document.getElementById('errorMessage');
  760. if (resultsDiv) {
  761. resultsDiv.innerHTML = '<p class="placeholder">请输入参数并点击计算获取结果</p>';
  762. }
  763. if (warningDiv) warningDiv.classList.add('hidden');
  764. if (errorDiv) errorDiv.classList.add('hidden');
  765. // 销毁图表
  766. if (this.chart) {
  767. this.chart.destroy();
  768. this.chart = null;
  769. }
  770. }
  771. resetForm() {
  772. // 重置所有输入框
  773. document.querySelectorAll('input[type="number"]').forEach(input => {
  774. input.value = input.defaultValue || '';
  775. input.classList.remove('error');
  776. });
  777. // 重置选择框
  778. const transmissionSelect = document.getElementById('transmissionType');
  779. if (transmissionSelect) {
  780. transmissionSelect.selectedIndex = 0;
  781. this.currentType = 'ballscrew';
  782. this.updateParametersForm();
  783. }
  784. this.clearResults();
  785. this.showAlert('表单已重置', 'info');
  786. }
  787. exportResults() {
  788. const resultsDiv = document.getElementById('resultsDisplay');
  789. if (!resultsDiv || resultsDiv.querySelector('.placeholder')) {
  790. this.showAlert('请先进行计算', 'warning');
  791. return;
  792. }
  793. const resultsText = resultsDiv.innerText;
  794. const blob = new Blob([resultsText], { type: 'text/plain' });
  795. const url = URL.createObjectURL(blob);
  796. const a = document.createElement('a');
  797. a.href = url;
  798. a.download = `servo_sizing_results_${new Date().toISOString().slice(0, 10)}.txt`;
  799. document.body.appendChild(a);
  800. a.click();
  801. document.body.removeChild(a);
  802. URL.revokeObjectURL(url);
  803. this.showAlert('结果已导出', 'success');
  804. }
  805. showAlert(message, type = 'info') {
  806. const alertContainer = document.getElementById('alertContainer');
  807. if (!alertContainer) return;
  808. const alertDiv = document.createElement('div');
  809. alertDiv.className = `alert alert-${type}`;
  810. alertDiv.textContent = message;
  811. alertContainer.appendChild(alertDiv);
  812. setTimeout(() => {
  813. alertDiv.remove();
  814. }, 3000);
  815. }
  816. }
  817. // 初始化应用 - 确保在 DOM 加载完成后执行
  818. document.addEventListener('DOMContentLoaded', () => {
  819. new ServoSizer();
  820. });