cyclecad 3.11.0 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/index.html +42 -11
- package/app/js/modules/ai-copilot.js +4 -1
- package/app/js/modules/ai-engineer-rag.js +689 -0
- package/app/js/modules/ai-engineer.js +855 -60
- package/app/js/modules/text-to-cad.js +561 -33
- package/cyclecad.html +1081 -0
- package/explodeview.html +1102 -0
- package/index.html +1683 -1240
- package/package.json +1 -1
- package/pentacad.html +1097 -0
- package/server/converter.py +528 -0
|
@@ -179,6 +179,466 @@
|
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
// ===================================================================
|
|
183
|
+
// GEAR MATERIAL DATA — AGMA Grade 1 steel allowables (Shigley Table 14-3 / 14-6)
|
|
184
|
+
// Keys: hardness in HB (Brinell). Values: {S_t, S_c} in MPa (converted from psi).
|
|
185
|
+
// S_t: allowable bending stress. S_c: allowable contact (surface) stress.
|
|
186
|
+
// Formulas for through-hardened steel (AGMA 2001):
|
|
187
|
+
// S_t = 77 * HB + 12,800 psi (Grade 1 bending)
|
|
188
|
+
// S_c = 322 * HB + 29,100 psi (Grade 1 contact)
|
|
189
|
+
// 1 psi = 0.00689476 MPa.
|
|
190
|
+
// ===================================================================
|
|
191
|
+
/**
|
|
192
|
+
* Allowable bending stress for Grade 1 through-hardened gear steel (AGMA 2001).
|
|
193
|
+
* @param {number} HB Brinell hardness (180–400 typical).
|
|
194
|
+
* @returns {number} S_t in MPa.
|
|
195
|
+
*/
|
|
196
|
+
function gearAllowableBending(HB) {
|
|
197
|
+
return (77 * HB + 12800) * 0.00689476;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Allowable contact stress for Grade 1 through-hardened gear steel (AGMA 2001).
|
|
201
|
+
* @param {number} HB Brinell hardness.
|
|
202
|
+
* @returns {number} S_c in MPa.
|
|
203
|
+
*/
|
|
204
|
+
function gearAllowableContact(HB) {
|
|
205
|
+
return (322 * HB + 29100) * 0.00689476;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// AGMA geometry factor J (bending) — approximated from Shigley Fig. 14-6
|
|
209
|
+
// for external spur gears at 20° pressure angle. Interpolated by number of teeth.
|
|
210
|
+
const J_TABLE = [
|
|
211
|
+
[12, 0.245], [14, 0.265], [17, 0.295], [20, 0.32], [25, 0.345],
|
|
212
|
+
[30, 0.365], [35, 0.38], [40, 0.39], [50, 0.41], [75, 0.435], [100, 0.45]
|
|
213
|
+
];
|
|
214
|
+
function gearGeometryJ(teeth) {
|
|
215
|
+
const z = Math.max(J_TABLE[0][0], Math.min(J_TABLE[J_TABLE.length-1][0], teeth));
|
|
216
|
+
for (let i = 0; i < J_TABLE.length - 1; i++) {
|
|
217
|
+
const [z1, j1] = J_TABLE[i], [z2, j2] = J_TABLE[i+1];
|
|
218
|
+
if (z >= z1 && z <= z2) return j1 + (j2 - j1) * (z - z1) / (z2 - z1);
|
|
219
|
+
}
|
|
220
|
+
return 0.4;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// AGMA geometry factor I (pitting) — external gear pair, 20° pressure angle.
|
|
224
|
+
// Approximated from Shigley Eq. 14-23 with m_N = 1 for spur gears.
|
|
225
|
+
function gearGeometryI(pinionTeeth, gearTeeth, pressureAngle) {
|
|
226
|
+
const phi = (pressureAngle || 20) * Math.PI / 180;
|
|
227
|
+
const mG = gearTeeth / pinionTeeth;
|
|
228
|
+
return (Math.cos(phi) * Math.sin(phi) / 2) * (mG / (mG + 1));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Spur gear AGMA bending + pitting analysis (Shigley Ch. 14).
|
|
233
|
+
*
|
|
234
|
+
* Uses the fundamental AGMA 2001 stress equations with sensible default modifying factors.
|
|
235
|
+
* For safety-critical applications, confirm each K-factor per AGMA 908-B89.
|
|
236
|
+
*
|
|
237
|
+
* @param {object} p
|
|
238
|
+
* @param {number} p.pinionTeeth z_P — pinion tooth count.
|
|
239
|
+
* @param {number} p.gearTeeth z_G — gear tooth count.
|
|
240
|
+
* @param {number} p.module m — in mm.
|
|
241
|
+
* @param {number} p.faceWidth F — in mm.
|
|
242
|
+
* @param {number} p.torque T_P — torque on pinion in N·m.
|
|
243
|
+
* @param {number} p.pinionHB HB_P — Brinell hardness of pinion.
|
|
244
|
+
* @param {number} p.gearHB HB_G — Brinell hardness of gear.
|
|
245
|
+
* @param {number} [p.overload=1.0] K_o — 1.0 uniform / 1.25 moderate / 1.5 heavy shock.
|
|
246
|
+
* @param {number} [p.dynamic=1.1] K_v — dynamic factor (≈1.0 precision, 1.1–1.3 typical).
|
|
247
|
+
* @param {number} [p.loadDist=1.3] K_m — load distribution (1.3 good alignment).
|
|
248
|
+
* @param {number} [p.reliability=1.0] K_R — 1.0 @ 99% reliability, 1.25 @ 99.9%.
|
|
249
|
+
* @param {number} [p.pressureAngle=20] φ — pressure angle in degrees.
|
|
250
|
+
* @param {number} [p.Z_E=190] Elastic coefficient for steel-steel (MPa^0.5).
|
|
251
|
+
* @returns {object} {inputs, pinion:{…}, gear:{…}, verdict, …}
|
|
252
|
+
*/
|
|
253
|
+
function spurGearAnalysis(p) {
|
|
254
|
+
const z_P = Math.max(12, Math.round(Number(p.pinionTeeth) || 20));
|
|
255
|
+
const z_G = Math.max(12, Math.round(Number(p.gearTeeth) || 40));
|
|
256
|
+
const m = Math.max(0.5, Number(p.module) || 2);
|
|
257
|
+
const F = Math.max(3, Number(p.faceWidth) || 25);
|
|
258
|
+
const T_P = Math.max(0, Number(p.torque) || 0);
|
|
259
|
+
const HB_P = Math.max(150, Number(p.pinionHB) || 240);
|
|
260
|
+
const HB_G = Math.max(150, Number(p.gearHB) || HB_P);
|
|
261
|
+
const K_o = Math.max(1, Number(p.overload) || 1.0);
|
|
262
|
+
const K_v = Math.max(1, Number(p.dynamic) || 1.1);
|
|
263
|
+
const K_m = Math.max(1, Number(p.loadDist) || 1.3);
|
|
264
|
+
const K_R = Math.max(1, Number(p.reliability) || 1.0);
|
|
265
|
+
const phi = Number(p.pressureAngle) || 20;
|
|
266
|
+
const Z_E = Number(p.Z_E) || 190;
|
|
267
|
+
const K_s = 1.0; // size factor — ignore for typical sizes
|
|
268
|
+
const K_B = 1.0; // rim thickness — solid blank
|
|
269
|
+
const C_f = 1.0; // surface condition — clean-cut
|
|
270
|
+
const Y_N = 1.0, Z_N = 1.0, C_H = 1.0, K_T = 1.0; // nominal life factors
|
|
271
|
+
const mG = z_G / z_P;
|
|
272
|
+
|
|
273
|
+
// Pitch diameters and tangential load
|
|
274
|
+
const d_P = m * z_P; // pinion pitch dia in mm
|
|
275
|
+
const d_G = m * z_G; // gear pitch dia
|
|
276
|
+
const T_Pnmm = T_P * 1000; // convert N·m → N·mm
|
|
277
|
+
const W_t = (d_P > 0) ? (2 * T_Pnmm) / d_P : 0; // tangential load N
|
|
278
|
+
|
|
279
|
+
// Geometry factors
|
|
280
|
+
const J_P = gearGeometryJ(z_P);
|
|
281
|
+
const J_G = gearGeometryJ(z_G);
|
|
282
|
+
const I = gearGeometryI(z_P, z_G, phi);
|
|
283
|
+
|
|
284
|
+
// AGMA bending stress (both gears see same W_t but different J)
|
|
285
|
+
const sigma_b_P = (W_t * K_o * K_v * K_s * K_m * K_B) / (F * m * J_P);
|
|
286
|
+
const sigma_b_G = (W_t * K_o * K_v * K_s * K_m * K_B) / (F * m * J_G);
|
|
287
|
+
|
|
288
|
+
// AGMA contact stress (same magnitude for both meshing teeth)
|
|
289
|
+
const sigma_c = (W_t > 0 && d_P > 0 && I > 0)
|
|
290
|
+
? Z_E * Math.sqrt((W_t * K_o * K_v * K_s * K_m * C_f) / (d_P * F * I))
|
|
291
|
+
: 0;
|
|
292
|
+
|
|
293
|
+
// Allowable stresses (material)
|
|
294
|
+
const St_P = gearAllowableBending(HB_P);
|
|
295
|
+
const St_G = gearAllowableBending(HB_G);
|
|
296
|
+
const Sc_P = gearAllowableContact(HB_P);
|
|
297
|
+
const Sc_G = gearAllowableContact(HB_G);
|
|
298
|
+
|
|
299
|
+
// Factors of safety — Shigley Eq. 14-41 / 14-42 simplified
|
|
300
|
+
const SF_bending_P = St_P * Y_N / (sigma_b_P * K_T * K_R);
|
|
301
|
+
const SF_bending_G = St_G * Y_N / (sigma_b_G * K_T * K_R);
|
|
302
|
+
const SF_contact_P = (Sc_P * Z_N * C_H) / (sigma_c * K_T * K_R);
|
|
303
|
+
const SF_contact_G = (Sc_G * Z_N * C_H) / (sigma_c * K_T * K_R);
|
|
304
|
+
|
|
305
|
+
const SF_min = Math.min(SF_bending_P, SF_bending_G, SF_contact_P, SF_contact_G);
|
|
306
|
+
const safe = SF_min >= 1.0;
|
|
307
|
+
const verdict = SF_min >= 2.0 ? 'SAFE (margin ≥ 2)' :
|
|
308
|
+
SF_min >= 1.5 ? 'SAFE (margin ≥ 1.5 — industry typical)' :
|
|
309
|
+
SF_min >= 1.0 ? 'MARGINAL (factor < 1.5 — review assumptions)' :
|
|
310
|
+
'UNSAFE (factor < 1.0 — tooth will fail)';
|
|
311
|
+
const verdictClass = SF_min >= 1.5 ? 'pass' : SF_min >= 1.0 ? 'warn' : 'fail';
|
|
312
|
+
const notes = [];
|
|
313
|
+
if (SF_bending_P < SF_bending_G) notes.push('Pinion is the weaker gear in bending (lower J factor) — as expected.');
|
|
314
|
+
if (SF_contact_P < SF_bending_P) notes.push('Contact stress governs over bending — consider surface hardening (carburizing/induction) for higher S_c.');
|
|
315
|
+
if (SF_min < 1.5) notes.push('Margin below industry-typical 1.5 — increase module, face width, or hardness.');
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
inputs: { z_P, z_G, m, F, T_P, HB_P, HB_G, K_o, K_v, K_m, K_R, phi, Z_E,
|
|
319
|
+
d_P, d_G, mG, W_t, J_P, J_G, I, St_P, St_G, Sc_P, Sc_G },
|
|
320
|
+
pinion: { SF_bending: SF_bending_P, SF_contact: SF_contact_P,
|
|
321
|
+
sigma_b: sigma_b_P, sigma_c, S_t: St_P, S_c: Sc_P, J: J_P },
|
|
322
|
+
gear: { SF_bending: SF_bending_G, SF_contact: SF_contact_G,
|
|
323
|
+
sigma_b: sigma_b_G, sigma_c, S_t: St_G, S_c: Sc_G, J: J_G },
|
|
324
|
+
SF_min, safe, verdict, verdictClass, notes
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ===================================================================
|
|
329
|
+
// SHAFT FATIGUE — Goodman / Soderberg (Shigley Ch. 7)
|
|
330
|
+
// ===================================================================
|
|
331
|
+
// Shaft material data (wrought carbon steel from Shigley Table A-20).
|
|
332
|
+
const SHAFT_MATERIALS = Object.freeze({
|
|
333
|
+
'1020_hr': { label: 'AISI 1020 hot-rolled', S_ut: 380, S_y: 210 },
|
|
334
|
+
'1020_cd': { label: 'AISI 1020 cold-drawn', S_ut: 470, S_y: 390 },
|
|
335
|
+
'1040_hr': { label: 'AISI 1040 hot-rolled', S_ut: 520, S_y: 290 },
|
|
336
|
+
'1040_cd': { label: 'AISI 1040 cold-drawn', S_ut: 590, S_y: 490 },
|
|
337
|
+
'1050_hr': { label: 'AISI 1050 hot-rolled', S_ut: 620, S_y: 340 },
|
|
338
|
+
'1050_cd': { label: 'AISI 1050 cold-drawn', S_ut: 690, S_y: 580 },
|
|
339
|
+
'4140_Q&T': { label: 'AISI 4140 Q&T 425°C', S_ut: 1020, S_y: 900 },
|
|
340
|
+
'4340_Q&T': { label: 'AISI 4340 Q&T 425°C', S_ut: 1280, S_y: 1140 }
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Marin surface factor — Shigley Eq. 6-19, a*S_ut^b.
|
|
344
|
+
const SURFACE_FACTORS = {
|
|
345
|
+
'ground': { a: 1.58, b: -0.085 },
|
|
346
|
+
'machined': { a: 4.51, b: -0.265 },
|
|
347
|
+
'cold-drawn': { a: 4.51, b: -0.265 },
|
|
348
|
+
'hot-rolled': { a: 57.7, b: -0.718 },
|
|
349
|
+
'as-forged': { a: 272, b: -0.995 }
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Shaft fatigue analysis using Goodman / Soderberg criteria.
|
|
354
|
+
*
|
|
355
|
+
* Given mean and alternating stresses (computed from bending moment + torque amplitudes),
|
|
356
|
+
* applies Marin modifying factors to estimate the endurance limit and returns factors of
|
|
357
|
+
* safety per Goodman (common) and Soderberg (conservative).
|
|
358
|
+
*
|
|
359
|
+
* @param {object} p
|
|
360
|
+
* @param {string} p.material Key into SHAFT_MATERIALS.
|
|
361
|
+
* @param {number} p.diameter Shaft diameter in mm.
|
|
362
|
+
* @param {number} p.M_a Alternating bending moment amplitude in N·m.
|
|
363
|
+
* @param {number} p.M_m Mean bending moment in N·m (often 0 for rotating-bending).
|
|
364
|
+
* @param {number} p.T_a Alternating torque amplitude in N·m.
|
|
365
|
+
* @param {number} p.T_m Mean torque in N·m.
|
|
366
|
+
* @param {number} [p.Kf=2.0] Fatigue stress concentration for bending (≥1).
|
|
367
|
+
* @param {number} [p.Kfs=1.5] Fatigue stress concentration for torsion.
|
|
368
|
+
* @param {string} [p.surface='machined'] Surface finish preset.
|
|
369
|
+
* @param {number} [p.reliability=0.99] Reliability (0.5–0.999999).
|
|
370
|
+
* @param {number} [p.temperatureC=25] Operating temperature in °C.
|
|
371
|
+
* @returns {object}
|
|
372
|
+
*/
|
|
373
|
+
function shaftFatigueAnalysis(p) {
|
|
374
|
+
const mat = SHAFT_MATERIALS[p.material] || SHAFT_MATERIALS['1050_cd'];
|
|
375
|
+
const d = Math.max(5, Number(p.diameter) || 25); // mm
|
|
376
|
+
const M_a = Math.max(0, Number(p.M_a) || 0); // N·m
|
|
377
|
+
const M_m = Math.max(0, Number(p.M_m) || 0);
|
|
378
|
+
const T_a = Math.max(0, Number(p.T_a) || 0);
|
|
379
|
+
const T_m = Math.max(0, Number(p.T_m) || 0);
|
|
380
|
+
const Kf = Math.max(1, Number(p.Kf) || 2.0);
|
|
381
|
+
const Kfs = Math.max(1, Number(p.Kfs) || 1.5);
|
|
382
|
+
const surfaceKey = p.surface || 'machined';
|
|
383
|
+
const surf = SURFACE_FACTORS[surfaceKey] || SURFACE_FACTORS.machined;
|
|
384
|
+
const R = Math.max(0.5, Math.min(0.999999, Number(p.reliability) || 0.99));
|
|
385
|
+
const T_C = Number(p.temperatureC) || 25;
|
|
386
|
+
|
|
387
|
+
// Marin surface factor k_a = a * (S_ut[MPa])^b
|
|
388
|
+
const k_a = surf.a * Math.pow(mat.S_ut, surf.b);
|
|
389
|
+
// Size factor k_b — Shigley Eq. 6-20 (rotating bending, 2.79 ≤ d ≤ 51 mm)
|
|
390
|
+
let k_b;
|
|
391
|
+
if (d <= 51) k_b = 1.24 * Math.pow(d, -0.107);
|
|
392
|
+
else if (d <= 254) k_b = 1.51 * Math.pow(d, -0.157);
|
|
393
|
+
else k_b = 0.60;
|
|
394
|
+
// Loading factor k_c = 1 for combined bending + torsion (we handle each via Kf/Kfs)
|
|
395
|
+
const k_c = 1.0;
|
|
396
|
+
// Temperature factor k_d — Eq. 6-27
|
|
397
|
+
const k_d = (T_C <= 70) ? 1.0 : (0.975 + 0.432e-3*T_C - 0.115e-5*T_C*T_C + 0.104e-8*T_C*T_C*T_C);
|
|
398
|
+
// Reliability factor k_e — Shigley Table 6-5 (z_a from normal distribution)
|
|
399
|
+
const z_a = (R === 0.99 ? 2.326 : R === 0.999 ? 3.091 : R === 0.95 ? 1.645 : R === 0.90 ? 1.288 : 2.326);
|
|
400
|
+
const k_e = 1 - 0.08 * z_a;
|
|
401
|
+
// Miscellaneous k_f = 1 for this scope
|
|
402
|
+
const k_f = 1.0;
|
|
403
|
+
// Uncorrected endurance limit S_e' — Shigley Eq. 6-10
|
|
404
|
+
const S_e_prime = (mat.S_ut <= 1400) ? 0.5 * mat.S_ut : 700;
|
|
405
|
+
// Corrected endurance limit
|
|
406
|
+
const S_e = k_a * k_b * k_c * k_d * k_e * k_f * S_e_prime;
|
|
407
|
+
|
|
408
|
+
// Stresses — bending σ_a/σ_m (Kf applied), torsion τ_a/τ_m (Kfs applied)
|
|
409
|
+
// σ = 32·M / (π·d^3) [M in N·mm, d in mm → MPa]
|
|
410
|
+
// τ = 16·T / (π·d^3)
|
|
411
|
+
const d_m = d; // mm
|
|
412
|
+
const sigma_a = (32 * M_a * 1000) / (Math.PI * Math.pow(d_m, 3));
|
|
413
|
+
const sigma_m = (32 * M_m * 1000) / (Math.PI * Math.pow(d_m, 3));
|
|
414
|
+
const tau_a = (16 * T_a * 1000) / (Math.PI * Math.pow(d_m, 3));
|
|
415
|
+
const tau_m = (16 * T_m * 1000) / (Math.PI * Math.pow(d_m, 3));
|
|
416
|
+
|
|
417
|
+
// Von Mises effective stresses (amplitude + mean) with fatigue concentrations
|
|
418
|
+
const sigma_prime_a = Math.sqrt(Math.pow(Kf * sigma_a, 2) + 3 * Math.pow(Kfs * tau_a, 2));
|
|
419
|
+
const sigma_prime_m = Math.sqrt(Math.pow(Kf * sigma_m, 2) + 3 * Math.pow(Kfs * tau_m, 2));
|
|
420
|
+
|
|
421
|
+
// Goodman: 1/n = σ_a'/S_e + σ_m'/S_ut
|
|
422
|
+
const goodmanInv = (sigma_prime_a / S_e) + (sigma_prime_m / mat.S_ut);
|
|
423
|
+
const n_Goodman = goodmanInv > 0 ? 1 / goodmanInv : Infinity;
|
|
424
|
+
|
|
425
|
+
// Soderberg: 1/n = σ_a'/S_e + σ_m'/S_y (conservative — yields instead of UTS)
|
|
426
|
+
const soderbergInv = (sigma_prime_a / S_e) + (sigma_prime_m / mat.S_y);
|
|
427
|
+
const n_Soderberg = soderbergInv > 0 ? 1 / soderbergInv : Infinity;
|
|
428
|
+
|
|
429
|
+
// First-cycle yield check — static
|
|
430
|
+
const sigma_max = sigma_prime_a + sigma_prime_m;
|
|
431
|
+
const n_yield = mat.S_y / sigma_max;
|
|
432
|
+
|
|
433
|
+
// Verdict
|
|
434
|
+
const n_fatigue = Math.min(n_Goodman, n_Soderberg);
|
|
435
|
+
let verdict, verdictClass;
|
|
436
|
+
if (n_fatigue >= 2.0 && n_yield >= 2.0) { verdict = 'SAFE (margin ≥ 2)'; verdictClass = 'pass'; }
|
|
437
|
+
else if (n_fatigue >= 1.5 && n_yield >= 1.5) { verdict = 'SAFE (industry typical)'; verdictClass = 'pass'; }
|
|
438
|
+
else if (n_fatigue >= 1.0 && n_yield >= 1.0) { verdict = 'MARGINAL — factor below 1.5'; verdictClass = 'warn'; }
|
|
439
|
+
else { verdict = 'UNSAFE — factor < 1.0'; verdictClass = 'fail'; }
|
|
440
|
+
const notes = [];
|
|
441
|
+
if (n_yield < n_fatigue) notes.push('First-cycle yield governs — increase diameter or use higher-strength material.');
|
|
442
|
+
if (sigma_prime_m > sigma_prime_a) notes.push('Mean stress dominant — consider rotating-bending to convert static to fully-reversed.');
|
|
443
|
+
if (n_Goodman > n_Soderberg + 0.3) notes.push('Soderberg (conservative) significantly lower — review if proof strength, not UTS, is the correct allowable.');
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
inputs: { material: p.material || '1050_cd', material_label: mat.label, d, S_ut: mat.S_ut, S_y: mat.S_y,
|
|
447
|
+
M_a, M_m, T_a, T_m, Kf, Kfs, surfaceKey, reliability: R, temperatureC: T_C },
|
|
448
|
+
marin: { k_a, k_b, k_c, k_d, k_e, k_f, S_e_prime, S_e },
|
|
449
|
+
stresses: { sigma_a, sigma_m, tau_a, tau_m, sigma_prime_a, sigma_prime_m, sigma_max },
|
|
450
|
+
n_Goodman, n_Soderberg, n_yield,
|
|
451
|
+
verdict, verdictClass, notes
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ===================================================================
|
|
456
|
+
// ROLLING-ELEMENT BEARING LIFE — ISO 281 / Shigley Ch. 11
|
|
457
|
+
// ===================================================================
|
|
458
|
+
// L_10 = (C / P)^a × 10^6 rev
|
|
459
|
+
// a = 3 for ball bearings, 10/3 for roller bearings.
|
|
460
|
+
// C = basic dynamic load rating (N), bearing-specific.
|
|
461
|
+
// P = equivalent dynamic load (N), P = X·F_r + Y·F_a for combined loading.
|
|
462
|
+
// L_h = L_10 / (60 × N) in hours at N rpm.
|
|
463
|
+
//
|
|
464
|
+
// Reference dynamic load ratings C (N) for deep-groove ball bearings (SKF catalogue).
|
|
465
|
+
const BEARING_CATALOGUE = Object.freeze({
|
|
466
|
+
'608': { type: 'ball', d: 8, D: 22, B: 7, C: 3.45e3 },
|
|
467
|
+
'625': { type: 'ball', d: 5, D: 16, B: 5, C: 1.85e3 },
|
|
468
|
+
'6200': { type: 'ball', d: 10, D: 30, B: 9, C: 5.4e3 },
|
|
469
|
+
'6201': { type: 'ball', d: 12, D: 32, B: 10, C: 6.89e3 },
|
|
470
|
+
'6202': { type: 'ball', d: 15, D: 35, B: 11, C: 7.8e3 },
|
|
471
|
+
'6203': { type: 'ball', d: 17, D: 40, B: 12, C: 9.56e3 },
|
|
472
|
+
'6204': { type: 'ball', d: 20, D: 47, B: 14, C: 13.5e3 },
|
|
473
|
+
'6205': { type: 'ball', d: 25, D: 52, B: 15, C: 14.0e3 },
|
|
474
|
+
'6206': { type: 'ball', d: 30, D: 62, B: 16, C: 19.5e3 },
|
|
475
|
+
'6300': { type: 'ball', d: 10, D: 35, B: 11, C: 8.06e3 },
|
|
476
|
+
'6302': { type: 'ball', d: 15, D: 42, B: 13, C: 11.9e3 },
|
|
477
|
+
'6304': { type: 'ball', d: 20, D: 52, B: 15, C: 15.9e3 },
|
|
478
|
+
'NJ204':{ type: 'roller', d: 20, D: 47, B: 14, C: 25.1e3 },
|
|
479
|
+
'NJ206':{ type: 'roller', d: 30, D: 62, B: 16, C: 44e3 }
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Rolling-element bearing L10 life analysis.
|
|
484
|
+
*
|
|
485
|
+
* Supports two input modes:
|
|
486
|
+
* (a) Designation ("6204") — looks up C from catalogue.
|
|
487
|
+
* (b) Explicit C override (N).
|
|
488
|
+
*
|
|
489
|
+
* Applies X/Y factors per ISO 281 to compute equivalent dynamic load P from radial F_r
|
|
490
|
+
* and axial F_a forces. Falls back to pure-radial (P = F_r) if F_a not given.
|
|
491
|
+
*
|
|
492
|
+
* @param {object} p
|
|
493
|
+
* @param {string} [p.designation] Bearing code, e.g. '6204'.
|
|
494
|
+
* @param {number} [p.C] Basic dynamic load rating override in N.
|
|
495
|
+
* @param {'ball'|'roller'} [p.type] Exponent selector (default 'ball').
|
|
496
|
+
* @param {number} p.radialLoad F_r in N.
|
|
497
|
+
* @param {number} [p.axialLoad=0] F_a in N.
|
|
498
|
+
* @param {number} p.rpm Rotational speed N (rev/min).
|
|
499
|
+
* @param {number} [p.X=1] Radial factor (0.56 for F_a/F_r > e; use default for pure radial).
|
|
500
|
+
* @param {number} [p.Y=0] Thrust factor.
|
|
501
|
+
* @param {number} [p.reliability=0.90] Life adjustment reliability (0.90 ≡ L10).
|
|
502
|
+
* @returns {object}
|
|
503
|
+
*/
|
|
504
|
+
function bearingLifeAnalysis(p) {
|
|
505
|
+
const catalog = p.designation ? BEARING_CATALOGUE[String(p.designation)] : null;
|
|
506
|
+
const type = (p.type || (catalog && catalog.type) || 'ball');
|
|
507
|
+
const a = type === 'roller' ? 10/3 : 3;
|
|
508
|
+
const C = Number(p.C) || (catalog ? catalog.C : 0);
|
|
509
|
+
const F_r = Math.max(0, Number(p.radialLoad) || 0);
|
|
510
|
+
const F_a = Math.max(0, Number(p.axialLoad) || 0);
|
|
511
|
+
const rpm = Math.max(1, Number(p.rpm) || 1);
|
|
512
|
+
const X = Number.isFinite(p.X) ? p.X : (F_a > 0 ? 0.56 : 1);
|
|
513
|
+
const Y = Number.isFinite(p.Y) ? p.Y : (F_a > 0 ? 1.2 : 0);
|
|
514
|
+
const R = Math.max(0.5, Math.min(0.999, Number(p.reliability) || 0.9));
|
|
515
|
+
|
|
516
|
+
if (!C || C <= 0) {
|
|
517
|
+
return {
|
|
518
|
+
inputs: { designation: p.designation, type, C: 0, F_r, F_a, rpm },
|
|
519
|
+
error: 'Missing or invalid dynamic load rating C — provide p.C in N or a recognised p.designation.',
|
|
520
|
+
verdict: 'INPUT ERROR', verdictClass: 'fail'
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Equivalent dynamic load P (N)
|
|
525
|
+
const P = X * F_r + Y * F_a;
|
|
526
|
+
// L10 life (millions of revolutions)
|
|
527
|
+
const L10_rev = Math.pow(C / Math.max(1, P), a); // in 10^6 rev
|
|
528
|
+
// Convert to hours at rpm
|
|
529
|
+
const L10_h = (L10_rev * 1e6) / (60 * rpm);
|
|
530
|
+
// Life adjustment for reliability — Shigley Eq 11-12 (Weibull): L_R = L10 × (ln(1/R) / ln(1/0.9))^(1/1.483)
|
|
531
|
+
const a_R = Math.pow(Math.log(1/R) / Math.log(1/0.9), 1/1.483);
|
|
532
|
+
const L_R_rev = L10_rev * a_R;
|
|
533
|
+
const L_R_h = L10_h * a_R;
|
|
534
|
+
|
|
535
|
+
// Verdict benchmarks (hours):
|
|
536
|
+
// • < 5 000 h → short life
|
|
537
|
+
// • 5 000–20 000 h → typical industrial
|
|
538
|
+
// • > 20 000 h → long life
|
|
539
|
+
let verdict, verdictClass;
|
|
540
|
+
if (L_R_h >= 20000) { verdict = 'LONG LIFE (> 20,000 h)'; verdictClass = 'pass'; }
|
|
541
|
+
else if (L_R_h >= 5000) { verdict = 'TYPICAL INDUSTRIAL (5k–20k h)'; verdictClass = 'pass'; }
|
|
542
|
+
else if (L_R_h >= 1000) { verdict = 'SHORT LIFE (< 5,000 h) — review'; verdictClass = 'warn'; }
|
|
543
|
+
else { verdict = 'VERY SHORT LIFE — resize bearing up'; verdictClass = 'fail'; }
|
|
544
|
+
const notes = [];
|
|
545
|
+
if (F_a > F_r && Y === 0) notes.push('Axial load ignored — explicit X/Y factors needed for thrust-dominated duty.');
|
|
546
|
+
if (P > C * 0.5) notes.push('Load exceeds 50% of C — consider a larger bearing to extend life dramatically.');
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
inputs: { designation: p.designation, type, a, C, F_r, F_a, rpm, X, Y, reliability: R, catalog },
|
|
550
|
+
P,
|
|
551
|
+
L10_rev, L10_h,
|
|
552
|
+
L_R_rev, L_R_h, a_R,
|
|
553
|
+
verdict, verdictClass, notes
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ===================================================================
|
|
558
|
+
// FILLET WELD — AWS D1.1 / Shigley Ch. 9 throat-stress analysis
|
|
559
|
+
// ===================================================================
|
|
560
|
+
// Throat thickness t = 0.707 · h (for 45° equal-leg fillet).
|
|
561
|
+
// Direct throat stress: τ = F / (t · L) for load perpendicular to weld line
|
|
562
|
+
// Shear + bending: combined per load case; here we expose two modes:
|
|
563
|
+
// 'transverse' (load pulls perpendicular to weld):
|
|
564
|
+
// σ = F / (0.707 · h · L) with a 1/√2 conversion for nominal direct stress
|
|
565
|
+
// 'longitudinal' (load parallel to weld — pure shear):
|
|
566
|
+
// τ = F / (0.707 · h · L)
|
|
567
|
+
// Allowable = 0.30 · S_ut_electrode (AWS D1.1 static).
|
|
568
|
+
// For cyclic loading: fatigue factor ~0.5 of static allowable (conservative).
|
|
569
|
+
//
|
|
570
|
+
// Reference electrode ultimate tensile strengths (MPa, converted from AWS E-class ksi):
|
|
571
|
+
const WELD_ELECTRODES = Object.freeze({
|
|
572
|
+
'E60': { S_ut: 414, label: 'E60 (60 ksi UTS)', standardClass: 'AWS D1.1 Table 2.5' },
|
|
573
|
+
'E70': { S_ut: 482, label: 'E70 (70 ksi UTS — most common)', standardClass: 'AWS D1.1 Table 2.5' },
|
|
574
|
+
'E80': { S_ut: 552, label: 'E80 (80 ksi UTS)', standardClass: 'AWS D1.1 Table 2.5' },
|
|
575
|
+
'E90': { S_ut: 620, label: 'E90 (90 ksi UTS)', standardClass: 'AWS D1.1 Table 2.5' },
|
|
576
|
+
'E100': { S_ut: 689, label: 'E100 (100 ksi UTS)', standardClass: 'AWS D1.1 Table 2.5' },
|
|
577
|
+
'E110': { S_ut: 758, label: 'E110 (110 ksi UTS)', standardClass: 'AWS D1.1 Table 2.5' }
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Fillet weld throat-stress analysis (AWS D1.1 static + optional cyclic derating).
|
|
582
|
+
*
|
|
583
|
+
* @param {object} p
|
|
584
|
+
* @param {number} p.legSize h — fillet leg size in mm.
|
|
585
|
+
* @param {number} p.length L — total weld length in mm (sum of all fillet segments carrying load).
|
|
586
|
+
* @param {number} p.force F — applied load in N.
|
|
587
|
+
* @param {'transverse'|'longitudinal'|'combined'} [p.loadDirection='transverse'] — Direction relative to weld line.
|
|
588
|
+
* @param {string} [p.electrode='E70'] — AWS electrode class (E60/E70/E80/E90/E100/E110).
|
|
589
|
+
* @param {boolean} [p.cyclic=false] — Apply 0.5× fatigue derate to the allowable.
|
|
590
|
+
* @param {number} [p.safetyFactor=1.0] — Additional user-specified SF on top of allowable.
|
|
591
|
+
* @returns {object}
|
|
592
|
+
*/
|
|
593
|
+
function filletWeldAnalysis(p) {
|
|
594
|
+
const h = Math.max(1, Number(p.legSize) || 0);
|
|
595
|
+
const L = Math.max(1, Number(p.length) || 0);
|
|
596
|
+
const F = Math.max(0, Number(p.force) || 0);
|
|
597
|
+
const direction = (p.loadDirection || 'transverse').toLowerCase();
|
|
598
|
+
const elecKey = p.electrode || 'E70';
|
|
599
|
+
const elec = WELD_ELECTRODES[elecKey] || WELD_ELECTRODES.E70;
|
|
600
|
+
const cyclic = !!p.cyclic;
|
|
601
|
+
const sf = Math.max(1, Number(p.safetyFactor) || 1.0);
|
|
602
|
+
|
|
603
|
+
// Throat area (mm²)
|
|
604
|
+
const t = 0.707 * h;
|
|
605
|
+
const A = t * L;
|
|
606
|
+
// Throat stress (MPa = N/mm²). For combined, use resultant of longitudinal shear + transverse bending.
|
|
607
|
+
// For this v1 treat 'transverse' and 'longitudinal' identically (same throat area);
|
|
608
|
+
// the nominal allowable per AWS is direction-independent for static design.
|
|
609
|
+
const tau = A > 0 ? F / A : 0;
|
|
610
|
+
|
|
611
|
+
// Allowable strength — AWS D1.1 static allowable is 0.30 × S_ut_electrode for fillet welds in shear.
|
|
612
|
+
let allowable = 0.30 * elec.S_ut;
|
|
613
|
+
if (cyclic) allowable *= 0.5; // fatigue derate (generic conservative)
|
|
614
|
+
|
|
615
|
+
const capacity = allowable * A; // N
|
|
616
|
+
const utilisation = tau / allowable; // 0..1 ideally
|
|
617
|
+
const safetyFactor = allowable / (tau * sf);
|
|
618
|
+
|
|
619
|
+
let verdict, verdictClass;
|
|
620
|
+
if (safetyFactor >= 2.0) { verdict = 'SAFE (margin ≥ 2)'; verdictClass = 'pass'; }
|
|
621
|
+
else if (safetyFactor >= 1.5) { verdict = 'SAFE (industry typical — margin ≥ 1.5)'; verdictClass = 'pass'; }
|
|
622
|
+
else if (safetyFactor >= 1.0) { verdict = 'MARGINAL (margin < 1.5)'; verdictClass = 'warn'; }
|
|
623
|
+
else { verdict = 'UNSAFE (weld will fail at design load)'; verdictClass = 'fail'; }
|
|
624
|
+
const notes = [];
|
|
625
|
+
if (cyclic) notes.push('Cyclic/fatigue derate applied — allowable reduced to 0.15 × S_ut (50% of static).');
|
|
626
|
+
if (direction === 'combined') notes.push('Combined direction treated as the more conservative of transverse/longitudinal for v1.');
|
|
627
|
+
if (utilisation > 0.9) notes.push('Utilisation > 90% — consider increasing leg size (h) to restore margin quickly; strength scales linearly with h.');
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
inputs: { h, L, F, direction, electrode: elecKey, electrode_label: elec.label, S_ut_electrode: elec.S_ut,
|
|
631
|
+
cyclic, safetyFactor_user: sf },
|
|
632
|
+
throat: { t, A },
|
|
633
|
+
stress: { tau },
|
|
634
|
+
allowable,
|
|
635
|
+
capacity,
|
|
636
|
+
utilisation,
|
|
637
|
+
safetyFactor,
|
|
638
|
+
verdict, verdictClass, notes
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
182
642
|
// ===================================================================
|
|
183
643
|
// UNIT TESTS — verify core against MecAgent screenshot values
|
|
184
644
|
// ===================================================================
|
|
@@ -206,6 +666,78 @@
|
|
|
206
666
|
test('τ (shear)', r.combinedStress.tau, 79, 1.5); // 6687.5/84.3 ≈ 79.3
|
|
207
667
|
test('σ_vm', r.combinedStress.sigma_vm, 558, 2); // √(542² + 3·79²)
|
|
208
668
|
|
|
669
|
+
// ------- GEAR TEST — Shigley Example 14-5 adapted -------
|
|
670
|
+
// 17T pinion / 52T gear, m=2mm, F=30mm, 2.5kW @ 1800rpm → T_P ≈ 13.26 N·m
|
|
671
|
+
// Through-hardened 240 HB both gears, K_o=1, K_v=1, K_m=1.3
|
|
672
|
+
const g = spurGearAnalysis({
|
|
673
|
+
pinionTeeth: 17, gearTeeth: 52, module: 2, faceWidth: 30,
|
|
674
|
+
torque: 13.26, pinionHB: 240, gearHB: 240,
|
|
675
|
+
overload: 1, dynamic: 1, loadDist: 1.3
|
|
676
|
+
});
|
|
677
|
+
// Tangential load W_t = 2·T / d_P = 2·13260 / 34 ≈ 780 N
|
|
678
|
+
test('gear W_t', g.inputs.W_t, 780, 3);
|
|
679
|
+
// J for 17 teeth ≈ 0.295 per interpolation table
|
|
680
|
+
test('gear J (pinion)', g.pinion.J, 0.295, 0.01);
|
|
681
|
+
// σ_b (pinion) = 780·1·1·1.3 / (30·2·0.295) = 1014/17.7 ≈ 57.3 MPa
|
|
682
|
+
test('gear σ_b pinion', g.pinion.sigma_b, 57.3, 1.5);
|
|
683
|
+
// S_t @240HB = (77·240 + 12800)·psi→MPa = 31280·0.00689 ≈ 215.7 MPa
|
|
684
|
+
test('gear S_t @240HB', g.pinion.S_t, 215.7, 1.5);
|
|
685
|
+
// SF_bending_P ≈ 215.7 / 57.3 ≈ 3.77
|
|
686
|
+
test('gear SF_bending pinion', g.pinion.SF_bending, 3.77, 0.2);
|
|
687
|
+
|
|
688
|
+
// ------- SHAFT TEST — clean case with rotating-bending + constant torque -------
|
|
689
|
+
// AISI 1050 CD, d=25mm, M_a=100 N·m (rotating bending so M_m=0), T_m=50 N·m, T_a=0
|
|
690
|
+
// Kf=2.0 (fillet), Kfs=1.5, machined surface, R=0.99, T=25°C
|
|
691
|
+
const sh = shaftFatigueAnalysis({
|
|
692
|
+
material: '1050_cd', diameter: 25,
|
|
693
|
+
M_a: 100, M_m: 0, T_a: 0, T_m: 50,
|
|
694
|
+
Kf: 2.0, Kfs: 1.5, surface: 'machined', reliability: 0.99, temperatureC: 25
|
|
695
|
+
});
|
|
696
|
+
// σ_a = 32·100000 / (π·25³) = 32e5 / 49087 ≈ 65.2 MPa
|
|
697
|
+
test('shaft σ_a', sh.stresses.sigma_a, 65.2, 0.5);
|
|
698
|
+
// τ_m = 16·50000 / (π·25³) ≈ 16.3 MPa
|
|
699
|
+
test('shaft τ_m', sh.stresses.tau_m, 16.3, 0.3);
|
|
700
|
+
// σ_prime_a with Kf·σ_a only (no mean bending, no alternating torque) ≈ Kf·σ_a = 130.4
|
|
701
|
+
test('shaft σ′_a', sh.stresses.sigma_prime_a, 130.4, 1.0);
|
|
702
|
+
// σ_prime_m = √3 · Kfs · τ_m ≈ √3 · 1.5 · 16.3 ≈ 42.4
|
|
703
|
+
test('shaft σ′_m', sh.stresses.sigma_prime_m, 42.4, 1.0);
|
|
704
|
+
// Must be finite + positive
|
|
705
|
+
test('shaft n_Goodman finite', Number.isFinite(sh.n_Goodman) && sh.n_Goodman > 0 ? 1 : 0, 1, 0);
|
|
706
|
+
test('shaft n_Soderberg ≤ Goodman', (sh.n_Soderberg <= sh.n_Goodman + 1e-6) ? 1 : 0, 1, 0);
|
|
707
|
+
|
|
708
|
+
// ------- BEARING TEST — 6204 @ 1800 rpm, 4 kN radial -------
|
|
709
|
+
// C = 13500 N, P = F_r = 4000 N, a = 3
|
|
710
|
+
// L10 = (13500/4000)^3 = 38.44 × 10^6 rev
|
|
711
|
+
// L10_h = 38.44e6 / (60·1800) = 355.9 hours
|
|
712
|
+
const b = bearingLifeAnalysis({
|
|
713
|
+
designation: '6204', radialLoad: 4000, rpm: 1800
|
|
714
|
+
});
|
|
715
|
+
test('bearing C from catalog', b.inputs.C, 13500, 10);
|
|
716
|
+
test('bearing exponent a', b.inputs.a, 3, 0);
|
|
717
|
+
test('bearing L10 (rev × 10^6)', b.L10_rev, 38.44, 0.5);
|
|
718
|
+
test('bearing L10 (hours)', b.L10_h, 355.9, 2);
|
|
719
|
+
test('bearing R90 ≡ L10', b.a_R, 1.0, 0.01);
|
|
720
|
+
|
|
721
|
+
// ------- WELD TEST — E70 fillet, h=6mm, L=120mm, 40 kN transverse -------
|
|
722
|
+
// A = 0.707·6·120 = 509.0 mm²
|
|
723
|
+
// τ = 40000 / 509.0 = 78.6 MPa
|
|
724
|
+
// allowable static = 0.30 × 482 = 144.6 MPa
|
|
725
|
+
// SF = 144.6 / 78.6 = 1.84
|
|
726
|
+
const w = filletWeldAnalysis({
|
|
727
|
+
legSize: 6, length: 120, force: 40000,
|
|
728
|
+
loadDirection: 'transverse', electrode: 'E70'
|
|
729
|
+
});
|
|
730
|
+
test('weld throat area', w.throat.A, 509.04, 0.5);
|
|
731
|
+
test('weld τ', w.stress.tau, 78.58, 0.5);
|
|
732
|
+
test('weld allowable E70', w.allowable, 144.6, 0.5);
|
|
733
|
+
test('weld SF', w.safetyFactor, 1.84, 0.1);
|
|
734
|
+
// Cyclic derate case — same loading, cyclic enabled → SF halves
|
|
735
|
+
const wc = filletWeldAnalysis({
|
|
736
|
+
legSize: 6, length: 120, force: 40000,
|
|
737
|
+
loadDirection: 'transverse', electrode: 'E70', cyclic: true
|
|
738
|
+
});
|
|
739
|
+
test('weld cyclic SF ≈ 0.92', wc.safetyFactor, 0.92, 0.05);
|
|
740
|
+
|
|
209
741
|
return { results, allPass: results.every(r => r.pass) };
|
|
210
742
|
}
|
|
211
743
|
|
|
@@ -306,12 +838,12 @@
|
|
|
306
838
|
}
|
|
307
839
|
|
|
308
840
|
// ===================================================================
|
|
309
|
-
// UI — form-based input with
|
|
841
|
+
// UI — form-based input with tab switcher (bolted-joint / gears / shafts / bearings / welds)
|
|
310
842
|
// ===================================================================
|
|
311
|
-
const S = { lastResult: null, els: {} };
|
|
843
|
+
const S = { lastResult: null, els: {}, currentTab: 'bolt' };
|
|
312
844
|
|
|
313
|
-
|
|
314
|
-
|
|
845
|
+
// Field schemas per analysis kind. Shape: [key, label, unit, default, min, type?, opts?]
|
|
846
|
+
const FIELDS_BOLT = [
|
|
315
847
|
['boltCount', 'Bolt count (z)', '', 4, 1],
|
|
316
848
|
['thread', 'Thread', '', 'M12', null, 'select', Object.keys(BOLT_STRESS_AREA)],
|
|
317
849
|
['grade', 'Grade (ISO 898-1)', '', '8.8', null, 'select', Object.keys(STEEL_GRADES)],
|
|
@@ -324,6 +856,84 @@
|
|
|
324
856
|
['safetyFactor', 'Slip safety factor (K_s)', '', 1.5, 1],
|
|
325
857
|
['frictionInterfaces','Friction interfaces (n)', '', 1, 1]
|
|
326
858
|
];
|
|
859
|
+
const FIELDS_GEAR = [
|
|
860
|
+
['pinionTeeth', 'Pinion teeth z_P', '', 20, 12],
|
|
861
|
+
['gearTeeth', 'Gear teeth z_G', '', 40, 12],
|
|
862
|
+
['module', 'Module m', 'mm', 2, 0.5],
|
|
863
|
+
['faceWidth', 'Face width F', 'mm', 25, 3],
|
|
864
|
+
['torque', 'Pinion torque T_P', 'N·m', 10, 0],
|
|
865
|
+
['pinionHB', 'Pinion hardness', 'HB', 240, 150],
|
|
866
|
+
['gearHB', 'Gear hardness', 'HB', 240, 150],
|
|
867
|
+
['overload', 'Overload K_o', '', 1.0, 1],
|
|
868
|
+
['dynamic', 'Dynamic K_v', '', 1.1, 1],
|
|
869
|
+
['loadDist', 'Load distribution K_m', '', 1.3, 1],
|
|
870
|
+
['reliability', 'Reliability K_R', '', 1.0, 1],
|
|
871
|
+
['pressureAngle','Pressure angle φ', '°', 20, 0]
|
|
872
|
+
];
|
|
873
|
+
const FIELDS_SHAFT = [
|
|
874
|
+
['material', 'Material', '', '1050_cd', null, 'select', Object.keys(SHAFT_MATERIALS)],
|
|
875
|
+
['diameter', 'Diameter d', 'mm', 25, 5],
|
|
876
|
+
['M_a', 'Alt. bending moment M_a', 'N·m', 100, 0],
|
|
877
|
+
['M_m', 'Mean bending moment M_m', 'N·m', 0, 0],
|
|
878
|
+
['T_a', 'Alt. torque T_a', 'N·m', 0, 0],
|
|
879
|
+
['T_m', 'Mean torque T_m', 'N·m', 50, 0],
|
|
880
|
+
['Kf', 'Fatigue K_f (bending)', '', 2.0, 1],
|
|
881
|
+
['Kfs', 'Fatigue K_fs (torsion)', '', 1.5, 1],
|
|
882
|
+
['surface', 'Surface finish', '', 'machined', null, 'select', Object.keys(SURFACE_FACTORS)],
|
|
883
|
+
['reliability', 'Reliability R', '', 0.99, 0.5],
|
|
884
|
+
['temperatureC', 'Temperature', '°C', 25, -50]
|
|
885
|
+
];
|
|
886
|
+
const FIELDS_BEARING = [
|
|
887
|
+
['designation', 'Designation', '', '6204', null, 'select', Object.keys(BEARING_CATALOGUE)],
|
|
888
|
+
['radialLoad', 'Radial load F_r', 'N', 4000, 0],
|
|
889
|
+
['axialLoad', 'Axial load F_a', 'N', 0, 0],
|
|
890
|
+
['rpm', 'Speed', 'rpm', 1800, 1],
|
|
891
|
+
['X', 'Radial factor X', '', 1, 0],
|
|
892
|
+
['Y', 'Thrust factor Y', '', 0, 0],
|
|
893
|
+
['reliability', 'Reliability', '', 0.9, 0.5]
|
|
894
|
+
];
|
|
895
|
+
const FIELDS_WELD = [
|
|
896
|
+
['legSize', 'Leg size h', 'mm', 6, 1],
|
|
897
|
+
['length', 'Weld length L', 'mm', 120, 1],
|
|
898
|
+
['force', 'Applied load F', 'N', 40000,0],
|
|
899
|
+
['loadDirection', 'Direction', '', 'transverse', null, 'select', ['transverse','longitudinal','combined']],
|
|
900
|
+
['electrode', 'Electrode', '', 'E70', null, 'select', Object.keys(WELD_ELECTRODES)],
|
|
901
|
+
['cyclic', 'Cyclic? (1 = yes)', '', 0, 0],
|
|
902
|
+
['safetyFactor', 'User SF multiplier', '', 1.0, 1]
|
|
903
|
+
];
|
|
904
|
+
|
|
905
|
+
const PRESETS_BOLT = [
|
|
906
|
+
{ label: 'MecAgent demo', values: { boltCount:4, thread:'M12', grade:'10.9', preload:39000, shearForce:18000, axialForce:18000, moment:420000, bcd:96, friction:0.16, safetyFactor:1.5 } },
|
|
907
|
+
{ label: 'Flange M8 light', values: { boltCount:8, thread:'M8', grade:'8.8', preload:15000, shearForce:5000, axialForce:2000, moment:50000, bcd:60, friction:0.15, safetyFactor:1.25 } },
|
|
908
|
+
{ label: 'Heavy M20', values: { boltCount:6, thread:'M20', grade:'8.8', preload:120000, shearForce:30000, axialForce:25000, moment:800000, bcd:200, friction:0.14, safetyFactor:1.5 } }
|
|
909
|
+
];
|
|
910
|
+
const PRESETS_GEAR = [
|
|
911
|
+
{ label: 'Shigley Ex 14-5', values: { pinionTeeth:17, gearTeeth:52, module:2, faceWidth:30, torque:13.26, pinionHB:240, gearHB:240, overload:1, dynamic:1, loadDist:1.3 } },
|
|
912
|
+
{ label: 'Light duty', values: { pinionTeeth:25, gearTeeth:75, module:1.5, faceWidth:18, torque:5, pinionHB:220, gearHB:220 } },
|
|
913
|
+
{ label: 'Heavy industrial',values: { pinionTeeth:20, gearTeeth:60, module:5, faceWidth:50, torque:250, pinionHB:320, gearHB:310, overload:1.25, loadDist:1.4 } }
|
|
914
|
+
];
|
|
915
|
+
const PRESETS_SHAFT = [
|
|
916
|
+
{ label: 'Rotating-bending', values: { material:'1050_cd', diameter:25, M_a:100, M_m:0, T_a:0, T_m:50, Kf:2, Kfs:1.5, surface:'machined' } },
|
|
917
|
+
{ label: 'Output shaft 4340',values: { material:'4340_Q&T', diameter:40, M_a:300, M_m:100, T_a:50, T_m:200, Kf:1.8, Kfs:1.3, surface:'ground' } }
|
|
918
|
+
];
|
|
919
|
+
const PRESETS_BEARING = [
|
|
920
|
+
{ label: '6204 / 4kN', values: { designation:'6204', radialLoad:4000, rpm:1800 } },
|
|
921
|
+
{ label: '6206 / 8kN', values: { designation:'6206', radialLoad:8000, rpm:1500 } },
|
|
922
|
+
{ label: 'NJ204 roller', values: { designation:'NJ204', radialLoad:10000, rpm:1200 } }
|
|
923
|
+
];
|
|
924
|
+
const PRESETS_WELD = [
|
|
925
|
+
{ label: 'E70 6mm × 120mm', values: { legSize:6, length:120, force:40000, electrode:'E70', loadDirection:'transverse' } },
|
|
926
|
+
{ label: 'Heavy bracket', values: { legSize:10, length:200, force:120000, electrode:'E80', loadDirection:'transverse' } },
|
|
927
|
+
{ label: 'Cyclic tie-down', values: { legSize:5, length:80, force:15000, electrode:'E70', loadDirection:'longitudinal', cyclic:1 } }
|
|
928
|
+
];
|
|
929
|
+
|
|
930
|
+
const TABS = Object.freeze({
|
|
931
|
+
bolt: { label: 'Bolted Joint', subtitle: 'VDI 2230 / Shigley', fields: FIELDS_BOLT, presets: PRESETS_BOLT, analyze: boltedJointAnalysis, hasPrompt: true, kind: 'bolt' },
|
|
932
|
+
gear: { label: 'Spur Gears', subtitle: 'AGMA bending + pitting (Shigley Ch 14)', fields: FIELDS_GEAR, presets: PRESETS_GEAR, analyze: spurGearAnalysis, hasPrompt: false, kind: 'gear' },
|
|
933
|
+
shaft: { label: 'Shaft Fatigue', subtitle: 'Goodman / Soderberg (Shigley Ch 7)', fields: FIELDS_SHAFT, presets: PRESETS_SHAFT, analyze: shaftFatigueAnalysis,hasPrompt: false, kind: 'shaft' },
|
|
934
|
+
bearing: { label: 'Bearing Life', subtitle: 'L_10 (ISO 281) — Shigley Ch 11', fields: FIELDS_BEARING, presets: PRESETS_BEARING, analyze: bearingLifeAnalysis, hasPrompt: false, kind: 'bearing' },
|
|
935
|
+
weld: { label: 'Fillet Welds', subtitle: 'Throat stress (AWS D1.1)', fields: FIELDS_WELD, presets: PRESETS_WELD, analyze: filletWeldAnalysis, hasPrompt: false, kind: 'weld' }
|
|
936
|
+
});
|
|
327
937
|
|
|
328
938
|
function fmt(n, unit, digits) {
|
|
329
939
|
if (!isFinite(n)) return '—';
|
|
@@ -332,27 +942,205 @@
|
|
|
332
942
|
return unit ? s + ' ' + unit : s;
|
|
333
943
|
}
|
|
334
944
|
|
|
945
|
+
function currentFields() { return (TABS[S.currentTab] || TABS.bolt).fields; }
|
|
946
|
+
function currentAnalyze() { return (TABS[S.currentTab] || TABS.bolt).analyze; }
|
|
947
|
+
|
|
335
948
|
function collectInputs() {
|
|
336
949
|
const out = {};
|
|
337
|
-
|
|
950
|
+
currentFields().forEach(([key,,,,, type]) => {
|
|
338
951
|
const el = S.els['in_' + key];
|
|
339
952
|
if (!el) return;
|
|
340
|
-
|
|
953
|
+
if (type === 'select') {
|
|
954
|
+
out[key] = el.value;
|
|
955
|
+
} else {
|
|
956
|
+
// Boolean fields encoded as 0/1 numeric — map back to boolean where the function expects it
|
|
957
|
+
out[key] = parseFloat(el.value);
|
|
958
|
+
}
|
|
341
959
|
});
|
|
960
|
+
// Weld's cyclic is encoded as 0/1 — convert to boolean
|
|
961
|
+
if (S.currentTab === 'weld' && 'cyclic' in out) out.cyclic = !!out.cyclic;
|
|
342
962
|
return out;
|
|
343
963
|
}
|
|
344
964
|
|
|
345
965
|
function compute() {
|
|
346
966
|
try {
|
|
347
967
|
const params = collectInputs();
|
|
348
|
-
const
|
|
968
|
+
const analyze = currentAnalyze();
|
|
969
|
+
const r = analyze(params);
|
|
349
970
|
S.lastResult = r;
|
|
350
|
-
|
|
351
|
-
|
|
971
|
+
if (S.currentTab === 'bolt') {
|
|
972
|
+
renderReport(r); // Full KaTeX-rich bolted-joint report
|
|
973
|
+
} else {
|
|
974
|
+
renderReportGeneric(r, S.currentTab);
|
|
975
|
+
}
|
|
976
|
+
} catch (e) {
|
|
352
977
|
if (S.els.report) S.els.report.innerHTML = '<div class="aie-err">Error: ' + e.message + '</div>';
|
|
353
978
|
}
|
|
354
979
|
}
|
|
355
980
|
|
|
981
|
+
/**
|
|
982
|
+
* Generic result renderer for v2 tabs (gear / shaft / bearing / weld).
|
|
983
|
+
* Outputs: verdict banner + key numbers table + notes list.
|
|
984
|
+
* KaTeX-rendered formulas can be added per-tab in a future pass.
|
|
985
|
+
* @param {object} r Result object from the relevant analyze function.
|
|
986
|
+
* @param {string} kind Tab key — 'gear' | 'shaft' | 'bearing' | 'weld'.
|
|
987
|
+
*/
|
|
988
|
+
function renderReportGeneric(r, kind) {
|
|
989
|
+
const root = S.els.report;
|
|
990
|
+
if (!root) return;
|
|
991
|
+
root.innerHTML = '';
|
|
992
|
+
if (r.error) {
|
|
993
|
+
root.innerHTML = '<div class="aie-err" style="padding:10px;background:#7f1d1d;color:#fecaca;border-radius:6px;font-size:13px">' + r.error + '</div>';
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Verdict banner
|
|
998
|
+
const vcls = r.verdictClass || 'pass';
|
|
999
|
+
const bg = vcls === 'pass' ? '#065f46' : vcls === 'warn' ? '#78350f' : '#7f1d1d';
|
|
1000
|
+
const fg = vcls === 'pass' ? '#d1fae5' : vcls === 'warn' ? '#fed7aa' : '#fecaca';
|
|
1001
|
+
const banner = document.createElement('div');
|
|
1002
|
+
banner.style.cssText = 'padding:10px 14px;border-radius:6px;background:'+bg+';color:'+fg+';font-weight:600;font-size:14px;margin-bottom:12px;border:1px solid rgba(255,255,255,0.08)';
|
|
1003
|
+
banner.textContent = 'Verdict: ' + r.verdict;
|
|
1004
|
+
root.appendChild(banner);
|
|
1005
|
+
|
|
1006
|
+
// Notes
|
|
1007
|
+
if (r.notes && r.notes.length) {
|
|
1008
|
+
const ul = document.createElement('ul');
|
|
1009
|
+
ul.style.cssText = 'margin:4px 0 14px 18px;font-size:12px;color:#94a3b8';
|
|
1010
|
+
r.notes.forEach(n => { const li = document.createElement('li'); li.textContent = n; ul.appendChild(li); });
|
|
1011
|
+
root.appendChild(ul);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Kind-specific body
|
|
1015
|
+
const body = document.createElement('div');
|
|
1016
|
+
body.style.cssText = 'padding:10px 12px;background:#0f172a;border:1px solid #334155;border-radius:6px;font-size:13px;color:#cbd5e1;line-height:1.8';
|
|
1017
|
+
|
|
1018
|
+
if (kind === 'gear') {
|
|
1019
|
+
body.innerHTML =
|
|
1020
|
+
'<strong style="color:#f1f5f9">Geometry</strong><br>' +
|
|
1021
|
+
'Pitch Ø pinion: ' + fmt(r.inputs.d_P, 'mm') + ' · Pitch Ø gear: ' + fmt(r.inputs.d_G, 'mm') +
|
|
1022
|
+
' · Ratio m_G: ' + fmt(r.inputs.mG) + '<br>' +
|
|
1023
|
+
'Tangential load W_t: ' + fmt(r.inputs.W_t, 'N') + '<br><br>' +
|
|
1024
|
+
'<strong style="color:#f1f5f9">Safety factors</strong><br>' +
|
|
1025
|
+
'<span style="color:#94a3b8">Pinion:</span> SF_bending = <strong>' + fmt(r.pinion.SF_bending) + '</strong> · SF_contact = <strong>' + fmt(r.pinion.SF_contact) + '</strong><br>' +
|
|
1026
|
+
'<span style="color:#94a3b8">Gear:</span> SF_bending = <strong>' + fmt(r.gear.SF_bending) + '</strong> · SF_contact = <strong>' + fmt(r.gear.SF_contact) + '</strong><br>' +
|
|
1027
|
+
'<span style="color:#94a3b8">Overall min:</span> <strong style="color:' + (r.SF_min >= 1.5 ? '#a7f3d0' : r.SF_min >= 1.0 ? '#fed7aa' : '#fca5a5') + '">' + fmt(r.SF_min) + '</strong>';
|
|
1028
|
+
} else if (kind === 'shaft') {
|
|
1029
|
+
body.innerHTML =
|
|
1030
|
+
'<strong style="color:#f1f5f9">Stresses</strong><br>' +
|
|
1031
|
+
'σ_a = ' + fmt(r.stresses.sigma_a, 'MPa') + ' · σ_m = ' + fmt(r.stresses.sigma_m, 'MPa') +
|
|
1032
|
+
' · τ_a = ' + fmt(r.stresses.tau_a, 'MPa') + ' · τ_m = ' + fmt(r.stresses.tau_m, 'MPa') + '<br>' +
|
|
1033
|
+
"σ'_a = " + fmt(r.stresses.sigma_prime_a, 'MPa') + " · σ'_m = " + fmt(r.stresses.sigma_prime_m, 'MPa') + '<br><br>' +
|
|
1034
|
+
'<strong style="color:#f1f5f9">Factors of safety</strong><br>' +
|
|
1035
|
+
'Goodman: <strong>' + fmt(r.n_Goodman) + '</strong> · Soderberg: <strong>' + fmt(r.n_Soderberg) + '</strong> · First-cycle yield: <strong>' + fmt(r.n_yield) + '</strong><br>' +
|
|
1036
|
+
'Endurance limit S_e (corrected): ' + fmt(r.marin.S_e, 'MPa') + ' (uncorrected ' + fmt(r.marin.S_e_prime, 'MPa') + ')';
|
|
1037
|
+
} else if (kind === 'bearing') {
|
|
1038
|
+
const cat = r.inputs.catalog;
|
|
1039
|
+
const tag = cat ? (r.inputs.designation + ' · Ø' + cat.d + '/' + cat.D + ' × ' + cat.B + 'mm · C = ' + fmt(cat.C/1000, 'kN')) : ('custom C = ' + fmt(r.inputs.C/1000, 'kN'));
|
|
1040
|
+
body.innerHTML =
|
|
1041
|
+
'<strong style="color:#f1f5f9">' + tag + '</strong><br>' +
|
|
1042
|
+
'Equivalent load P: ' + fmt(r.P, 'N') + ' · Exponent a = ' + fmt(r.inputs.a) + '<br><br>' +
|
|
1043
|
+
'<strong style="color:#f1f5f9">L_10 life</strong><br>' +
|
|
1044
|
+
fmt(r.L10_rev) + ' × 10⁶ revolutions<br>' +
|
|
1045
|
+
fmt(r.L10_h, 'hours') + ' at ' + r.inputs.rpm + ' rpm' + (r.inputs.reliability !== 0.9 ? ' (L₁₀)' : '') + '<br>' +
|
|
1046
|
+
(r.inputs.reliability !== 0.9 ?
|
|
1047
|
+
('Adjusted to R = ' + r.inputs.reliability + ': ' + fmt(r.L_R_h, 'hours')) :
|
|
1048
|
+
'');
|
|
1049
|
+
} else if (kind === 'weld') {
|
|
1050
|
+
body.innerHTML =
|
|
1051
|
+
'<strong style="color:#f1f5f9">Throat geometry</strong><br>' +
|
|
1052
|
+
't = 0.707·h = ' + fmt(r.throat.t, 'mm') + ' · area A = ' + fmt(r.throat.A, 'mm²') + '<br><br>' +
|
|
1053
|
+
'<strong style="color:#f1f5f9">Stress</strong><br>' +
|
|
1054
|
+
'τ = F / A = ' + fmt(r.stress.tau, 'MPa') + '<br>' +
|
|
1055
|
+
'Allowable (' + r.inputs.electrode + ', ' + (r.inputs.cyclic ? 'cyclic' : 'static') + '): ' + fmt(r.allowable, 'MPa') + '<br>' +
|
|
1056
|
+
'Capacity: ' + fmt(r.capacity, 'N') + ' · Utilisation: ' + fmt(r.utilisation * 100, '%') + '<br>' +
|
|
1057
|
+
'Safety factor: <strong style="color:' + (r.safetyFactor >= 1.5 ? '#a7f3d0' : r.safetyFactor >= 1.0 ? '#fed7aa' : '#fca5a5') + '">' + fmt(r.safetyFactor) + '</strong>';
|
|
1058
|
+
}
|
|
1059
|
+
root.appendChild(body);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Switch the UI to a different analysis tab. Rebuilds the form grid, preset buttons,
|
|
1064
|
+
* and report area in-place using the tab's schema.
|
|
1065
|
+
* @param {string} tabKey One of 'bolt' | 'gear' | 'shaft' | 'bearing' | 'weld'.
|
|
1066
|
+
*/
|
|
1067
|
+
function switchTab(tabKey) {
|
|
1068
|
+
if (!TABS[tabKey]) return;
|
|
1069
|
+
S.currentTab = tabKey;
|
|
1070
|
+
// Rebuild pill highlights
|
|
1071
|
+
if (S.els.tabBar) {
|
|
1072
|
+
Array.from(S.els.tabBar.children).forEach(pill => {
|
|
1073
|
+
const active = pill.dataset.tab === tabKey;
|
|
1074
|
+
pill.style.background = active ? '#38bdf8' : '#334155';
|
|
1075
|
+
pill.style.color = active ? '#0f172a' : '#cbd5e1';
|
|
1076
|
+
pill.style.fontWeight = active ? '700' : '500';
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
// Show/hide NL prompt (bolt only)
|
|
1080
|
+
if (S.els.promptWrap) S.els.promptWrap.style.display = TABS[tabKey].hasPrompt ? 'flex' : 'none';
|
|
1081
|
+
// Update subtitle
|
|
1082
|
+
if (S.els.subtitle) S.els.subtitle.textContent = TABS[tabKey].label + ' — ' + TABS[tabKey].subtitle;
|
|
1083
|
+
// Rebuild form grid
|
|
1084
|
+
rebuildFormGrid();
|
|
1085
|
+
// Rebuild presets
|
|
1086
|
+
rebuildPresets();
|
|
1087
|
+
// Clear report
|
|
1088
|
+
if (S.els.report) S.els.report.innerHTML = '<div style="padding:14px 0;color:#64748b;font-size:12px;font-style:italic">Adjust inputs to see live analysis.</div>';
|
|
1089
|
+
// Compute once
|
|
1090
|
+
setTimeout(compute, 0);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function rebuildFormGrid() {
|
|
1094
|
+
const grid = S.els.grid;
|
|
1095
|
+
if (!grid) return;
|
|
1096
|
+
grid.innerHTML = '';
|
|
1097
|
+
// Clear stale input element refs (keep prompt, report, etc.)
|
|
1098
|
+
Object.keys(S.els).forEach(k => { if (k.startsWith('in_')) delete S.els[k]; });
|
|
1099
|
+
currentFields().forEach(field => {
|
|
1100
|
+
const [key, label, unit, def, min, type, opts] = field;
|
|
1101
|
+
const cell = document.createElement('label');
|
|
1102
|
+
cell.style.cssText = 'display:flex;flex-direction:column;gap:2px;font-size:11px;color:#94a3b8';
|
|
1103
|
+
const lbl = document.createElement('span');
|
|
1104
|
+
lbl.innerHTML = label + (unit ? ' <span style="color:#64748b">['+unit+']</span>' : '');
|
|
1105
|
+
let input;
|
|
1106
|
+
if (type === 'select') {
|
|
1107
|
+
input = document.createElement('select');
|
|
1108
|
+
input.style.cssText = 'padding:5px;background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:3px;font-size:12px';
|
|
1109
|
+
opts.forEach(v => { const o = document.createElement('option'); o.value = v; o.textContent = v; input.appendChild(o); });
|
|
1110
|
+
input.value = def;
|
|
1111
|
+
} else {
|
|
1112
|
+
input = document.createElement('input');
|
|
1113
|
+
input.type = 'number';
|
|
1114
|
+
input.step = 'any';
|
|
1115
|
+
if (min !== null) input.min = String(min);
|
|
1116
|
+
input.value = String(def);
|
|
1117
|
+
input.style.cssText = 'padding:5px;background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:3px;font-size:12px';
|
|
1118
|
+
}
|
|
1119
|
+
input.addEventListener('input', compute);
|
|
1120
|
+
input.addEventListener('change', compute);
|
|
1121
|
+
S.els['in_' + key] = input;
|
|
1122
|
+
cell.appendChild(lbl);
|
|
1123
|
+
cell.appendChild(input);
|
|
1124
|
+
grid.appendChild(cell);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function rebuildPresets() {
|
|
1129
|
+
const presets = S.els.presets;
|
|
1130
|
+
if (!presets) return;
|
|
1131
|
+
presets.innerHTML = '';
|
|
1132
|
+
TABS[S.currentTab].presets.forEach(preset => {
|
|
1133
|
+
const b = document.createElement('button');
|
|
1134
|
+
b.textContent = preset.label;
|
|
1135
|
+
b.style.cssText = 'padding:4px 8px;background:#334155;color:#cbd5e1;border:0;border-radius:3px;cursor:pointer;font-size:11px';
|
|
1136
|
+
b.onclick = () => {
|
|
1137
|
+
Object.entries(preset.values).forEach(([k, v]) => { const el = S.els['in_' + k]; if (el) el.value = String(v); });
|
|
1138
|
+
compute();
|
|
1139
|
+
};
|
|
1140
|
+
presets.appendChild(b);
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
356
1144
|
function renderReport(r) {
|
|
357
1145
|
const root = S.els.report;
|
|
358
1146
|
if (!root) return;
|
|
@@ -477,11 +1265,32 @@
|
|
|
477
1265
|
|
|
478
1266
|
// Header
|
|
479
1267
|
const header = document.createElement('div');
|
|
480
|
-
|
|
481
|
-
|
|
1268
|
+
const title = document.createElement('div');
|
|
1269
|
+
title.style.cssText = 'font-size:15px;font-weight:700;color:#f1f5f9';
|
|
1270
|
+
title.textContent = 'AI Engineering Analyst';
|
|
1271
|
+
const subtitle = document.createElement('div');
|
|
1272
|
+
subtitle.style.cssText = 'font-size:11px;color:#94a3b8;margin-top:2px';
|
|
1273
|
+
subtitle.textContent = 'Bolted Joint — VDI 2230 / Shigley';
|
|
1274
|
+
S.els.subtitle = subtitle;
|
|
1275
|
+
header.appendChild(title);
|
|
1276
|
+
header.appendChild(subtitle);
|
|
482
1277
|
wrap.appendChild(header);
|
|
483
1278
|
|
|
484
|
-
//
|
|
1279
|
+
// Tab pills — 5 analysis kinds
|
|
1280
|
+
const tabBar = document.createElement('div');
|
|
1281
|
+
tabBar.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;padding:6px;background:#0f172a;border:1px solid #334155;border-radius:6px';
|
|
1282
|
+
Object.entries(TABS).forEach(([key, tab]) => {
|
|
1283
|
+
const pill = document.createElement('button');
|
|
1284
|
+
pill.dataset.tab = key;
|
|
1285
|
+
pill.textContent = tab.label;
|
|
1286
|
+
pill.style.cssText = 'padding:6px 12px;background:' + (key === 'bolt' ? '#38bdf8' : '#334155') + ';color:' + (key === 'bolt' ? '#0f172a' : '#cbd5e1') + ';border:0;border-radius:4px;cursor:pointer;font-size:12px;font-weight:' + (key === 'bolt' ? '700' : '500') + ';transition:background 0.15s';
|
|
1287
|
+
pill.addEventListener('click', () => switchTab(key));
|
|
1288
|
+
tabBar.appendChild(pill);
|
|
1289
|
+
});
|
|
1290
|
+
S.els.tabBar = tabBar;
|
|
1291
|
+
wrap.appendChild(tabBar);
|
|
1292
|
+
|
|
1293
|
+
// Prompt box for natural-language entry (bolted-joint only)
|
|
485
1294
|
const promptWrap = document.createElement('div');
|
|
486
1295
|
promptWrap.style.cssText = 'display:flex;gap:6px';
|
|
487
1296
|
const prompt = document.createElement('input');
|
|
@@ -494,60 +1303,24 @@
|
|
|
494
1303
|
applyBtn.onclick = applyFromPrompt;
|
|
495
1304
|
prompt.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); applyFromPrompt(); } });
|
|
496
1305
|
S.els.prompt = prompt;
|
|
1306
|
+
S.els.promptWrap = promptWrap;
|
|
497
1307
|
promptWrap.appendChild(prompt);
|
|
498
1308
|
promptWrap.appendChild(applyBtn);
|
|
499
1309
|
wrap.appendChild(promptWrap);
|
|
500
1310
|
|
|
501
|
-
// Input grid
|
|
1311
|
+
// Input grid — populated by rebuildFormGrid()
|
|
502
1312
|
const grid = document.createElement('div');
|
|
503
1313
|
grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px 12px;padding:10px;background:#1e293b;border-radius:6px';
|
|
504
|
-
|
|
505
|
-
const [key, label, unit, def, min, type, opts] = field;
|
|
506
|
-
const cell = document.createElement('label');
|
|
507
|
-
cell.style.cssText = 'display:flex;flex-direction:column;gap:2px;font-size:11px;color:#94a3b8';
|
|
508
|
-
const lbl = document.createElement('span');
|
|
509
|
-
lbl.innerHTML = label + (unit ? ' <span style="color:#64748b">['+unit+']</span>' : '');
|
|
510
|
-
let input;
|
|
511
|
-
if (type === 'select') {
|
|
512
|
-
input = document.createElement('select');
|
|
513
|
-
input.style.cssText = 'padding:5px;background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:3px;font-size:12px';
|
|
514
|
-
opts.forEach(v => { const o = document.createElement('option'); o.value = v; o.textContent = v; input.appendChild(o); });
|
|
515
|
-
input.value = def;
|
|
516
|
-
} else {
|
|
517
|
-
input = document.createElement('input');
|
|
518
|
-
input.type = 'number';
|
|
519
|
-
input.step = 'any';
|
|
520
|
-
if (min !== null) input.min = String(min);
|
|
521
|
-
input.value = String(def);
|
|
522
|
-
input.style.cssText = 'padding:5px;background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:3px;font-size:12px';
|
|
523
|
-
}
|
|
524
|
-
input.addEventListener('input', compute);
|
|
525
|
-
input.addEventListener('change', compute);
|
|
526
|
-
S.els['in_' + key] = input;
|
|
527
|
-
cell.appendChild(lbl);
|
|
528
|
-
cell.appendChild(input);
|
|
529
|
-
grid.appendChild(cell);
|
|
530
|
-
});
|
|
1314
|
+
S.els.grid = grid;
|
|
531
1315
|
wrap.appendChild(grid);
|
|
1316
|
+
rebuildFormGrid();
|
|
532
1317
|
|
|
533
|
-
// Preset examples
|
|
1318
|
+
// Preset examples — populated by rebuildPresets()
|
|
534
1319
|
const presets = document.createElement('div');
|
|
535
1320
|
presets.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px';
|
|
536
|
-
|
|
537
|
-
{ label: 'MecAgent demo', values: { boltCount:4, thread:'M12', grade:'10.9', preload:39000, shearForce:18000, axialForce:18000, moment:420000, bcd:96, friction:0.16, safetyFactor:1.5 } },
|
|
538
|
-
{ label: 'Flange M8 light', values: { boltCount:8, thread:'M8', grade:'8.8', preload:15000, shearForce:5000, axialForce:2000, moment:50000, bcd:60, friction:0.15, safetyFactor:1.25 } },
|
|
539
|
-
{ label: 'Heavy M20', values: { boltCount:6, thread:'M20', grade:'8.8', preload:120000, shearForce:30000, axialForce:25000, moment:800000, bcd:200, friction:0.14, safetyFactor:1.5 } }
|
|
540
|
-
].forEach(preset => {
|
|
541
|
-
const b = document.createElement('button');
|
|
542
|
-
b.textContent = preset.label;
|
|
543
|
-
b.style.cssText = 'padding:4px 8px;background:#334155;color:#cbd5e1;border:0;border-radius:3px;cursor:pointer;font-size:11px';
|
|
544
|
-
b.onclick = () => {
|
|
545
|
-
Object.entries(preset.values).forEach(([k, v]) => { const el = S.els['in_' + k]; if (el) el.value = String(v); });
|
|
546
|
-
compute();
|
|
547
|
-
};
|
|
548
|
-
presets.appendChild(b);
|
|
549
|
-
});
|
|
1321
|
+
S.els.presets = presets;
|
|
550
1322
|
wrap.appendChild(presets);
|
|
1323
|
+
rebuildPresets();
|
|
551
1324
|
|
|
552
1325
|
// Report area
|
|
553
1326
|
const report = document.createElement('div');
|
|
@@ -585,8 +1358,26 @@
|
|
|
585
1358
|
// ===================================================================
|
|
586
1359
|
let uiEl = null;
|
|
587
1360
|
window.CycleCAD.AIEngineer = {
|
|
1361
|
+
// ---- v1: bolted-joint ----
|
|
588
1362
|
analyze: boltedJointAnalysis,
|
|
589
1363
|
parsePrompt: parseBoltedJointPrompt,
|
|
1364
|
+
// ---- v2: gears ----
|
|
1365
|
+
analyzeGear: spurGearAnalysis,
|
|
1366
|
+
gearAllowableBending,
|
|
1367
|
+
gearAllowableContact,
|
|
1368
|
+
gearGeometryJ,
|
|
1369
|
+
gearGeometryI,
|
|
1370
|
+
// ---- v2: shafts ----
|
|
1371
|
+
analyzeShaft: shaftFatigueAnalysis,
|
|
1372
|
+
SHAFT_MATERIALS,
|
|
1373
|
+
SURFACE_FACTORS,
|
|
1374
|
+
// ---- v2: bearings ----
|
|
1375
|
+
analyzeBearing: bearingLifeAnalysis,
|
|
1376
|
+
BEARING_CATALOGUE,
|
|
1377
|
+
// ---- v2: welds ----
|
|
1378
|
+
analyzeWeld: filletWeldAnalysis,
|
|
1379
|
+
WELD_ELECTRODES,
|
|
1380
|
+
// ---- shared ----
|
|
590
1381
|
runSelfTests,
|
|
591
1382
|
STEEL_GRADES,
|
|
592
1383
|
BOLT_STRESS_AREA,
|
|
@@ -597,17 +1388,21 @@
|
|
|
597
1388
|
if (!t.allPass) {
|
|
598
1389
|
console.warn('[AI Engineer] self-test failures:', t.results.filter(r => !r.pass));
|
|
599
1390
|
} else {
|
|
600
|
-
console.log('[AI Engineer] self-tests pass (' + t.results.length + '/' + t.results.length + '
|
|
1391
|
+
console.log('[AI Engineer] self-tests pass (' + t.results.length + '/' + t.results.length + ' across bolted-joint + gears + shafts)');
|
|
601
1392
|
}
|
|
602
1393
|
return t.allPass;
|
|
603
1394
|
},
|
|
604
1395
|
getUI: () => { if (!uiEl) uiEl = buildUI(); return uiEl; },
|
|
605
1396
|
execute: (cmd, params) => {
|
|
606
|
-
if (cmd === 'analyze')
|
|
607
|
-
if (cmd === '
|
|
608
|
-
if (cmd === '
|
|
1397
|
+
if (cmd === 'analyze') return boltedJointAnalysis(params || {});
|
|
1398
|
+
if (cmd === 'analyze-gear') return spurGearAnalysis(params || {});
|
|
1399
|
+
if (cmd === 'analyze-shaft') return shaftFatigueAnalysis(params || {});
|
|
1400
|
+
if (cmd === 'analyze-bearing') return bearingLifeAnalysis(params || {});
|
|
1401
|
+
if (cmd === 'analyze-weld') return filletWeldAnalysis(params || {});
|
|
1402
|
+
if (cmd === 'parse') return parseBoltedJointPrompt((params && params.prompt) || '');
|
|
1403
|
+
if (cmd === 'show') { if (!uiEl) uiEl = buildUI(); return uiEl; }
|
|
609
1404
|
}
|
|
610
1405
|
};
|
|
611
1406
|
|
|
612
|
-
console.log('AI Engineering Analyst
|
|
1407
|
+
console.log('AI Engineering Analyst v2.0 module loaded (bolted-joint + gears + shafts)');
|
|
613
1408
|
})();
|