cyclecad 0.2.3 → 0.3.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.
@@ -0,0 +1,1144 @@
1
+ /**
2
+ * GD&T (Geometric Dimensioning & Tolerancing) Training Module
3
+ * Comprehensive ASME Y14.5 reference, training, and interactive tools
4
+ * Inspired by gdandtbasics.com
5
+ */
6
+
7
+ window.cycleCAD = window.cycleCAD || {};
8
+
9
+ window.cycleCAD.gdtTraining = (function() {
10
+ 'use strict';
11
+
12
+ // GD&T Symbol Reference Database
13
+ const SYMBOLS = {
14
+ // Form Controls
15
+ flatness: {
16
+ name: 'Flatness',
17
+ unicode: '⏥',
18
+ svg: '<path d="M0,10 L40,10" stroke="currentColor" stroke-width="2" fill="none"/>',
19
+ category: 'Form Control',
20
+ asmReference: 'Y14.5-2018 § 8.2.1',
21
+ description: 'Controls surface flatness in 3D space',
22
+ whenToUse: 'Mating surfaces, optical elements, sealing surfaces',
23
+ toleranceZone: 'Two parallel planes separated by tolerance value',
24
+ requiresDatum: false,
25
+ example: 'Machine bed surface must be flat within 0.002"',
26
+ commonMistakes: 'Confusing flatness with perpendicularity, over-tolerancing mating surfaces'
27
+ },
28
+ straightness: {
29
+ name: 'Straightness',
30
+ unicode: '—',
31
+ svg: '<line x1="0" y1="10" x2="40" y2="10" stroke="currentColor" stroke-width="2"/>',
32
+ category: 'Form Control',
33
+ asmReference: 'Y14.5-2018 § 8.2.2',
34
+ description: 'Controls straightness of a line or axis',
35
+ whenToUse: 'Cylindrical features, precision shafts, slide rails',
36
+ toleranceZone: 'Two parallel lines (line element) or cylinder (axis)',
37
+ requiresDatum: false,
38
+ example: 'Shaft centerline must be straight within 0.001" TIR',
39
+ commonMistakes: 'Applying to surfaces instead of axis, ignoring RFS implications'
40
+ },
41
+ circularity: {
42
+ name: 'Circularity',
43
+ unicode: '○',
44
+ svg: '<circle cx="20" cy="10" r="8" stroke="currentColor" stroke-width="2" fill="none"/>',
45
+ category: 'Form Control',
46
+ asmReference: 'Y14.5-2018 § 8.2.3',
47
+ description: 'Controls roundness of a circular cross-section',
48
+ whenToUse: 'Rotating parts, spindle journals, bearing surfaces',
49
+ toleranceZone: 'Two concentric circles in single plane',
50
+ requiresDatum: false,
51
+ example: 'Pulley OD must be circular within 0.002" per cross-section',
52
+ commonMistakes: 'Confusing with cylindricity, not checking all cross-sections'
53
+ },
54
+ cylindricity: {
55
+ name: 'Cylindricity',
56
+ unicode: '⌭',
57
+ svg: '<ellipse cx="20" cy="10" rx="10" ry="6" stroke="currentColor" stroke-width="2" fill="none"/><path d="M10,4 L10,16 M30,4 L30,16" stroke="currentColor" stroke-width="1"/>',
58
+ category: 'Form Control',
59
+ asmReference: 'Y14.5-2018 § 8.2.4',
60
+ description: 'Controls overall cylindrical form (form + orientation)',
61
+ whenToUse: 'Precision bearings, pump shafts, precision cylinders',
62
+ toleranceZone: 'Two coaxial cylinders separated by tolerance value',
63
+ requiresDatum: false,
64
+ example: 'Drive shaft must be cylindrical within 0.003" over 6" length',
65
+ commonMistakes: 'Overusing when position would suffice, not considering axis orientation'
66
+ },
67
+ // Orientation Controls
68
+ perpendicularity: {
69
+ name: 'Perpendicularity',
70
+ unicode: '⊥',
71
+ svg: '<line x1="0" y1="15" x2="40" y2="15" stroke="currentColor" stroke-width="2"/><line x1="20" y1="0" x2="20" y2="20" stroke="currentColor" stroke-width="2"/>',
72
+ category: 'Orientation Control',
73
+ asmReference: 'Y14.5-2018 § 8.3.1',
74
+ description: 'Controls perpendicularity to a datum (90° angle)',
75
+ whenToUse: 'Hole patterns, mounting surfaces, mounting bosses',
76
+ toleranceZone: 'Parallel planes or cylinder perpendicular to datum',
77
+ requiresDatum: true,
78
+ example: 'Top face must be perpendicular to datum A within 0.005"',
79
+ commonMistakes: 'Applying without specifying datum, using for non-90° angles'
80
+ },
81
+ parallelism: {
82
+ name: 'Parallelism',
83
+ unicode: '//',
84
+ svg: '<line x1="5" y1="5" x2="35" y2="5" stroke="currentColor" stroke-width="2"/><line x1="5" y1="15" x2="35" y2="15" stroke="currentColor" stroke-width="2"/>',
85
+ category: 'Orientation Control',
86
+ asmReference: 'Y14.5-2018 § 8.3.2',
87
+ description: 'Controls parallelism to a datum feature',
88
+ whenToUse: 'Parallel surfaces, guide ways, stack-up tolerances',
89
+ toleranceZone: 'Two planes parallel to datum plane',
90
+ requiresDatum: true,
91
+ example: 'Bottom surface parallel to datum A within 0.003"',
92
+ commonMistakes: 'Tolerancing too tight, not accounting for form variation'
93
+ },
94
+ angularity: {
95
+ name: 'Angularity',
96
+ unicode: '∠',
97
+ svg: '<line x1="0" y1="20" x2="30" y2="20" stroke="currentColor" stroke-width="2"/><line x1="0" y1="20" x2="20" y2="5" stroke="currentColor" stroke-width="2"/><path d="M8,18 Q10,16 12,15" stroke="currentColor" stroke-width="1" fill="none"/>',
98
+ category: 'Orientation Control',
99
+ asmReference: 'Y14.5-2018 § 8.3.3',
100
+ description: 'Controls angle between feature and datum at specified angle',
101
+ whenToUse: 'Angled surfaces, beveled edges, chamfers',
102
+ toleranceZone: 'Two parallel planes at specified angle to datum',
103
+ requiresDatum: true,
104
+ example: 'Chamfer surface 45° to datum A, within 0.004"',
105
+ commonMistakes: 'Forgetting angle specification, using when perpendicularity applies'
106
+ },
107
+ // Location Controls
108
+ position: {
109
+ name: 'Position',
110
+ unicode: '⊕',
111
+ svg: '<circle cx="20" cy="10" r="6" stroke="currentColor" stroke-width="2" fill="none"/><line x1="14" y1="10" x2="26" y2="10" stroke="currentColor" stroke-width="2"/><line x1="20" y1="4" x2="20" y2="16" stroke="currentColor" stroke-width="2"/>',
112
+ category: 'Location Control',
113
+ asmReference: 'Y14.5-2018 § 8.4.1',
114
+ description: 'Controls true position from datum reference frame',
115
+ whenToUse: 'Hole patterns, mounting patterns, critical alignments',
116
+ toleranceZone: 'Cylinder around true position (diameter = 2× tolerance)',
117
+ requiresDatum: true,
118
+ example: 'Four M8 holes at true position ⊕ 0.015" MMC from datum A-B-C',
119
+ commonMistakes: 'Using coordinate tolerances instead, not applying MMC, forgetting datum sequence'
120
+ },
121
+ concentricity: {
122
+ name: 'Concentricity',
123
+ unicode: '◎',
124
+ svg: '<circle cx="20" cy="10" r="8" stroke="currentColor" stroke-width="2" fill="none"/><circle cx="20" cy="10" r="5" stroke="currentColor" stroke-width="1" fill="none"/><line x1="15" y1="10" x2="25" y2="10" stroke="currentColor" stroke-width="1"/>',
125
+ category: 'Location Control',
126
+ asmReference: 'Y14.5-2018 § 8.4.2',
127
+ description: 'Controls coaxial alignment of features',
128
+ whenToUse: 'Coaxial bores, stepped shafts, spindle cartridges',
129
+ toleranceZone: 'Cylinder around datum axis',
130
+ requiresDatum: true,
131
+ example: 'Inner bore concentricity to datum A within 0.005" TIR',
132
+ commonMistakes: 'Overusing instead of position, not understanding RFS cost'
133
+ },
134
+ symmetry: {
135
+ name: 'Symmetry',
136
+ unicode: '≡',
137
+ svg: '<line x1="5" y1="5" x2="35" y2="5" stroke="currentColor" stroke-width="2"/><line x1="5" y1="10" x2="35" y2="10" stroke="currentColor" stroke-width="2"/><line x1="5" y1="15" x2="35" y2="15" stroke="currentColor" stroke-width="2"/><line x1="5" y1="20" x2="35" y2="20" stroke="currentColor" stroke-width="2"/>',
138
+ category: 'Location Control',
139
+ asmReference: 'Y14.5-2018 § 8.4.3',
140
+ description: 'Controls symmetric positioning about datum plane',
141
+ whenToUse: 'Slot width, tab centering, mirrored features',
142
+ toleranceZone: 'Two parallel planes equidistant from datum',
143
+ requiresDatum: true,
144
+ example: 'Slot symmetrical to datum plane A within 0.004"',
145
+ commonMistakes: 'Confusing with parallelism, applying to non-symmetric features'
146
+ },
147
+ // Profile Controls
148
+ profileLine: {
149
+ name: 'Profile of a Line',
150
+ unicode: '⌒',
151
+ svg: '<path d="M5,15 Q15,5 35,10" stroke="currentColor" stroke-width="2" fill="none"/><path d="M5,17 Q15,7 35,12" stroke="currentColor" stroke-width="1" fill="none"/><path d="M5,13 Q15,3 35,8" stroke="currentColor" stroke-width="1" fill="none"/>',
152
+ category: 'Profile Control',
153
+ asmReference: 'Y14.5-2018 § 8.5.1',
154
+ description: 'Controls 2D profile in single plane (like profile cut)',
155
+ whenToUse: 'Turbine blades, cam profiles, irregular shapes',
156
+ toleranceZone: 'Two lines equidistant from true profile',
157
+ requiresDatum: false,
158
+ example: 'Cam profile ⌒ 0.010" bilateral',
159
+ commonMistakes: 'Not understanding bilateral vs unilateral, over-constraining'
160
+ },
161
+ profileSurface: {
162
+ name: 'Profile of a Surface',
163
+ unicode: '⌓',
164
+ svg: '<path d="M5,5 Q20,15 35,8" stroke="currentColor" stroke-width="2" fill="none"/><ellipse cx="20" cy="10" rx="15" ry="7" stroke="currentColor" stroke-width="1" fill="none"/>',
165
+ category: 'Profile Control',
166
+ asmReference: 'Y14.5-2018 § 8.5.2',
167
+ description: 'Controls 3D surface profile',
168
+ whenToUse: 'Complex 3D surfaces, aerodynamic shapes, artistic surfaces',
169
+ toleranceZone: 'Two surfaces equidistant from true profile',
170
+ requiresDatum: false,
171
+ example: 'Fuselage surface ⌓ 0.050" bilateral',
172
+ commonMistakes: 'Over-tolerancing, not using CAD for comparison'
173
+ },
174
+ // Runout Controls
175
+ circularRunout: {
176
+ name: 'Circular Runout',
177
+ unicode: '↗',
178
+ svg: '<circle cx="20" cy="10" r="8" stroke="currentColor" stroke-width="2" fill="none"/><path d="M20,2 L25,18" stroke="currentColor" stroke-width="2"/>',
179
+ category: 'Runout Control',
180
+ asmReference: 'Y14.5-2018 § 8.6.1',
181
+ description: 'Controls runout at single circular element',
182
+ whenToUse: 'Rotating parts, balancing, dynamic concentricity',
183
+ toleranceZone: 'FIR (Full Indicator Reading) at each circular element',
184
+ requiresDatum: true,
185
+ example: 'Gear OD circular runout 0.005" FIR to datum A',
186
+ commonMistakes: 'Confusing with total runout, not understanding FIR measurement'
187
+ },
188
+ totalRunout: {
189
+ name: 'Total Runout',
190
+ unicode: '↗↗',
191
+ svg: '<circle cx="20" cy="10" r="8" stroke="currentColor" stroke-width="2" fill="none"/><path d="M20,2 L25,18" stroke="currentColor" stroke-width="2"/><path d="M18,3 L23,19" stroke="currentColor" stroke-width="1.5"/>',
192
+ category: 'Runout Control',
193
+ asmReference: 'Y14.5-2018 § 8.6.2',
194
+ description: 'Controls runout across entire surface',
195
+ whenToUse: 'Critical bearings, pump shafts, spindle cartridges',
196
+ toleranceZone: 'FIR measured over full surface during one rotation',
197
+ requiresDatum: true,
198
+ example: 'Bearing OD total runout 0.003" FIR to datum A',
199
+ commonMistakes: 'Over-tolerancing, not measuring at multiple axial locations'
200
+ }
201
+ };
202
+
203
+ // Training Quiz Database
204
+ const QUIZZES = {
205
+ level1: [
206
+ {
207
+ question: 'What does GD&T stand for?',
208
+ options: [
209
+ 'General Dimensional Tolerance',
210
+ 'Geometric Dimensioning & Tolerancing',
211
+ 'Global Design & Testing',
212
+ 'Geometric Design Tolerance'
213
+ ],
214
+ correct: 1,
215
+ explanation: 'GD&T is Geometric Dimensioning & Tolerancing, the international standard for controlling size, shape, orientation, and position.'
216
+ },
217
+ {
218
+ question: 'What is Rule #1 (Envelope Principle)?',
219
+ options: [
220
+ 'All tolerances must be on the envelope',
221
+ 'Where no tolerance is specified, the tolerance is zero',
222
+ 'Perfect form at MMC boundary, then size tolerance applies',
223
+ 'Every feature must have a geometric tolerance'
224
+ ],
225
+ correct: 2,
226
+ explanation: 'Rule #1 states that at MMC, the feature cannot exceed a boundary of perfect form. As the feature departs from MMC, a form tolerance is permissible.'
227
+ },
228
+ {
229
+ question: 'What is a datum?',
230
+ options: [
231
+ 'A theoretical point in space',
232
+ 'A reference feature or surface used to establish a coordinate system',
233
+ 'The maximum material size',
234
+ 'A type of tolerance symbol'
235
+ ],
236
+ correct: 1,
237
+ explanation: 'A datum is a real or theoretical point, line, plane, cylinder, or sphere used as a reference to control geometric relationships.'
238
+ },
239
+ {
240
+ question: 'Which basic dimension is shown in a box?',
241
+ options: [
242
+ 'Bilateral tolerance',
243
+ 'Unilateral tolerance',
244
+ 'Basic dimension (theoretically exact)',
245
+ 'Limit dimension'
246
+ ],
247
+ correct: 2,
248
+ explanation: 'Basic dimensions are enclosed in a box and represent theoretically exact values used with GD&T. The tolerance is in the feature control frame.'
249
+ },
250
+ {
251
+ question: 'What material condition modifier means maximum material?',
252
+ options: [
253
+ 'LMC (Least Material Condition)',
254
+ 'MMC (Maximum Material Condition)',
255
+ 'RFS (Regardless of Feature Size)',
256
+ 'FFS (Fixed Feature Size)'
257
+ ],
258
+ correct: 1,
259
+ explanation: 'MMC (Ⓜ) means the feature has maximum amount of material, largest hole or smallest shaft. Bonus tolerance is maximum at MMC.'
260
+ },
261
+ {
262
+ question: 'What is a feature control frame?',
263
+ options: [
264
+ 'A frame that holds geometric features',
265
+ 'The box containing geometric tolerance symbol, tolerance value, and datum references',
266
+ 'A manufacturing fixture',
267
+ 'A testing apparatus'
268
+ ],
269
+ correct: 1,
270
+ explanation: 'A feature control frame is a rectangle divided into compartments containing the geometric characteristic symbol, tolerance, modifiers, and datum references.'
271
+ },
272
+ {
273
+ question: 'How many datum references are in a complete datum reference frame?',
274
+ options: [
275
+ 'One (primary)',
276
+ 'Two (primary + secondary)',
277
+ 'Three (primary + secondary + tertiary)',
278
+ 'Unlimited'
279
+ ],
280
+ correct: 2,
281
+ explanation: 'A complete datum reference frame has three mutually perpendicular planes: primary (origin), secondary (2nd plane), and tertiary (3rd plane).'
282
+ },
283
+ {
284
+ question: 'What is the difference between size tolerance and geometric tolerance?',
285
+ options: [
286
+ 'There is no difference',
287
+ 'Size controls +/- dimensions; geometry controls form, orientation, location, and runout',
288
+ 'Size tolerance is for holes, geometry is for shafts',
289
+ 'Geometric tolerance is older'
290
+ ],
291
+ correct: 1,
292
+ explanation: 'Size tolerance (e.g., ±0.005") controls the overall dimensions. Geometric tolerance controls form, orientation, location, profile, and runout independently.'
293
+ },
294
+ {
295
+ question: 'When should you use basic dimensions?',
296
+ options: [
297
+ 'Always, for all dimensions',
298
+ 'Never, they are obsolete',
299
+ 'When used with geometric tolerances for precise control',
300
+ 'Only for fasteners'
301
+ ],
302
+ correct: 2,
303
+ explanation: 'Basic dimensions are used as references with geometric tolerances to define the theoretically exact position, profile, or orientation of features.'
304
+ },
305
+ {
306
+ question: 'What does RFS (Regardless of Feature Size) mean?',
307
+ options: [
308
+ 'The tolerance applies at maximum material condition',
309
+ 'The tolerance applies at least material condition',
310
+ 'The tolerance applies regardless of the actual feature size',
311
+ 'The tolerance is zero'
312
+ ],
313
+ correct: 2,
314
+ explanation: 'RFS means the tolerance value does not change based on feature size—no bonus tolerance is available. This is the most restrictive condition.'
315
+ }
316
+ ],
317
+ level2: [
318
+ {
319
+ question: 'Which form control does NOT require a datum?',
320
+ options: [
321
+ 'Perpendicularity',
322
+ 'Circularity',
323
+ 'Parallelism',
324
+ 'Position'
325
+ ],
326
+ correct: 1,
327
+ explanation: 'Circularity is a form control that does not require a datum. It measures roundness in a single plane independent of the axis.'
328
+ },
329
+ {
330
+ question: 'What is the tolerance zone for flatness?',
331
+ options: [
332
+ 'Two concentric circles',
333
+ 'A cylinder',
334
+ 'Two parallel planes separated by the tolerance value',
335
+ 'A sphere'
336
+ ],
337
+ correct: 2,
338
+ explanation: 'Flatness creates a tolerance zone of two parallel planes. The surface must lie within these planes separated by the tolerance value.'
339
+ },
340
+ {
341
+ question: 'When would you apply straightness control?',
342
+ options: [
343
+ 'To control the flatness of a surface',
344
+ 'To control the straightness of an axis or line element',
345
+ 'To control the roundness of a bore',
346
+ 'To control the angle of a surface'
347
+ ],
348
+ correct: 1,
349
+ explanation: 'Straightness is applied to control the linearity of an axis (RFS) or individual line elements on a surface. It ensures the feature follows a straight path.'
350
+ },
351
+ {
352
+ question: 'What is the main difference between circularity and cylindricity?',
353
+ options: [
354
+ 'Circularity is 2D, cylindricity is 3D',
355
+ 'They are the same thing',
356
+ 'Circularity controls roundness; cylindricity controls roundness AND straightness',
357
+ 'Cylindricity is older'
358
+ ],
359
+ correct: 2,
360
+ explanation: 'Circularity checks roundness at each circular cross-section. Cylindricity checks roundness AND straightness across the entire cylinder, making it more restrictive.'
361
+ },
362
+ {
363
+ question: 'Can flatness tolerance be unilateral?',
364
+ options: [
365
+ 'Yes, always unilateral',
366
+ 'No, always bilateral',
367
+ 'Yes, if specified with an arrow',
368
+ 'Only for thickness'
369
+ ],
370
+ correct: 2,
371
+ explanation: 'Flatness tolerance is typically bilateral (±) but can be specified as unilateral (↑ or ↓) if required by design. The symbol placement indicates direction.'
372
+ },
373
+ {
374
+ question: 'How is straightness of an axis different from straightness of a line?',
375
+ options: [
376
+ 'There is no difference',
377
+ 'Axis straightness is RFS; line straightness has specific datum',
378
+ 'Axis straightness creates a cylindrical zone; line straightness creates two parallel planes in each plane',
379
+ 'Line straightness is obsolete'
380
+ ],
381
+ correct: 2,
382
+ explanation: 'Straightness applied to an axis creates a cylindrical tolerance zone. Applied to line elements (surfaces), it creates parallel plane boundaries in each cutting plane.'
383
+ },
384
+ {
385
+ question: 'What feature would you use circularity for?',
386
+ options: [
387
+ 'A cylindrical shaft over a long length',
388
+ 'A bearing race at a specific cross-section',
389
+ 'A flat surface',
390
+ 'An angled surface'
391
+ ],
392
+ correct: 1,
393
+ explanation: 'Circularity controls roundness at individual circular cross-sections. Use it for bearing races, rotor bores, or features needing round-at-a-station control.'
394
+ },
395
+ {
396
+ question: 'How tight should flatness be on a mating surface?',
397
+ options: [
398
+ 'It should be zero',
399
+ 'It should be equal to the surface finish',
400
+ 'It should be tight enough to ensure good contact (typically 10-50% of size tolerance)',
401
+ 'It should be very loose'
402
+ ],
403
+ correct: 2,
404
+ explanation: 'Flatness on mating surfaces controls contact quality. Typical practice is 10-50% of the size tolerance to balance cost and function.'
405
+ },
406
+ {
407
+ question: 'What is a common mistake with form controls?',
408
+ options: [
409
+ 'Applying them when size tolerances suffice',
410
+ 'Making them too loose',
411
+ 'Applying too many form controls to one feature',
412
+ 'All of the above'
413
+ ],
414
+ correct: 3,
415
+ explanation: 'Common mistakes include over-constraining with form controls when size tolerances would work, or combining multiple form controls unnecessarily.'
416
+ },
417
+ {
418
+ question: 'Can a feature have both straightness and circularity controls?',
419
+ options: [
420
+ 'No, only one control per feature',
421
+ 'Yes, if they serve different purposes',
422
+ 'Only on shafts',
423
+ 'Never in modern GD&T'
424
+ ],
425
+ correct: 1,
426
+ explanation: 'Yes, a cylindrical feature can have both circularity (roundness check) and straightness (axis straightness) if both characteristics need independent control.'
427
+ }
428
+ ],
429
+ level3: [
430
+ {
431
+ question: 'What does perpendicularity control?',
432
+ options: [
433
+ 'The roundness of a feature',
434
+ 'The angle of 90° between feature and datum',
435
+ 'The parallelism to a datum',
436
+ 'The profile shape'
437
+ ],
438
+ correct: 1,
439
+ explanation: 'Perpendicularity controls the 90° angle between a feature (surface, axis, or line) and a datum feature, with a tolerance zone of parallel planes perpendicular to the datum.'
440
+ },
441
+ {
442
+ question: 'When should you use perpendicularity vs angularity?',
443
+ options: [
444
+ 'They are interchangeable',
445
+ 'Perpendicularity for 90° angles; angularity for other angles',
446
+ 'Angularity is always better',
447
+ 'Perpendicularity never uses a datum'
448
+ ],
449
+ correct: 1,
450
+ explanation: 'Use perpendicularity for 90° relationships. Use angularity for any other specific angle (45°, 30°, etc.) between a feature and datum.'
451
+ },
452
+ {
453
+ question: 'What is the difference between parallelism and perpendicularity?',
454
+ options: [
455
+ 'There is no difference',
456
+ 'Parallelism = 0° angle to datum; Perpendicularity = 90° angle to datum',
457
+ 'Parallelism controls form',
458
+ 'Perpendicularity is older'
459
+ ],
460
+ correct: 1,
461
+ explanation: 'Parallelism controls features parallel (0° angle) to a datum. Perpendicularity controls features at 90° to a datum. Both require datum references.'
462
+ },
463
+ {
464
+ question: 'How do you specify angularity on a drawing?',
465
+ options: [
466
+ 'Just the tolerance value',
467
+ 'Tolerance value + angle value + datum reference',
468
+ 'Only the angle, no tolerance',
469
+ 'Angle in parentheses'
470
+ ],
471
+ correct: 1,
472
+ explanation: 'Angularity is specified with: the angle symbol ∠, the angle value (e.g., 45°), the tolerance value, and datum reference(s), typically shown as a basic dimension and FCF.'
473
+ },
474
+ {
475
+ question: 'Can perpendicularity be applied without a datum?',
476
+ options: [
477
+ 'Yes, always',
478
+ 'No, perpendicularity always requires a datum',
479
+ 'Only on cylindrical features',
480
+ 'Only on flat surfaces'
481
+ ],
482
+ correct: 1,
483
+ explanation: 'Perpendicularity is an orientation control and ALWAYS requires a datum reference to establish the 90° relationship.'
484
+ },
485
+ {
486
+ question: 'Which datum is most commonly used for perpendicularity?',
487
+ options: [
488
+ 'Secondary datum',
489
+ 'Primary datum (establishes the plane)',
490
+ 'Tertiary datum',
491
+ 'None (no datum)'
492
+ ],
493
+ correct: 1,
494
+ explanation: 'The primary datum for perpendicularity usually establishes the reference plane or surface, then the perpendicular feature is controlled to it.'
495
+ },
496
+ {
497
+ question: 'What is bonus tolerance in perpendicularity?',
498
+ options: [
499
+ 'Extra tolerance at no cost',
500
+ 'Increased tolerance when applied at MMC, equal to the material departure from MMC',
501
+ 'Tolerance that must be written in pencil',
502
+ 'There is no bonus tolerance for perpendicularity'
503
+ ],
504
+ correct: 1,
505
+ explanation: 'When perpendicularity is applied at MMC, bonus tolerance equals the difference between MMC and actual size, allowing larger geometric variation at smaller sizes.'
506
+ },
507
+ {
508
+ question: 'What is the tolerance zone for parallelism to a plane?',
509
+ options: [
510
+ 'A cylinder',
511
+ 'Two planes parallel to the datum plane',
512
+ 'A sphere',
513
+ 'A single plane'
514
+ ],
515
+ correct: 1,
516
+ explanation: 'Parallelism to a plane creates a tolerance zone of two parallel planes, equally disposed or unilateral, relative to the datum plane.'
517
+ },
518
+ {
519
+ question: 'How does RFS affect orientation controls?',
520
+ options: [
521
+ 'It makes them larger',
522
+ 'It makes them smaller',
523
+ 'It eliminates bonus tolerance',
524
+ 'It has no effect'
525
+ ],
526
+ correct: 2,
527
+ explanation: 'RFS on an orientation control (default condition) provides no bonus tolerance. The tolerance remains constant regardless of feature size.'
528
+ },
529
+ {
530
+ question: 'Can you apply parallelism to the centerline of a hole?',
531
+ options: [
532
+ 'No, parallelism is only for surfaces',
533
+ 'Yes, if you want the hole axis parallel to a datum axis',
534
+ 'Only if the hole is very large',
535
+ 'Never in modern GD&T'
536
+ ],
537
+ correct: 1,
538
+ explanation: 'Yes, parallelism can control the axis of a hole (or other cylindrical feature) to be parallel to a datum axis or plane, creating a cylindrical tolerance zone.'
539
+ }
540
+ ],
541
+ level4: [
542
+ {
543
+ question: 'What is true position?',
544
+ options: [
545
+ 'The actual measured location',
546
+ 'The theoretically exact location, established by basic dimensions and datums',
547
+ 'The maximum allowable size',
548
+ 'The same as coordinate tolerances'
549
+ ],
550
+ correct: 1,
551
+ explanation: 'True position is the theoretically perfect location of a feature from the datum reference frame, established by basic dimensions. Position tolerance controls variation from true position.'
552
+ },
553
+ {
554
+ question: 'What is the advantage of position with MMC vs coordinate tolerances?',
555
+ options: [
556
+ 'Position is always tighter',
557
+ 'Coordinate tolerances are always better',
558
+ 'Position allows bonus tolerance and is more efficient; coordinate gives rigid, unchanging tolerance',
559
+ 'There is no advantage'
560
+ ],
561
+ correct: 2,
562
+ explanation: 'Position with MMC provides bonus tolerance equal to size variation, allowing ~57% larger functional zone than rectangular (coordinate) tolerances for the same nominal holes.'
563
+ },
564
+ {
565
+ question: 'How is the tolerance zone for position defined?',
566
+ options: [
567
+ 'Two parallel planes',
568
+ 'A cylinder (circular zone) or sphere, diameter = 2× tolerance value',
569
+ 'Two concentric circles',
570
+ 'A rectangular box'
571
+ ],
572
+ correct: 1,
573
+ explanation: 'Position tolerance creates a cylindrical tolerance zone around true position. Diameter of cylinder = 2× the position tolerance value (more efficient than rectangular zones).'
574
+ },
575
+ {
576
+ question: 'When would you use concentricity instead of position?',
577
+ options: [
578
+ 'Always use position',
579
+ 'Only for coaxial features with tight alignment and no size variation bonus needed',
580
+ 'Concentricity is obsolete',
581
+ 'Never use concentricity'
582
+ ],
583
+ correct: 1,
584
+ explanation: 'Concentricity (RFS only) is restrictive and expensive. Use position (MMC) for coaxial holes/shafts. Use concentricity only when RFS is mandatory and coaxiality is critical.'
585
+ },
586
+ {
587
+ question: 'What is symmetry tolerance?',
588
+ options: [
589
+ 'Makes a feature symmetrical',
590
+ 'Controls symmetric positioning about a datum plane, ensuring equal distance on both sides',
591
+ 'Controls the flatness of a surface',
592
+ 'A form control'
593
+ ],
594
+ correct: 1,
595
+ explanation: 'Symmetry controls the centerline or median plane of a feature (like slot width or tab position) to be symmetric about a datum plane, within the tolerance value.'
596
+ },
597
+ {
598
+ question: 'How is profile of a line different from profile of a surface?',
599
+ options: [
600
+ 'They are the same',
601
+ 'Profile of a line controls 2D profile in a single plane; profile of a surface controls 3D shape',
602
+ 'Profile of a surface is for external features only',
603
+ 'Profile of a line is obsolete'
604
+ ],
605
+ correct: 1,
606
+ explanation: 'Profile of a line is 2D (like a cross-section or slice through the part). Profile of a surface is 3D (the entire surface shape), more stringent and complex.'
607
+ },
608
+ {
609
+ question: 'Can position tolerance be unilateral?',
610
+ options: [
611
+ 'Yes, always',
612
+ 'No, always bilateral',
613
+ 'Yes, if specified; typically used when feature must not go in one direction',
614
+ 'Only for large holes'
615
+ ],
616
+ correct: 2,
617
+ explanation: 'Position tolerance can be bilateral (zone around true position) or unilateral (zone on one side of true position), specified by annotation on the drawing.'
618
+ },
619
+ {
620
+ question: 'What does a floating fastener formula calculate?',
621
+ options: [
622
+ 'The bolt size',
623
+ 'Hole diameter minus fastener diameter gives available position tolerance',
624
+ 'The tightest tolerance possible',
625
+ 'The cost of fasteners'
626
+ ],
627
+ correct: 1,
628
+ explanation: 'Floating fastener formula: T = H - F (Tolerance = Hole diameter minus Fastener diameter). Half of this is applied to each hole for symmetric tolerance budget.'
629
+ },
630
+ {
631
+ question: 'How do you specify profile bilateral vs unilateral?',
632
+ options: [
633
+ 'No difference in specification',
634
+ 'Bilateral (±) is default; unilateral uses one-sided arrow (↑ or ↓) placed on drawing view',
635
+ 'Only bilateral is allowed',
636
+ 'Profile must be centered'
637
+ ],
638
+ correct: 1,
639
+ explanation: 'Profile is bilateral (±) by default. For unilateral, the direction is shown with arrows (↑ inside, ↓ outside) next to the profile tolerance symbol.'
640
+ },
641
+ {
642
+ question: 'What causes a common mistake in position tolerancing?',
643
+ options: [
644
+ 'Over-tolerancing',
645
+ 'Using cartesian tolerances instead; not applying MMC; wrong datum sequence',
646
+ 'Under-tolerancing always',
647
+ 'Using concentricity'
648
+ ],
649
+ correct: 1,
650
+ explanation: 'Common errors: using rectangular (cartesian) coordinate tolerances instead of position; forgetting MMC allows bonus tolerance; incorrect datum sequence affects feature alignment.'
651
+ }
652
+ ],
653
+ level5: [
654
+ {
655
+ question: 'What is the difference between circular runout and total runout?',
656
+ options: [
657
+ 'They are the same',
658
+ 'Circular runout checks FIR at each circular element; total runout checks FIR over entire surface in one rotation',
659
+ 'Circular runout is obsolete',
660
+ 'Total runout is for 2D profiles'
661
+ ],
662
+ correct: 1,
663
+ explanation: 'Circular runout measures indicator movement at single circular element (local check). Total runout measures across entire surface in one rotation (global check)—more stringent.'
664
+ },
665
+ {
666
+ question: 'What does FIR (Full Indicator Reading) mean in runout?',
667
+ options: [
668
+ 'The total thickness',
669
+ 'The difference between high and low indicator readings as part rotates',
670
+ 'The final inspection result',
671
+ 'The finished radius'
672
+ ],
673
+ correct: 1,
674
+ explanation: 'FIR is the full indicator reading: the difference between maximum and minimum dial readings as the part completes one full rotation about the datum axis.'
675
+ },
676
+ {
677
+ question: 'When would you use total runout instead of circular runout?',
678
+ options: [
679
+ 'Total runout is never used',
680
+ 'When entire surface must be concentric (bearings, spindles, critical dynamic balance)',
681
+ 'Only for shafts',
682
+ 'When form tolerance suffices'
683
+ ],
684
+ correct: 1,
685
+ explanation: 'Use total runout for critical rotating surfaces (bearings, pump rotors, spindle cartridges) where the entire surface must maintain runout in a single rotation.'
686
+ },
687
+ {
688
+ question: 'What is composite position tolerance?',
689
+ options: [
690
+ 'Position applied twice with different datums',
691
+ 'Tighter position pattern for hole-to-hole spacing, looser pattern for position from datums',
692
+ 'Always less restrictive',
693
+ 'A form control'
694
+ ],
695
+ correct: 1,
696
+ explanation: 'Composite position has two rows: upper controls position from primary datum (looser, establishes pattern); lower controls hole-to-hole spacing (tighter, relative positioning).'
697
+ },
698
+ {
699
+ question: 'What is a projected tolerance zone?',
700
+ options: [
701
+ 'The zone visible on a projection',
702
+ 'A zone extending beyond the part (for threaded holes or press-fits to control stack-up)',
703
+ 'Always cylindrical',
704
+ 'Rarely used'
705
+ ],
706
+ correct: 1,
707
+ explanation: 'Projected tolerance zone extends vertically above the part surface (symbol: P with height box). Used for threaded holes, studs, or press-fits to control mating part clearance.'
708
+ },
709
+ {
710
+ question: 'What is free state variation?',
711
+ options: [
712
+ 'Random tolerance',
713
+ 'Geometric control allowing for part relaxation when unconstrained (e.g., sheet metal, castings)',
714
+ 'No tolerance',
715
+ 'A type of surface finish'
716
+ ],
717
+ correct: 1,
718
+ explanation: 'Free state notation (⟜ symbol) permits geometric variation for parts not supported, accounting for material relaxation (common in sheet metal and thin-wall castings).'
719
+ },
720
+ {
721
+ question: 'How does statistical tolerancing differ from worst-case?',
722
+ options: [
723
+ 'No difference',
724
+ 'Worst-case: all tolerances stack additively (conservative); statistical: uses standard deviation (efficient but requires process capability)',
725
+ 'Statistical is always allowed',
726
+ 'Worst-case is obsolete'
727
+ ],
728
+ correct: 1,
729
+ explanation: 'Worst-case stacks all tolerances additively (safe, loose final tolerance). Statistical uses RSS (root sum square) assuming normal distribution (tighter, requires Cpk ≥1.33).'
730
+ },
731
+ {
732
+ question: 'What is the purpose of a datum target?',
733
+ options: [
734
+ 'To specify target size',
735
+ 'To define a datum when no sufficient existing feature exists (using points, lines, or areas)',
736
+ 'To measure tolerance',
737
+ 'To specify material condition'
738
+ ],
739
+ correct: 1,
740
+ explanation: 'Datum targets (points, lines, or areas on a complex surface) establish a datum when no suitable existing feature can serve as the datum.'
741
+ },
742
+ {
743
+ question: 'How do you specify concentricity at MMC vs RFS?',
744
+ options: [
745
+ 'Same way',
746
+ 'Concentricity is RFS only (no MMC modifier allowed)',
747
+ 'Always MMC is tighter',
748
+ 'Never use concentricity'
749
+ ],
750
+ correct: 1,
751
+ explanation: 'Concentricity is RFS by default (most restrictive and expensive). MMC modifier is rarely used because concentricity cost is already high. No bonus applies to concentricity.'
752
+ },
753
+ {
754
+ question: 'What is the key to applying GD&T effectively?',
755
+ options: [
756
+ 'Apply as many controls as possible',
757
+ 'Use tight tolerances for everything',
758
+ 'Apply only necessary controls to function and manufacturability',
759
+ 'GD&T is only theoretical'
760
+ ],
761
+ correct: 2,
762
+ explanation: 'Effective GD&T applies the minimum necessary controls (form, orientation, location, runout) to ensure function, assembly, and manufacturability without over-constraining.'
763
+ }
764
+ ]
765
+ };
766
+
767
+ // Tolerance Calculator Functions
768
+ function calculatePositionBonus(mmcSize, actualSize) {
769
+ return Math.max(0, mmcSize - actualSize);
770
+ }
771
+
772
+ function floatingFastenerTolerance(holeDiameter, fastenerDiameter) {
773
+ return holeDiameter - fastenerDiameter;
774
+ }
775
+
776
+ function fixedFastenerTolerance(holeDiameter, boltTolerance, fastenerDiameter) {
777
+ return holeDiameter - fastenerDiameter - boltTolerance;
778
+ }
779
+
780
+ function toleranceStackWorstCase(tolerances) {
781
+ return tolerances.reduce((sum, t) => sum + Math.abs(t), 0);
782
+ }
783
+
784
+ function toleranceStackRSS(tolerances) {
785
+ const sumSquares = tolerances.reduce((sum, t) => sum + (t * t), 0);
786
+ return Math.sqrt(sumSquares);
787
+ }
788
+
789
+ // Drawing Annotation Suggestions
790
+ function suggestAnnotations(partGeometry) {
791
+ const suggestions = [];
792
+
793
+ if (partGeometry.hasMatingFaces) {
794
+ suggestions.push({
795
+ feature: 'Top face',
796
+ annotation: 'Flatness 0.002" + Perpendicularity to Datum A 0.003"',
797
+ reason: 'Controls contact quality and assembly alignment'
798
+ });
799
+ }
800
+
801
+ if (partGeometry.hasHoles) {
802
+ suggestions.push({
803
+ feature: 'Mounting holes',
804
+ annotation: 'Position ⊕ 0.015" MMC from Datum A-B-C',
805
+ reason: 'Critical for assembly accuracy and fastener alignment'
806
+ });
807
+ }
808
+
809
+ if (partGeometry.hasShaft) {
810
+ suggestions.push({
811
+ feature: 'Drive shaft',
812
+ annotation: 'Cylindricity 0.003" + Total Runout 0.005" FIR',
813
+ reason: 'Ensures dynamic balance and smooth rotation'
814
+ });
815
+ }
816
+
817
+ return suggestions;
818
+ }
819
+
820
+ // Feature Control Frame Builder
821
+ function buildFCF(options) {
822
+ const {
823
+ characteristic = 'position',
824
+ toleranceValue = 0.010,
825
+ symbol = SYMBOLS[characteristic],
826
+ hasDiameter = true,
827
+ primaryDatum = 'A',
828
+ secondaryDatum = null,
829
+ tertiaryDatum = null,
830
+ materialCondition = 'MMC'
831
+ } = options;
832
+
833
+ const diameterSymbol = hasDiameter ? '⌀' : '';
834
+ const conditionSymbol = materialCondition === 'MMC' ? 'Ⓜ' : materialCondition === 'LMC' ? 'Ⓛ' : '';
835
+
836
+ const fcfHTML = `
837
+ <div class="fcf-diagram" style="display: inline-block; border: 2px solid currentColor; padding: 4px; font-family: monospace; font-size: 12px;">
838
+ <div style="display: flex; align-items: center; gap: 2px;">
839
+ <div style="border: 1px solid currentColor; padding: 2px 4px; min-width: 20px; text-align: center;">${symbol.unicode}</div>
840
+ <div style="border: 1px solid currentColor; padding: 2px 4px;">${diameterSymbol} ${toleranceValue.toFixed(3)}"</div>
841
+ ${conditionSymbol ? `<div style="border: 1px solid currentColor; padding: 2px 4px;">${conditionSymbol}</div>` : ''}
842
+ <div style="border: 1px solid currentColor; padding: 2px 4px;">${primaryDatum}</div>
843
+ ${secondaryDatum ? `<div style="border: 1px solid currentColor; padding: 2px 4px;">${secondaryDatum}</div>` : ''}
844
+ ${tertiaryDatum ? `<div style="border: 1px solid currentColor; padding: 2px 4px;">${tertiaryDatum}</div>` : ''}
845
+ </div>
846
+ </div>
847
+ `;
848
+
849
+ const notation = `${symbol.unicode} ${diameterSymbol}${toleranceValue.toFixed(3)}" ${conditionSymbol} | ${primaryDatum}${secondaryDatum ? ' ' + secondaryDatum : ''}${tertiaryDatum ? ' ' + tertiaryDatum : ''}`;
850
+
851
+ return {
852
+ html: fcfHTML,
853
+ notation: notation.trim(),
854
+ data: {
855
+ characteristic,
856
+ toleranceValue,
857
+ hasDiameter,
858
+ primaryDatum,
859
+ secondaryDatum,
860
+ tertiaryDatum,
861
+ materialCondition
862
+ }
863
+ };
864
+ }
865
+
866
+ // Get UI Panel
867
+ function getUI() {
868
+ const tabs = ['Symbols', 'Training', 'FCF Builder', 'Calculator'];
869
+ const tabsHTML = tabs.map((tab, i) =>
870
+ `<button class="gdt-tab-btn" data-tab="${i}" style="padding: 8px 12px; background: ${i === 0 ? '#58a6ff' : 'transparent'}; color: #e6edf3; border: none; cursor: pointer; font-weight: 500;">${tab}</button>`
871
+ ).join('');
872
+
873
+ const symbolsGrid = Object.values(SYMBOLS).map(sym => `
874
+ <div class="gdt-symbol-card" style="background: #262626; padding: 12px; border: 1px solid #444; border-radius: 6px; cursor: pointer;">
875
+ <div style="font-size: 24px; margin-bottom: 8px;">${sym.unicode}</div>
876
+ <div style="font-weight: bold; color: #58a6ff; font-size: 13px;">${sym.name}</div>
877
+ <div style="color: #999; font-size: 11px; margin-top: 4px;">${sym.category}</div>
878
+ <div class="gdt-symbol-details" style="display: none; margin-top: 8px; border-top: 1px solid #444; padding-top: 8px; font-size: 11px;">
879
+ <div><strong>Requires Datum:</strong> ${sym.requiresDatum ? 'Yes' : 'No'}</div>
880
+ <div style="margin-top: 4px;"><strong>When to Use:</strong> ${sym.whenToUse}</div>
881
+ <div style="margin-top: 4px;"><strong>Tolerance Zone:</strong> ${sym.toleranceZone}</div>
882
+ <div style="margin-top: 4px;"><strong>Example:</strong> ${sym.example}</div>
883
+ </div>
884
+ </div>
885
+ `).join('');
886
+
887
+ const trainingLevels = [1, 2, 3, 4, 5].map(level => `
888
+ <div class="gdt-level-card" style="background: #262626; padding: 12px; border: 1px solid #444; border-radius: 6px; margin-bottom: 8px;">
889
+ <div style="display: flex; justify-content: space-between; align-items: center;">
890
+ <div>
891
+ <div style="font-weight: bold; color: #58a6ff; font-size: 14px;">Level ${level}: ${['Fundamentals', 'Form Controls', 'Orientation Controls', 'Location & Profile', 'Runout & Advanced'][level-1]}</div>
892
+ <div style="color: #999; font-size: 12px; margin-top: 4px;">10 questions</div>
893
+ </div>
894
+ <button class="gdt-start-quiz" data-level="${level}" style="padding: 6px 12px; background: #238636; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">Start</button>
895
+ </div>
896
+ </div>
897
+ `).join('');
898
+
899
+ const fcfBuilderForm = `
900
+ <div style="display: grid; gap: 8px; background: #262626; padding: 12px; border-radius: 6px;">
901
+ <div>
902
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold;">Geometric Characteristic</label>
903
+ <select class="gdt-fcf-characteristic" style="width: 100%; padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px; margin-top: 4px;">
904
+ <option value="flatness">Flatness</option>
905
+ <option value="straightness">Straightness</option>
906
+ <option value="circularity">Circularity</option>
907
+ <option value="cylindricity">Cylindricity</option>
908
+ <option value="perpendicularity">Perpendicularity</option>
909
+ <option value="parallelism">Parallelism</option>
910
+ <option value="angularity">Angularity</option>
911
+ <option value="position" selected>Position</option>
912
+ <option value="concentricity">Concentricity</option>
913
+ <option value="symmetry">Symmetry</option>
914
+ <option value="profileLine">Profile of a Line</option>
915
+ <option value="profileSurface">Profile of a Surface</option>
916
+ <option value="circularRunout">Circular Runout</option>
917
+ <option value="totalRunout">Total Runout</option>
918
+ </select>
919
+ </div>
920
+ <div>
921
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold;">Tolerance Value</label>
922
+ <input type="number" class="gdt-fcf-tolerance" step="0.001" value="0.010" style="width: 100%; padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px; margin-top: 4px;">
923
+ </div>
924
+ <div style="display: flex; gap: 8px;">
925
+ <div style="flex: 1;">
926
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold;">Diameter Modifier</label>
927
+ <input type="checkbox" class="gdt-fcf-diameter" checked style="margin-top: 8px; cursor: pointer;">
928
+ </div>
929
+ <div style="flex: 1;">
930
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold;">Material Condition</label>
931
+ <select class="gdt-fcf-material" style="width: 100%; padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px; margin-top: 4px;">
932
+ <option value="MMC">MMC (Ⓜ)</option>
933
+ <option value="LMC">LMC (Ⓛ)</option>
934
+ <option value="RFS">RFS</option>
935
+ </select>
936
+ </div>
937
+ </div>
938
+ <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px;">
939
+ <div>
940
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold;">Primary Datum</label>
941
+ <select class="gdt-fcf-datum-a" style="width: 100%; padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px; margin-top: 4px;">
942
+ <option>A</option><option>B</option><option>C</option><option>D</option>
943
+ </select>
944
+ </div>
945
+ <div>
946
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold;">Secondary Datum</label>
947
+ <select class="gdt-fcf-datum-b" style="width: 100%; padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px; margin-top: 4px;">
948
+ <option value="">None</option><option>B</option><option>C</option><option>D</option>
949
+ </select>
950
+ </div>
951
+ <div>
952
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold;">Tertiary Datum</label>
953
+ <select class="gdt-fcf-datum-c" style="width: 100%; padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px; margin-top: 4px;">
954
+ <option value="">None</option><option>C</option><option>D</option>
955
+ </select>
956
+ </div>
957
+ </div>
958
+ <button class="gdt-build-fcf" style="padding: 8px 12px; background: #238636; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; margin-top: 8px;">Build FCF</button>
959
+ <div class="gdt-fcf-output" style="margin-top: 8px; padding: 8px; background: #1e1e1e; border: 1px solid #444; border-radius: 4px; font-family: monospace; font-size: 12px; min-height: 40px; color: #58a6ff;"></div>
960
+ </div>
961
+ `;
962
+
963
+ const calculatorForm = `
964
+ <div style="display: grid; gap: 8px; background: #262626; padding: 12px; border-radius: 6px;">
965
+ <div>
966
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold; display: block;">Position Bonus Tolerance</label>
967
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 4px;">
968
+ <input type="number" class="gdt-calc-mmc" placeholder="MMC Size" step="0.001" style="padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px;">
969
+ <input type="number" class="gdt-calc-actual" placeholder="Actual Size" step="0.001" style="padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px;">
970
+ </div>
971
+ <button class="gdt-calc-bonus" style="width: 100%; padding: 6px; margin-top: 6px; background: #238636; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">Calculate Bonus</button>
972
+ <div class="gdt-calc-bonus-result" style="margin-top: 6px; color: #58a6ff; font-size: 12px;"></div>
973
+ </div>
974
+ <div style="border-top: 1px solid #444; padding-top: 8px;">
975
+ <label style="color: #e6edf3; font-size: 12px; font-weight: bold; display: block;">Floating Fastener Tolerance</label>
976
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 4px;">
977
+ <input type="number" class="gdt-calc-hole" placeholder="Hole Diameter" step="0.001" style="padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px;">
978
+ <input type="number" class="gdt-calc-fastener" placeholder="Fastener Diameter" step="0.001" style="padding: 6px; background: #1e1e1e; color: #e6edf3; border: 1px solid #444; border-radius: 4px;">
979
+ </div>
980
+ <button class="gdt-calc-fastener-btn" style="width: 100%; padding: 6px; margin-top: 6px; background: #238636; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">Calculate Tolerance</button>
981
+ <div class="gdt-calc-fastener-result" style="margin-top: 6px; color: #58a6ff; font-size: 12px;"></div>
982
+ </div>
983
+ </div>
984
+ `;
985
+
986
+ return `
987
+ <div class="gdt-panel" style="background: #1e1e1e; color: #e6edf3; border-radius: 6px; overflow: hidden; max-height: 600px; display: flex; flex-direction: column;">
988
+ <div style="display: flex; border-bottom: 1px solid #444; background: #262626;">
989
+ ${tabsHTML}
990
+ </div>
991
+ <div style="flex: 1; overflow-y: auto; padding: 12px;">
992
+ <div class="gdt-tab-content" data-content="0" style="display: block;">
993
+ <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px;">
994
+ ${symbolsGrid}
995
+ </div>
996
+ </div>
997
+ <div class="gdt-tab-content" data-content="1" style="display: none;">
998
+ ${trainingLevels}
999
+ </div>
1000
+ <div class="gdt-tab-content" data-content="2" style="display: none;">
1001
+ ${fcfBuilderForm}
1002
+ </div>
1003
+ <div class="gdt-tab-content" data-content="3" style="display: none;">
1004
+ ${calculatorForm}
1005
+ </div>
1006
+ </div>
1007
+ </div>
1008
+ `;
1009
+ }
1010
+
1011
+ // Quiz Renderer
1012
+ function renderQuiz(level) {
1013
+ const quizData = QUIZZES[`level${level}`];
1014
+ const quizHTML = quizData.map((q, i) => `
1015
+ <div class="gdt-quiz-question" data-question="${i}" style="background: #262626; padding: 12px; border-radius: 6px; margin-bottom: 12px;">
1016
+ <div style="font-weight: bold; color: #58a6ff; margin-bottom: 8px;">Q${i + 1}: ${q.question}</div>
1017
+ <div style="display: grid; gap: 6px;">
1018
+ ${q.options.map((opt, j) => `
1019
+ <label style="display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 6px; background: #1e1e1e; border-radius: 4px; border: 1px solid #444;">
1020
+ <input type="radio" name="q${i}" value="${j}" style="cursor: pointer;">
1021
+ <span>${opt}</span>
1022
+ </label>
1023
+ `).join('')}
1024
+ </div>
1025
+ </div>
1026
+ `).join('');
1027
+
1028
+ return quizHTML;
1029
+ }
1030
+
1031
+ // Initialize Module
1032
+ function init(containerEl) {
1033
+ if (!containerEl) return;
1034
+ containerEl.innerHTML = getUI();
1035
+
1036
+ // Tab switching
1037
+ containerEl.querySelectorAll('.gdt-tab-btn').forEach(btn => {
1038
+ btn.addEventListener('click', (e) => {
1039
+ const tabIndex = e.target.dataset.tab;
1040
+ containerEl.querySelectorAll('.gdt-tab-btn').forEach(b => b.style.background = 'transparent');
1041
+ containerEl.querySelectorAll('.gdt-tab-content').forEach(c => c.style.display = 'none');
1042
+ e.target.style.background = '#58a6ff';
1043
+ containerEl.querySelector(`[data-content="${tabIndex}"]`).style.display = 'block';
1044
+ });
1045
+ });
1046
+
1047
+ // Symbol card detail toggle
1048
+ containerEl.querySelectorAll('.gdt-symbol-card').forEach(card => {
1049
+ card.addEventListener('click', () => {
1050
+ const details = card.querySelector('.gdt-symbol-details');
1051
+ details.style.display = details.style.display === 'none' ? 'block' : 'none';
1052
+ });
1053
+ });
1054
+
1055
+ // Quiz launcher
1056
+ containerEl.querySelectorAll('.gdt-start-quiz').forEach(btn => {
1057
+ btn.addEventListener('click', (e) => {
1058
+ const level = parseInt(e.target.dataset.level);
1059
+ const quizHTML = renderQuiz(level);
1060
+ const quizPanel = document.createElement('div');
1061
+ quizPanel.innerHTML = `
1062
+ <div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z: 10000;">
1063
+ <div style="background: #1e1e1e; color: #e6edf3; border-radius: 8px; padding: 20px; max-width: 600px; max-height: 80vh; overflow-y: auto; border: 1px solid #444;">
1064
+ <div style="font-size: 18px; font-weight: bold; color: #58a6ff; margin-bottom: 16px;">Level ${level} Quiz (10 Questions)</div>
1065
+ ${quizHTML}
1066
+ <button class="gdt-submit-quiz" style="width: 100%; padding: 10px; background: #238636; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; margin-top: 12px;">Submit Quiz</button>
1067
+ <button class="gdt-close-quiz" style="width: 100%; padding: 10px; background: #444; color: #e6edf3; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; margin-top: 8px;">Close</button>
1068
+ </div>
1069
+ </div>
1070
+ `;
1071
+ document.body.appendChild(quizPanel);
1072
+
1073
+ quizPanel.querySelector('.gdt-submit-quiz').addEventListener('click', () => {
1074
+ let score = 0;
1075
+ quizPanel.querySelectorAll('.gdt-quiz-question').forEach((q, i) => {
1076
+ const selected = q.querySelector('input[type="radio"]:checked');
1077
+ if (selected && parseInt(selected.value) === QUIZZES[`level${level}`][i].correct) {
1078
+ score++;
1079
+ }
1080
+ });
1081
+ alert(`Quiz Complete!\nScore: ${score}/10 (${(score/10*100).toFixed(0)}%)\n\nProgress saved to localStorage.`);
1082
+ localStorage.setItem(`gdt_level${level}_score`, score);
1083
+ quizPanel.remove();
1084
+ });
1085
+
1086
+ quizPanel.querySelector('.gdt-close-quiz').addEventListener('click', () => quizPanel.remove());
1087
+ });
1088
+ });
1089
+
1090
+ // FCF Builder
1091
+ containerEl.querySelector('.gdt-build-fcf')?.addEventListener('click', () => {
1092
+ const char = containerEl.querySelector('.gdt-fcf-characteristic').value;
1093
+ const tolerance = parseFloat(containerEl.querySelector('.gdt-fcf-tolerance').value);
1094
+ const hasDia = containerEl.querySelector('.gdt-fcf-diameter').checked;
1095
+ const material = containerEl.querySelector('.gdt-fcf-material').value;
1096
+ const datumA = containerEl.querySelector('.gdt-fcf-datum-a').value;
1097
+ const datumB = containerEl.querySelector('.gdt-fcf-datum-b').value || null;
1098
+ const datumC = containerEl.querySelector('.gdt-fcf-datum-c').value || null;
1099
+
1100
+ const fcf = buildFCF({
1101
+ characteristic: char,
1102
+ toleranceValue: tolerance,
1103
+ hasDiameter: hasDia,
1104
+ materialCondition: material,
1105
+ primaryDatum: datumA,
1106
+ secondaryDatum: datumB,
1107
+ tertiaryDatum: datumC
1108
+ });
1109
+
1110
+ containerEl.querySelector('.gdt-fcf-output').innerHTML = fcf.html + `<div style="margin-top: 8px; color: #e6edf3;">Notation: ${fcf.notation}</div>`;
1111
+ });
1112
+
1113
+ // Calculator
1114
+ containerEl.querySelector('.gdt-calc-bonus')?.addEventListener('click', () => {
1115
+ const mmc = parseFloat(containerEl.querySelector('.gdt-calc-mmc').value);
1116
+ const actual = parseFloat(containerEl.querySelector('.gdt-calc-actual').value);
1117
+ const bonus = calculatePositionBonus(mmc, actual);
1118
+ containerEl.querySelector('.gdt-calc-bonus-result').textContent = `Bonus Tolerance: ${bonus.toFixed(4)}"`;
1119
+ });
1120
+
1121
+ containerEl.querySelector('.gdt-calc-fastener-btn')?.addEventListener('click', () => {
1122
+ const hole = parseFloat(containerEl.querySelector('.gdt-calc-hole').value);
1123
+ const fastener = parseFloat(containerEl.querySelector('.gdt-calc-fastener').value);
1124
+ const tolerance = floatingFastenerTolerance(hole, fastener);
1125
+ containerEl.querySelector('.gdt-calc-fastener-result').textContent = `Total Tolerance: ${tolerance.toFixed(4)}" (${(tolerance/2).toFixed(4)}" per hole)`;
1126
+ });
1127
+ }
1128
+
1129
+ // Public API
1130
+ return {
1131
+ init,
1132
+ getUI,
1133
+ SYMBOLS,
1134
+ QUIZZES,
1135
+ calculatePositionBonus,
1136
+ floatingFastenerTolerance,
1137
+ fixedFastenerTolerance,
1138
+ toleranceStackWorstCase,
1139
+ toleranceStackRSS,
1140
+ suggestAnnotations,
1141
+ buildFCF,
1142
+ renderQuiz
1143
+ };
1144
+ })();