cyclecad 3.11.0 → 3.12.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 +40 -10
- package/app/js/modules/ai-engineer.js +331 -5
- 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/app/index.html
CHANGED
|
@@ -1412,7 +1412,7 @@
|
|
|
1412
1412
|
</div>
|
|
1413
1413
|
<div class="status-item">
|
|
1414
1414
|
<span class="status-label">Version:</span>
|
|
1415
|
-
<span class="status-value">
|
|
1415
|
+
<span class="status-value" id="status-version">v3.12.0</span>
|
|
1416
1416
|
</div>
|
|
1417
1417
|
</div>
|
|
1418
1418
|
|
|
@@ -1455,12 +1455,15 @@ window._dismissSplash = function(action) {
|
|
|
1455
1455
|
<script>
|
|
1456
1456
|
(function() {
|
|
1457
1457
|
function dismiss() { document.getElementById("welcome-panel").style.display = "none"; }
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
document.getElementById("splash-
|
|
1462
|
-
document.getElementById("splash-
|
|
1463
|
-
document.getElementById("splash-
|
|
1458
|
+
// Queue the action on the pending hook so the main module picks it up after load.
|
|
1459
|
+
// If the module is already loaded, the defineProperty setter dispatches immediately with a 100ms delay.
|
|
1460
|
+
function dispatchAction(action) { dismiss(); window._pendingSplashAction = action; }
|
|
1461
|
+
document.getElementById("splash-sketch") .addEventListener("click", function() { dispatchAction('sketch-new'); });
|
|
1462
|
+
document.getElementById("splash-import") .addEventListener("click", function() { dispatchAction('file-import'); });
|
|
1463
|
+
document.getElementById("splash-textcad") .addEventListener("click", function() { dispatchAction('tools-text-to-cad'); });
|
|
1464
|
+
document.getElementById("splash-imagecad").addEventListener("click", function() { dispatchAction('tools-image-to-cad'); });
|
|
1465
|
+
document.getElementById("splash-openscad").addEventListener("click", function() { dispatchAction('tools-openscad'); });
|
|
1466
|
+
document.getElementById("splash-inventor").addEventListener("click", function() { dispatchAction('file-import'); });
|
|
1464
1467
|
})();
|
|
1465
1468
|
</script>
|
|
1466
1469
|
|
|
@@ -1959,9 +1962,10 @@ window._dismissSplash = function(action) {
|
|
|
1959
1962
|
})();
|
|
1960
1963
|
}
|
|
1961
1964
|
break;
|
|
1962
|
-
case 'help-about':
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
+
case 'help-about': {
|
|
1966
|
+
const v = (window.CycleCAD && window.CycleCAD.version) ? window.CycleCAD.version : '3.12.0';
|
|
1967
|
+
showDialog('About cycleCAD', 'cycleCAD v' + v + '<br>Part of the cycleCAD Suite — parametric CAD, ExplodeView, Pentacad.<br><br>Open-source (MIT) parametric 3D CAD modeller. Built with Three.js, OpenCascade.js for real B-rep, supporting STEP / IGES / GLB import, full parametric modelling, and AI-powered design assistance via the AI Copilot + AI Engineering Analyst.');
|
|
1968
|
+
break; }
|
|
1965
1969
|
case 'tools-ai-copilot':
|
|
1966
1970
|
if (window.CycleCAD && window.CycleCAD.AICopilot) {
|
|
1967
1971
|
showDialog('✨ AI Copilot — multi-step CAD from natural language', '');
|
|
@@ -2953,6 +2957,32 @@ window._dismissSplash = function(action) {
|
|
|
2953
2957
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initDialogDrag);
|
|
2954
2958
|
else initDialogDrag();
|
|
2955
2959
|
})();
|
|
2960
|
+
|
|
2961
|
+
// ─── Dynamic version badge — fetch from /package.json on load, fall back to embedded ───
|
|
2962
|
+
(function(){
|
|
2963
|
+
const FALLBACK_VERSION = '3.12.0';
|
|
2964
|
+
async function updateVersion() {
|
|
2965
|
+
const badge = document.getElementById('status-version');
|
|
2966
|
+
if (!badge) return;
|
|
2967
|
+
try {
|
|
2968
|
+
const r = await fetch('/package.json', { cache: 'no-cache' });
|
|
2969
|
+
if (!r.ok) throw new Error('package.json HTTP ' + r.status);
|
|
2970
|
+
const pkg = await r.json();
|
|
2971
|
+
if (pkg && pkg.version) {
|
|
2972
|
+
badge.textContent = 'v' + pkg.version;
|
|
2973
|
+
window.CycleCAD = window.CycleCAD || {};
|
|
2974
|
+
window.CycleCAD.version = pkg.version;
|
|
2975
|
+
}
|
|
2976
|
+
} catch (err) {
|
|
2977
|
+
// Keep hardcoded fallback; log quietly for debugging.
|
|
2978
|
+
badge.textContent = 'v' + FALLBACK_VERSION;
|
|
2979
|
+
window.CycleCAD = window.CycleCAD || {};
|
|
2980
|
+
window.CycleCAD.version = FALLBACK_VERSION;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', updateVersion);
|
|
2984
|
+
else updateVersion();
|
|
2985
|
+
})();
|
|
2956
2986
|
</script>
|
|
2957
2987
|
</body>
|
|
2958
2988
|
</html>
|
|
@@ -179,6 +179,279 @@
|
|
|
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
|
+
|
|
182
455
|
// ===================================================================
|
|
183
456
|
// UNIT TESTS — verify core against MecAgent screenshot values
|
|
184
457
|
// ===================================================================
|
|
@@ -206,6 +479,45 @@
|
|
|
206
479
|
test('τ (shear)', r.combinedStress.tau, 79, 1.5); // 6687.5/84.3 ≈ 79.3
|
|
207
480
|
test('σ_vm', r.combinedStress.sigma_vm, 558, 2); // √(542² + 3·79²)
|
|
208
481
|
|
|
482
|
+
// ------- GEAR TEST — Shigley Example 14-5 adapted -------
|
|
483
|
+
// 17T pinion / 52T gear, m=2mm, F=30mm, 2.5kW @ 1800rpm → T_P ≈ 13.26 N·m
|
|
484
|
+
// Through-hardened 240 HB both gears, K_o=1, K_v=1, K_m=1.3
|
|
485
|
+
const g = spurGearAnalysis({
|
|
486
|
+
pinionTeeth: 17, gearTeeth: 52, module: 2, faceWidth: 30,
|
|
487
|
+
torque: 13.26, pinionHB: 240, gearHB: 240,
|
|
488
|
+
overload: 1, dynamic: 1, loadDist: 1.3
|
|
489
|
+
});
|
|
490
|
+
// Tangential load W_t = 2·T / d_P = 2·13260 / 34 ≈ 780 N
|
|
491
|
+
test('gear W_t', g.inputs.W_t, 780, 3);
|
|
492
|
+
// J for 17 teeth ≈ 0.295 per interpolation table
|
|
493
|
+
test('gear J (pinion)', g.pinion.J, 0.295, 0.01);
|
|
494
|
+
// σ_b (pinion) = 780·1·1·1.3 / (30·2·0.295) = 1014/17.7 ≈ 57.3 MPa
|
|
495
|
+
test('gear σ_b pinion', g.pinion.sigma_b, 57.3, 1.5);
|
|
496
|
+
// S_t @240HB = (77·240 + 12800)·psi→MPa = 31280·0.00689 ≈ 215.7 MPa
|
|
497
|
+
test('gear S_t @240HB', g.pinion.S_t, 215.7, 1.5);
|
|
498
|
+
// SF_bending_P ≈ 215.7 / 57.3 ≈ 3.77
|
|
499
|
+
test('gear SF_bending pinion', g.pinion.SF_bending, 3.77, 0.2);
|
|
500
|
+
|
|
501
|
+
// ------- SHAFT TEST — clean case with rotating-bending + constant torque -------
|
|
502
|
+
// AISI 1050 CD, d=25mm, M_a=100 N·m (rotating bending so M_m=0), T_m=50 N·m, T_a=0
|
|
503
|
+
// Kf=2.0 (fillet), Kfs=1.5, machined surface, R=0.99, T=25°C
|
|
504
|
+
const sh = shaftFatigueAnalysis({
|
|
505
|
+
material: '1050_cd', diameter: 25,
|
|
506
|
+
M_a: 100, M_m: 0, T_a: 0, T_m: 50,
|
|
507
|
+
Kf: 2.0, Kfs: 1.5, surface: 'machined', reliability: 0.99, temperatureC: 25
|
|
508
|
+
});
|
|
509
|
+
// σ_a = 32·100000 / (π·25³) = 32e5 / 49087 ≈ 65.2 MPa
|
|
510
|
+
test('shaft σ_a', sh.stresses.sigma_a, 65.2, 0.5);
|
|
511
|
+
// τ_m = 16·50000 / (π·25³) ≈ 16.3 MPa
|
|
512
|
+
test('shaft τ_m', sh.stresses.tau_m, 16.3, 0.3);
|
|
513
|
+
// σ_prime_a with Kf·σ_a only (no mean bending, no alternating torque) ≈ Kf·σ_a = 130.4
|
|
514
|
+
test('shaft σ′_a', sh.stresses.sigma_prime_a, 130.4, 1.0);
|
|
515
|
+
// σ_prime_m = √3 · Kfs · τ_m ≈ √3 · 1.5 · 16.3 ≈ 42.4
|
|
516
|
+
test('shaft σ′_m', sh.stresses.sigma_prime_m, 42.4, 1.0);
|
|
517
|
+
// Must be finite + positive
|
|
518
|
+
test('shaft n_Goodman finite', Number.isFinite(sh.n_Goodman) && sh.n_Goodman > 0 ? 1 : 0, 1, 0);
|
|
519
|
+
test('shaft n_Soderberg ≤ Goodman', (sh.n_Soderberg <= sh.n_Goodman + 1e-6) ? 1 : 0, 1, 0);
|
|
520
|
+
|
|
209
521
|
return { results, allPass: results.every(r => r.pass) };
|
|
210
522
|
}
|
|
211
523
|
|
|
@@ -585,8 +897,20 @@
|
|
|
585
897
|
// ===================================================================
|
|
586
898
|
let uiEl = null;
|
|
587
899
|
window.CycleCAD.AIEngineer = {
|
|
900
|
+
// ---- v1: bolted-joint ----
|
|
588
901
|
analyze: boltedJointAnalysis,
|
|
589
902
|
parsePrompt: parseBoltedJointPrompt,
|
|
903
|
+
// ---- v2: gears ----
|
|
904
|
+
analyzeGear: spurGearAnalysis,
|
|
905
|
+
gearAllowableBending,
|
|
906
|
+
gearAllowableContact,
|
|
907
|
+
gearGeometryJ,
|
|
908
|
+
gearGeometryI,
|
|
909
|
+
// ---- v2: shafts ----
|
|
910
|
+
analyzeShaft: shaftFatigueAnalysis,
|
|
911
|
+
SHAFT_MATERIALS,
|
|
912
|
+
SURFACE_FACTORS,
|
|
913
|
+
// ---- shared ----
|
|
590
914
|
runSelfTests,
|
|
591
915
|
STEEL_GRADES,
|
|
592
916
|
BOLT_STRESS_AREA,
|
|
@@ -597,17 +921,19 @@
|
|
|
597
921
|
if (!t.allPass) {
|
|
598
922
|
console.warn('[AI Engineer] self-test failures:', t.results.filter(r => !r.pass));
|
|
599
923
|
} else {
|
|
600
|
-
console.log('[AI Engineer] self-tests pass (' + t.results.length + '/' + t.results.length + '
|
|
924
|
+
console.log('[AI Engineer] self-tests pass (' + t.results.length + '/' + t.results.length + ' across bolted-joint + gears + shafts)');
|
|
601
925
|
}
|
|
602
926
|
return t.allPass;
|
|
603
927
|
},
|
|
604
928
|
getUI: () => { if (!uiEl) uiEl = buildUI(); return uiEl; },
|
|
605
929
|
execute: (cmd, params) => {
|
|
606
|
-
if (cmd === 'analyze')
|
|
607
|
-
if (cmd === '
|
|
608
|
-
if (cmd === '
|
|
930
|
+
if (cmd === 'analyze') return boltedJointAnalysis(params || {});
|
|
931
|
+
if (cmd === 'analyze-gear') return spurGearAnalysis(params || {});
|
|
932
|
+
if (cmd === 'analyze-shaft') return shaftFatigueAnalysis(params || {});
|
|
933
|
+
if (cmd === 'parse') return parseBoltedJointPrompt((params && params.prompt) || '');
|
|
934
|
+
if (cmd === 'show') { if (!uiEl) uiEl = buildUI(); return uiEl; }
|
|
609
935
|
}
|
|
610
936
|
};
|
|
611
937
|
|
|
612
|
-
console.log('AI Engineering Analyst
|
|
938
|
+
console.log('AI Engineering Analyst v2.0 module loaded (bolted-joint + gears + shafts)');
|
|
613
939
|
})();
|