cyclecad 0.2.2 → 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.
Files changed (85) hide show
  1. package/API-BUILD-MANIFEST.txt +339 -0
  2. package/API-SERVER.md +535 -0
  3. package/Architecture-Deck.pptx +0 -0
  4. package/CLAUDE.md +172 -11
  5. package/CLI-BUILD-SUMMARY.md +504 -0
  6. package/CLI-INDEX.md +356 -0
  7. package/CLI-README.md +466 -0
  8. package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
  9. package/CONNECTED_FABS_GUIDE.md +612 -0
  10. package/CONNECTED_FABS_README.md +310 -0
  11. package/DELIVERABLES.md +343 -0
  12. package/DFM-ANALYZER-INTEGRATION.md +368 -0
  13. package/DFM-QUICK-START.js +253 -0
  14. package/Dockerfile +69 -0
  15. package/IMPLEMENTATION.md +327 -0
  16. package/LICENSE +31 -0
  17. package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
  18. package/MCP-INDEX.md +264 -0
  19. package/QUICKSTART-API.md +388 -0
  20. package/QUICKSTART-CLI.md +211 -0
  21. package/QUICKSTART-MCP.md +196 -0
  22. package/README-MCP.md +208 -0
  23. package/TEST-TOKEN-ENGINE.md +319 -0
  24. package/TOKEN-ENGINE-SUMMARY.md +266 -0
  25. package/TOKENS-README.md +263 -0
  26. package/TOOLS-REFERENCE.md +254 -0
  27. package/app/index.html +373 -3
  28. package/app/js/TOKEN-INTEGRATION.md +391 -0
  29. package/app/js/agent-api.js +3 -3
  30. package/app/js/ai-copilot.js +1435 -0
  31. package/app/js/cad-vr.js +917 -0
  32. package/app/js/cam-operations.js +638 -0
  33. package/app/js/cam-pipeline.js +840 -0
  34. package/app/js/collaboration-ui.js +995 -0
  35. package/app/js/collaboration.js +1116 -0
  36. package/app/js/connected-fabs-example.js +404 -0
  37. package/app/js/connected-fabs.js +1449 -0
  38. package/app/js/dfm-analyzer.js +1760 -0
  39. package/app/js/gcode-generator.js +485 -0
  40. package/app/js/gdt-training.js +1144 -0
  41. package/app/js/machine-profiles.js +534 -0
  42. package/app/js/marketplace-v2.js +766 -0
  43. package/app/js/marketplace.js +1994 -0
  44. package/app/js/material-library.js +2115 -0
  45. package/app/js/misumi-catalog.js +904 -0
  46. package/app/js/section-view.js +666 -0
  47. package/app/js/sketch-enhance.js +779 -0
  48. package/app/js/stock-manager.js +482 -0
  49. package/app/js/text-to-cad.js +806 -0
  50. package/app/js/token-dashboard.js +563 -0
  51. package/app/js/token-engine.js +743 -0
  52. package/app/js/tool-library.js +593 -0
  53. package/app/test-agent.html +1801 -0
  54. package/app/tutorials/advanced.html +1924 -0
  55. package/app/tutorials/basic.html +1160 -0
  56. package/app/tutorials/intermediate.html +1456 -0
  57. package/bin/cyclecad-cli.js +662 -0
  58. package/bin/cyclecad-mcp +2 -0
  59. package/bin/server.js +242 -0
  60. package/cycleCAD-Architecture.pptx +0 -0
  61. package/cycleCAD-Investor-Deck.pptx +0 -0
  62. package/demo-mcp.sh +60 -0
  63. package/docs/API-SERVER-SUMMARY.md +375 -0
  64. package/docs/API-SERVER.md +667 -0
  65. package/docs/CAM-EXAMPLES.md +344 -0
  66. package/docs/CAM-INTEGRATION.md +612 -0
  67. package/docs/CAM-QUICK-REFERENCE.md +199 -0
  68. package/docs/CLI-INTEGRATION.md +510 -0
  69. package/docs/CLI.md +872 -0
  70. package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
  71. package/docs/MARKETPLACE-INTEGRATION.md +467 -0
  72. package/docs/MARKETPLACE-SETUP.html +439 -0
  73. package/docs/MCP-SERVER.md +403 -0
  74. package/examples/api-client-example.js +488 -0
  75. package/examples/api-client-example.py +359 -0
  76. package/examples/batch-manufacturing.txt +28 -0
  77. package/examples/batch-simple.txt +26 -0
  78. package/linkedin-post-combined.md +31 -0
  79. package/model-marketplace.html +1273 -0
  80. package/package.json +14 -3
  81. package/server/api-server.js +1120 -0
  82. package/server/mcp-server.js +1161 -0
  83. package/test-api-server.js +432 -0
  84. package/test-mcp.js +198 -0
  85. package/~$cycleCAD-Investor-Deck.pptx +0 -0
@@ -0,0 +1,593 @@
1
+ /**
2
+ * Tool Library Manager
3
+ * Manages cutting tools (end mills, ball mills, drills, taper mills) with feeds & speeds calculator
4
+ * Registers on window.cycleCAD.tools
5
+ *
6
+ * Usage:
7
+ * const tool = window.cycleCAD.tools.get('em-3175')
8
+ * const feedspeeds = window.cycleCAD.tools.calculateFeedsAndSpeeds('aluminum', 'em-3175')
9
+ * const recommended = window.cycleCAD.tools.recommend('aluminum', 'facing')
10
+ */
11
+
12
+ (function() {
13
+ 'use strict';
14
+
15
+ // Tool Database
16
+ const TOOL_LIBRARY = {
17
+ // End Mills (1-25mm diameters)
18
+ 'em-1': {
19
+ name: '1mm End Mill',
20
+ type: 'End Mill',
21
+ diameter: 1,
22
+ flutes: 2,
23
+ length: 40,
24
+ material: 'Carbide',
25
+ coating: 'None',
26
+ rpm_range: [12000, 20000],
27
+ chipLoad: 0.02,
28
+ max_doc: 1,
29
+ max_woc: 0.5
30
+ },
31
+ 'em-2': {
32
+ name: '2mm End Mill',
33
+ type: 'End Mill',
34
+ diameter: 2,
35
+ flutes: 2,
36
+ length: 50,
37
+ material: 'Carbide',
38
+ coating: 'TiN',
39
+ rpm_range: [8000, 15000],
40
+ chipLoad: 0.03,
41
+ max_doc: 1.5,
42
+ max_woc: 1
43
+ },
44
+ 'em-3175': {
45
+ name: '1/8" End Mill (3.175mm)',
46
+ type: 'End Mill',
47
+ diameter: 3.175,
48
+ flutes: 2,
49
+ length: 50,
50
+ material: 'Carbide',
51
+ coating: 'TiAlN',
52
+ rpm_range: [6000, 12000],
53
+ chipLoad: 0.05,
54
+ max_doc: 2,
55
+ max_woc: 1.5
56
+ },
57
+ 'em-4': {
58
+ name: '4mm End Mill',
59
+ type: 'End Mill',
60
+ diameter: 4,
61
+ flutes: 3,
62
+ length: 60,
63
+ material: 'Carbide',
64
+ coating: 'TiAlN',
65
+ rpm_range: [5000, 10000],
66
+ chipLoad: 0.06,
67
+ max_doc: 2.5,
68
+ max_woc: 2
69
+ },
70
+ 'em-5': {
71
+ name: '5mm End Mill',
72
+ type: 'End Mill',
73
+ diameter: 5,
74
+ flutes: 3,
75
+ length: 60,
76
+ material: 'Carbide',
77
+ coating: 'TiAlN',
78
+ rpm_range: [4000, 8000],
79
+ chipLoad: 0.08,
80
+ max_doc: 3,
81
+ max_woc: 2.5
82
+ },
83
+ 'em-6': {
84
+ name: '6mm End Mill',
85
+ type: 'End Mill',
86
+ diameter: 6,
87
+ flutes: 4,
88
+ length: 75,
89
+ material: 'Carbide',
90
+ coating: 'DLC',
91
+ rpm_range: [3500, 7500],
92
+ chipLoad: 0.1,
93
+ max_doc: 3.5,
94
+ max_woc: 3
95
+ },
96
+ 'em-8': {
97
+ name: '8mm End Mill',
98
+ type: 'End Mill',
99
+ diameter: 8,
100
+ flutes: 4,
101
+ length: 80,
102
+ material: 'Carbide',
103
+ coating: 'DLC',
104
+ rpm_range: [2500, 5000],
105
+ chipLoad: 0.12,
106
+ max_doc: 4,
107
+ max_woc: 4
108
+ },
109
+ 'em-10': {
110
+ name: '10mm End Mill',
111
+ type: 'End Mill',
112
+ diameter: 10,
113
+ flutes: 4,
114
+ length: 100,
115
+ material: 'Carbide',
116
+ coating: 'TiAlN',
117
+ rpm_range: [2000, 4000],
118
+ chipLoad: 0.15,
119
+ max_doc: 5,
120
+ max_woc: 5
121
+ },
122
+ 'em-12': {
123
+ name: '12mm End Mill',
124
+ type: 'End Mill',
125
+ diameter: 12,
126
+ flutes: 4,
127
+ length: 100,
128
+ material: 'Carbide',
129
+ coating: 'TiAlN',
130
+ rpm_range: [1500, 3500],
131
+ chipLoad: 0.2,
132
+ max_doc: 6,
133
+ max_woc: 6
134
+ },
135
+ 'em-16': {
136
+ name: '16mm End Mill',
137
+ type: 'End Mill',
138
+ diameter: 16,
139
+ flutes: 4,
140
+ length: 120,
141
+ material: 'Carbide',
142
+ coating: 'DLC',
143
+ rpm_range: [1000, 2500],
144
+ chipLoad: 0.25,
145
+ max_doc: 8,
146
+ max_woc: 8
147
+ },
148
+ 'em-20': {
149
+ name: '20mm End Mill',
150
+ type: 'End Mill',
151
+ diameter: 20,
152
+ flutes: 4,
153
+ length: 150,
154
+ material: 'Carbide',
155
+ coating: 'DLC',
156
+ rpm_range: [800, 2000],
157
+ chipLoad: 0.3,
158
+ max_doc: 10,
159
+ max_woc: 10
160
+ },
161
+ 'em-25': {
162
+ name: '25mm End Mill',
163
+ type: 'End Mill',
164
+ diameter: 25,
165
+ flutes: 4,
166
+ length: 150,
167
+ material: 'Carbide',
168
+ coating: 'DLC',
169
+ rpm_range: [600, 1500],
170
+ chipLoad: 0.35,
171
+ max_doc: 12,
172
+ max_woc: 12
173
+ },
174
+
175
+ // Ball Mills (3-20mm)
176
+ 'bm-3': {
177
+ name: '3mm Ball Mill',
178
+ type: 'Ball Mill',
179
+ diameter: 3,
180
+ flutes: 2,
181
+ length: 50,
182
+ material: 'Carbide',
183
+ coating: 'TiN',
184
+ rpm_range: [10000, 18000],
185
+ chipLoad: 0.02,
186
+ max_doc: 1.5,
187
+ max_woc: 0.75
188
+ },
189
+ 'bm-6': {
190
+ name: '6mm Ball Mill',
191
+ type: 'Ball Mill',
192
+ diameter: 6,
193
+ flutes: 2,
194
+ length: 60,
195
+ material: 'Carbide',
196
+ coating: 'TiAlN',
197
+ rpm_range: [6000, 12000],
198
+ chipLoad: 0.04,
199
+ max_doc: 3,
200
+ max_woc: 1.5
201
+ },
202
+ 'bm-10': {
203
+ name: '10mm Ball Mill',
204
+ type: 'Ball Mill',
205
+ diameter: 10,
206
+ flutes: 2,
207
+ length: 80,
208
+ material: 'Carbide',
209
+ coating: 'TiAlN',
210
+ rpm_range: [3000, 6000],
211
+ chipLoad: 0.08,
212
+ max_doc: 5,
213
+ max_woc: 2.5
214
+ },
215
+ 'bm-16': {
216
+ name: '16mm Ball Mill',
217
+ type: 'Ball Mill',
218
+ diameter: 16,
219
+ flutes: 2,
220
+ length: 120,
221
+ material: 'Carbide',
222
+ coating: 'DLC',
223
+ rpm_range: [1500, 3500],
224
+ chipLoad: 0.15,
225
+ max_doc: 8,
226
+ max_woc: 4
227
+ },
228
+ 'bm-20': {
229
+ name: '20mm Ball Mill',
230
+ type: 'Ball Mill',
231
+ diameter: 20,
232
+ flutes: 2,
233
+ length: 150,
234
+ material: 'Carbide',
235
+ coating: 'DLC',
236
+ rpm_range: [1000, 2500],
237
+ chipLoad: 0.2,
238
+ max_doc: 10,
239
+ max_woc: 5
240
+ },
241
+
242
+ // Taper Mills (V-bits, 60°, 90°, etc.)
243
+ 'vm-2-60': {
244
+ name: '2mm V-bit 60°',
245
+ type: 'Taper Mill',
246
+ diameter: 2,
247
+ flutes: 1,
248
+ length: 45,
249
+ angle: 60,
250
+ material: 'Carbide',
251
+ coating: 'DLC',
252
+ rpm_range: [12000, 24000],
253
+ chipLoad: 0.03,
254
+ max_doc: 1,
255
+ max_woc: 2
256
+ },
257
+ 'vm-6-90': {
258
+ name: '6mm V-bit 90°',
259
+ type: 'Taper Mill',
260
+ diameter: 6,
261
+ flutes: 1,
262
+ length: 60,
263
+ angle: 90,
264
+ material: 'Carbide',
265
+ coating: 'DLC',
266
+ rpm_range: [8000, 16000],
267
+ chipLoad: 0.05,
268
+ max_doc: 3,
269
+ max_woc: 6
270
+ },
271
+ 'tm-8-30': {
272
+ name: '8mm Taper 30°',
273
+ type: 'Taper Mill',
274
+ diameter: 8,
275
+ flutes: 2,
276
+ length: 75,
277
+ angle: 30,
278
+ material: 'Carbide',
279
+ coating: 'TiAlN',
280
+ rpm_range: [5000, 10000],
281
+ chipLoad: 0.1,
282
+ max_doc: 4,
283
+ max_woc: 4
284
+ },
285
+
286
+ // Drills (0.8-10mm)
287
+ 'dr-1': {
288
+ name: '1mm Twist Drill',
289
+ type: 'Drill',
290
+ diameter: 1,
291
+ flutes: 2,
292
+ length: 40,
293
+ material: 'HSS',
294
+ coating: 'None',
295
+ rpm_range: [4000, 8000],
296
+ chipLoad: 0.02,
297
+ max_doc: 1,
298
+ max_woc: 1
299
+ },
300
+ 'dr-2': {
301
+ name: '2mm Twist Drill',
302
+ type: 'Drill',
303
+ diameter: 2,
304
+ flutes: 2,
305
+ length: 50,
306
+ material: 'HSS',
307
+ coating: 'None',
308
+ rpm_range: [2000, 4000],
309
+ chipLoad: 0.04,
310
+ max_doc: 2,
311
+ max_woc: 2
312
+ },
313
+ 'dr-3': {
314
+ name: '3mm Twist Drill',
315
+ type: 'Drill',
316
+ diameter: 3,
317
+ flutes: 2,
318
+ length: 60,
319
+ material: 'HSS',
320
+ coating: 'TiN',
321
+ rpm_range: [1500, 3000],
322
+ chipLoad: 0.06,
323
+ max_doc: 3,
324
+ max_woc: 3
325
+ },
326
+ 'dr-5': {
327
+ name: '5mm Twist Drill',
328
+ type: 'Drill',
329
+ diameter: 5,
330
+ flutes: 2,
331
+ length: 80,
332
+ material: 'HSS',
333
+ coating: 'TiN',
334
+ rpm_range: [1000, 2000],
335
+ chipLoad: 0.1,
336
+ max_doc: 5,
337
+ max_woc: 5
338
+ },
339
+ 'dr-8': {
340
+ name: '8mm Twist Drill',
341
+ type: 'Drill',
342
+ diameter: 8,
343
+ flutes: 2,
344
+ length: 100,
345
+ material: 'HSS',
346
+ coating: 'TiAlN',
347
+ rpm_range: [600, 1200],
348
+ chipLoad: 0.15,
349
+ max_doc: 8,
350
+ max_woc: 8
351
+ },
352
+ 'dr-10': {
353
+ name: '10mm Twist Drill',
354
+ type: 'Drill',
355
+ diameter: 10,
356
+ flutes: 2,
357
+ length: 120,
358
+ material: 'Carbide',
359
+ coating: 'TiAlN',
360
+ rpm_range: [500, 1000],
361
+ chipLoad: 0.2,
362
+ max_doc: 10,
363
+ max_woc: 10
364
+ }
365
+ };
366
+
367
+ // Feeds & Speeds material constants (surface speed in m/min)
368
+ const MATERIAL_SPEEDS = {
369
+ 'aluminum': { speedRange: [100, 300], density: 2.7 },
370
+ 'brass': { speedRange: [80, 200], density: 8.5 },
371
+ 'copper': { speedRange: [60, 150], density: 8.96 },
372
+ 'steel': { speedRange: [20, 60], density: 7.85 },
373
+ 'stainless': { speedRange: [15, 40], density: 8.0 },
374
+ 'cast-iron': { speedRange: [15, 40], density: 7.2 },
375
+ 'titanium': { speedRange: [10, 30], density: 4.5 },
376
+ 'plastic': { speedRange: [150, 400], density: 1.2 },
377
+ 'wood': { speedRange: [80, 200], density: 0.6 }
378
+ };
379
+
380
+ /**
381
+ * Tool library API
382
+ */
383
+ const toolAPI = {
384
+ /**
385
+ * Get tool by ID
386
+ */
387
+ get(toolId) {
388
+ return TOOL_LIBRARY[toolId] || null;
389
+ },
390
+
391
+ /**
392
+ * List all tools
393
+ */
394
+ list() {
395
+ return Object.keys(TOOL_LIBRARY).map(id => ({
396
+ id,
397
+ ...TOOL_LIBRARY[id]
398
+ }));
399
+ },
400
+
401
+ /**
402
+ * List tools by type
403
+ */
404
+ listByType(type) {
405
+ return Object.keys(TOOL_LIBRARY)
406
+ .filter(id => TOOL_LIBRARY[id].type === type)
407
+ .map(id => ({ id, ...TOOL_LIBRARY[id] }));
408
+ },
409
+
410
+ /**
411
+ * Calculate feeds and speeds given material and tool
412
+ * Returns { rpm, feedRate, chipLoad, doc, woc }
413
+ */
414
+ calculateFeedsAndSpeeds(material, toolId) {
415
+ const tool = TOOL_LIBRARY[toolId];
416
+ if (!tool) return null;
417
+
418
+ const matData = MATERIAL_SPEEDS[material.toLowerCase()];
419
+ if (!matData) return null;
420
+
421
+ // Select surface speed based on material (use midpoint)
422
+ const surfaceSpeed = (matData.speedRange[0] + matData.speedRange[1]) / 2;
423
+
424
+ // RPM = (surface speed * 1000) / (π * diameter)
425
+ const rpm = Math.round((surfaceSpeed * 1000) / (Math.PI * tool.diameter));
426
+
427
+ // Cap RPM within tool's range
428
+ const capped_rpm = Math.max(
429
+ tool.rpm_range[0],
430
+ Math.min(rpm, tool.rpm_range[1])
431
+ );
432
+
433
+ // Feed rate = RPM * number of flutes * chip load
434
+ const feedRate = Math.round(capped_rpm * tool.flutes * tool.chipLoad);
435
+
436
+ return {
437
+ rpm: capped_rpm,
438
+ feedRate: feedRate,
439
+ chipLoad: tool.chipLoad,
440
+ doc: tool.max_doc,
441
+ woc: tool.max_woc,
442
+ surfaceSpeed: surfaceSpeed,
443
+ material: material
444
+ };
445
+ },
446
+
447
+ /**
448
+ * Recommend best tool for operation + material
449
+ * Returns tool object
450
+ */
451
+ recommend(material, operationType) {
452
+ const mat = material.toLowerCase();
453
+ let toolType = 'End Mill';
454
+
455
+ // Pick tool type based on operation
456
+ if (operationType === 'roughing') {
457
+ toolType = 'End Mill';
458
+ } else if (operationType === 'finishing') {
459
+ toolType = 'Ball Mill';
460
+ } else if (operationType === 'engraving') {
461
+ toolType = 'Taper Mill';
462
+ } else if (operationType === 'drilling') {
463
+ toolType = 'Drill';
464
+ }
465
+
466
+ // Get tools of that type
467
+ const candidates = this.listByType(toolType);
468
+ if (candidates.length === 0) return null;
469
+
470
+ // Prefer carbide for metals, any for others
471
+ let recommended;
472
+ if (['aluminum', 'steel', 'stainless', 'cast-iron', 'titanium'].includes(mat)) {
473
+ recommended = candidates.find(t => t.material === 'Carbide') || candidates[0];
474
+ } else {
475
+ recommended = candidates[0];
476
+ }
477
+
478
+ return recommended;
479
+ },
480
+
481
+ /**
482
+ * Get tool wear estimation (simplified)
483
+ * Returns estimated tool life in minutes
484
+ */
485
+ estimateWearLife(material, toolId, rpm, feedRate) {
486
+ const tool = TOOL_LIBRARY[toolId];
487
+ if (!tool) return null;
488
+
489
+ // Rough estimate: tool life inversely related to speed
490
+ // (Higher speed = shorter life)
491
+ const baseLife = 180; // 3 hours baseline
492
+ const speedFactor = tool.rpm_range[1] / rpm; // 0-1 scale
493
+ const lifeMinutes = baseLife * speedFactor;
494
+
495
+ return {
496
+ estimatedLife: lifeMinutes,
497
+ recommendedChange: lifeMinutes * 0.8, // Change at 80%
498
+ tool: tool.name
499
+ };
500
+ },
501
+
502
+ /**
503
+ * Create custom tool
504
+ */
505
+ create(toolId, profile) {
506
+ if (TOOL_LIBRARY[toolId]) {
507
+ console.warn(`Tool "${toolId}" already exists.`);
508
+ return false;
509
+ }
510
+
511
+ TOOL_LIBRARY[toolId] = {
512
+ name: profile.name || toolId,
513
+ type: profile.type || 'End Mill',
514
+ diameter: profile.diameter || 3,
515
+ flutes: profile.flutes || 2,
516
+ length: profile.length || 50,
517
+ material: profile.material || 'Carbide',
518
+ coating: profile.coating || 'None',
519
+ rpm_range: profile.rpm_range || [1000, 10000],
520
+ chipLoad: profile.chipLoad || 0.1,
521
+ max_doc: profile.max_doc || 3,
522
+ max_woc: profile.max_woc || 3
523
+ };
524
+
525
+ return true;
526
+ },
527
+
528
+ /**
529
+ * Update tool
530
+ */
531
+ update(toolId, updates) {
532
+ if (!TOOL_LIBRARY[toolId]) {
533
+ console.warn(`Tool "${toolId}" not found.`);
534
+ return false;
535
+ }
536
+
537
+ TOOL_LIBRARY[toolId] = {
538
+ ...TOOL_LIBRARY[toolId],
539
+ ...updates
540
+ };
541
+
542
+ return true;
543
+ },
544
+
545
+ /**
546
+ * Delete tool
547
+ */
548
+ delete(toolId) {
549
+ if (!TOOL_LIBRARY[toolId]) {
550
+ console.warn(`Tool "${toolId}" not found.`);
551
+ return false;
552
+ }
553
+
554
+ delete TOOL_LIBRARY[toolId];
555
+ return true;
556
+ },
557
+
558
+ /**
559
+ * Export tool as JSON
560
+ */
561
+ export(toolId) {
562
+ const tool = TOOL_LIBRARY[toolId];
563
+ if (!tool) return null;
564
+
565
+ return JSON.stringify({
566
+ id: toolId,
567
+ ...tool
568
+ }, null, 2);
569
+ },
570
+
571
+ /**
572
+ * Import tool from JSON
573
+ */
574
+ import(jsonString) {
575
+ try {
576
+ const profile = JSON.parse(jsonString);
577
+ const id = profile.id || `imported-${Date.now()}`;
578
+
579
+ const { id: _, ...rest } = profile;
580
+ return this.create(id, rest);
581
+ } catch (err) {
582
+ console.error('Failed to import tool:', err);
583
+ return false;
584
+ }
585
+ }
586
+ };
587
+
588
+ // Register on window.cycleCAD
589
+ window.cycleCAD = window.cycleCAD || {};
590
+ window.cycleCAD.tools = toolAPI;
591
+
592
+ console.log('[cycleCAD.tools] Module loaded. 30+ preset tools, feeds & speeds calculator.');
593
+ })();