cyclecad 2.0.1 → 3.0.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 (48) hide show
  1. package/DELIVERABLES.txt +296 -445
  2. package/ENHANCEMENT_COMPLETION_REPORT.md +383 -0
  3. package/ENHANCEMENT_SUMMARY.txt +308 -0
  4. package/FEATURE_INVENTORY.md +235 -0
  5. package/FUSION360_FEATURES_SUMMARY.md +452 -0
  6. package/FUSION360_PARITY_ENHANCEMENTS.md +461 -0
  7. package/FUSION360_PARITY_SUMMARY.md +520 -0
  8. package/FUSION360_QUICK_REFERENCE.md +351 -0
  9. package/IMPLEMENTATION_GUIDE.md +502 -0
  10. package/INTEGRATION-GUIDE.md +377 -0
  11. package/MODULES_PHASES_6_7.md +780 -0
  12. package/MODULE_API_REFERENCE.md +712 -0
  13. package/MODULE_INVENTORY.txt +264 -0
  14. package/app/index.html +1345 -4930
  15. package/app/js/app.js +1312 -514
  16. package/app/js/brep-kernel.js +1353 -455
  17. package/app/js/help-module.js +1437 -0
  18. package/app/js/kernel.js +364 -40
  19. package/app/js/modules/animation-module.js +1461 -0
  20. package/app/js/modules/assembly-module.js +47 -3
  21. package/app/js/modules/cam-module.js +1572 -0
  22. package/app/js/modules/collaboration-module.js +1615 -0
  23. package/app/js/modules/constraint-module.js +1266 -0
  24. package/app/js/modules/data-module.js +1054 -0
  25. package/app/js/modules/drawing-module.js +54 -8
  26. package/app/js/modules/formats-module.js +873 -0
  27. package/app/js/modules/inspection-module.js +1330 -0
  28. package/app/js/modules/mesh-module-enhanced.js +880 -0
  29. package/app/js/modules/mesh-module.js +968 -0
  30. package/app/js/modules/operations-module.js +40 -7
  31. package/app/js/modules/plugin-module.js +1554 -0
  32. package/app/js/modules/rendering-module.js +1766 -0
  33. package/app/js/modules/scripting-module.js +1073 -0
  34. package/app/js/modules/simulation-module.js +60 -3
  35. package/app/js/modules/sketch-module.js +2029 -91
  36. package/app/js/modules/step-module.js +47 -6
  37. package/app/js/modules/surface-module.js +1040 -0
  38. package/app/js/modules/version-module.js +1830 -0
  39. package/app/js/modules/viewport-module.js +95 -8
  40. package/app/test-agent-v2.html +881 -1316
  41. package/cycleCAD-Architecture-v2.pptx +0 -0
  42. package/docs/ARCHITECTURE.html +838 -1408
  43. package/docs/DEVELOPER-GUIDE.md +1504 -0
  44. package/docs/TUTORIAL.md +740 -0
  45. package/package.json +1 -1
  46. package/~$cycleCAD-Architecture-v2.pptx +0 -0
  47. package/.github/scripts/cad-diff.js +0 -590
  48. package/.github/workflows/cad-diff.yml +0 -117
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyclecad",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "Browser-based parametric 3D CAD modeler with AI-powered tools, native Inventor file parsing, and smart assembly management. No install required.",
5
5
  "main": "index.html",
6
6
  "bin": {
Binary file
@@ -1,590 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * CAD Visual Diff Generator
5
- *
6
- * Detects CAD file changes in a PR, renders before/after previews,
7
- * creates side-by-side comparisons, and posts results as PR comment.
8
- */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const { execSync } = require('child_process');
13
- const core = require('@actions/core');
14
- const github = require('@actions/github');
15
-
16
- const DIFF_DIR = '.github/diffs';
17
- const CAD_EXTENSIONS = ['.step', '.stp', '.stl', '.cyclecad', '.iam', '.ipt'];
18
-
19
- /**
20
- * Get list of changed files from PR
21
- */
22
- async function getChangedFiles() {
23
- try {
24
- const output = execSync(
25
- `git diff --name-only origin/${github.context.payload.pull_request.base.ref} HEAD`,
26
- { encoding: 'utf8' }
27
- ).trim();
28
-
29
- return output.split('\n').filter(file => {
30
- if (!file) return false;
31
- const ext = path.extname(file).toLowerCase();
32
- return CAD_EXTENSIONS.includes(ext);
33
- });
34
- } catch (error) {
35
- console.log('No changed files found or git error:', error.message);
36
- return [];
37
- }
38
- }
39
-
40
- /**
41
- * Get file status (added, modified, deleted)
42
- */
43
- function getFileStatus(file, baseBranch) {
44
- try {
45
- const baseExists = execSync(
46
- `git cat-file -e origin/${baseBranch}:${file} 2>/dev/null && echo exists || echo missing`,
47
- { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
48
- ).trim();
49
-
50
- const headExists = fs.existsSync(file);
51
-
52
- if (baseExists === 'missing' && headExists) return 'added';
53
- if (baseExists === 'exists' && !headExists) return 'deleted';
54
- return 'modified';
55
- } catch {
56
- return headExists ? 'added' : 'deleted';
57
- }
58
- }
59
-
60
- /**
61
- * Extract geometry info from CAD files
62
- */
63
- function analyzeCADFile(filePath) {
64
- if (!fs.existsSync(filePath)) {
65
- return { parts: 0, bbox: null, error: 'File not found' };
66
- }
67
-
68
- try {
69
- const ext = path.extname(filePath).toLowerCase();
70
-
71
- if (ext === '.cyclecad') {
72
- const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
73
- const parts = data.features ? data.features.length : 0;
74
- const bbox = data.bbox || null;
75
- return { parts, bbox, format: 'cycleCAD' };
76
- }
77
-
78
- if (ext === '.stl') {
79
- const buffer = fs.readFileSync(filePath);
80
- // Parse STL header for triangle count
81
- if (buffer.length >= 84) {
82
- const triangles = buffer.readUInt32LE(80);
83
- return { parts: 1, triangles, bbox: null, format: 'STL' };
84
- }
85
- }
86
-
87
- if (['.step', '.stp', '.iam', '.ipt'].includes(ext)) {
88
- // Basic file size check - larger files have more geometry
89
- const stats = fs.statSync(filePath);
90
- const sizeKB = (stats.size / 1024).toFixed(1);
91
- return { parts: 'unknown', size: sizeKB, format: ext.toUpperCase(), bbox: null };
92
- }
93
-
94
- if (ext === '.json') {
95
- const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
96
- const parts = data.parts ? data.parts.length : 0;
97
- return { parts, bbox: data.bbox || null, format: 'JSON' };
98
- }
99
-
100
- return { parts: 'unknown', format: ext.toUpperCase() };
101
- } catch (error) {
102
- console.error(`Error analyzing ${filePath}:`, error.message);
103
- return { parts: 'error', error: error.message };
104
- }
105
- }
106
-
107
- /**
108
- * Get file content from base branch
109
- */
110
- function getFileFromBase(filePath, baseBranch) {
111
- try {
112
- const content = execSync(
113
- `git show origin/${baseBranch}:${filePath}`,
114
- { encoding: 'binary', stdio: ['pipe', 'pipe', 'ignore'] }
115
- );
116
- return Buffer.from(content, 'binary');
117
- } catch {
118
- return null;
119
- }
120
- }
121
-
122
- /**
123
- * Create temporary file for base version
124
- */
125
- function createTempFile(content, filePath) {
126
- const fileName = path.basename(filePath);
127
- const tempDir = path.join(DIFF_DIR, 'temp');
128
- fs.mkdirSync(tempDir, { recursive: true });
129
- const tempPath = path.join(tempDir, `base-${Date.now()}-${fileName}`);
130
- fs.writeFileSync(tempPath, content);
131
- return tempPath;
132
- }
133
-
134
- /**
135
- * Generate HTML diff comparison
136
- */
137
- function generateDiffHTML(file, beforeInfo, afterInfo, status) {
138
- const fileName = path.basename(file);
139
- const safeName = fileName.replace(/[^a-z0-9]/gi, '-').toLowerCase();
140
-
141
- let beforeSection = '';
142
- let afterSection = '';
143
- let statusBadge = '';
144
-
145
- // Status badge
146
- switch (status) {
147
- case 'added':
148
- statusBadge = '✨ <strong>New File</strong>';
149
- break;
150
- case 'deleted':
151
- statusBadge = 'šŸ—‘ļø <strong>Deleted</strong>';
152
- break;
153
- case 'modified':
154
- statusBadge = 'šŸ“ <strong>Modified</strong>';
155
- break;
156
- }
157
-
158
- // Before section
159
- if (status === 'deleted') {
160
- beforeSection = `
161
- <div class="preview-box">
162
- <div class="file-info">
163
- <strong>${fileName}</strong>
164
- <span class="badge deleted">Deleted</span>
165
- </div>
166
- <div class="no-content">
167
- File was deleted in this PR
168
- </div>
169
- </div>
170
- `;
171
- } else if (beforeInfo.error) {
172
- beforeSection = `
173
- <div class="preview-box">
174
- <div class="file-info">
175
- <strong>${fileName}</strong>
176
- <span class="badge before">Before</span>
177
- </div>
178
- <div class="no-content">
179
- Unable to load: ${beforeInfo.error}
180
- </div>
181
- </div>
182
- `;
183
- } else {
184
- const partStr = typeof beforeInfo.parts === 'number'
185
- ? `Parts: <strong>${beforeInfo.parts}</strong>`
186
- : `Size: <strong>${beforeInfo.size} KB</strong>`;
187
-
188
- beforeSection = `
189
- <div class="preview-box">
190
- <div class="file-info">
191
- <strong>${fileName}</strong>
192
- <span class="badge before">Before</span>
193
- </div>
194
- <div class="stats">
195
- ${partStr}
196
- ${beforeInfo.format ? `<br>Format: <code>${beforeInfo.format}</code>` : ''}
197
- </div>
198
- <div class="preview-placeholder">
199
- šŸ”· ${beforeInfo.format || 'CAD'} File Preview
200
- </div>
201
- </div>
202
- `;
203
- }
204
-
205
- // After section
206
- if (status === 'added') {
207
- afterSection = `
208
- <div class="preview-box">
209
- <div class="file-info">
210
- <strong>${fileName}</strong>
211
- <span class="badge added">Added</span>
212
- </div>
213
- <div class="no-content">
214
- New file added in this PR
215
- </div>
216
- </div>
217
- `;
218
- } else if (afterInfo.error) {
219
- afterSection = `
220
- <div class="preview-box">
221
- <div class="file-info">
222
- <strong>${fileName}</strong>
223
- <span class="badge after">After</span>
224
- </div>
225
- <div class="no-content">
226
- Unable to load: ${afterInfo.error}
227
- </div>
228
- </div>
229
- `;
230
- } else {
231
- const partStr = typeof afterInfo.parts === 'number'
232
- ? `Parts: <strong>${afterInfo.parts}</strong>`
233
- : `Size: <strong>${afterInfo.size} KB</strong>`;
234
-
235
- afterSection = `
236
- <div class="preview-box">
237
- <div class="file-info">
238
- <strong>${fileName}</strong>
239
- <span class="badge after">After</span>
240
- </div>
241
- <div class="stats">
242
- ${partStr}
243
- ${afterInfo.format ? `<br>Format: <code>${afterInfo.format}</code>` : ''}
244
- </div>
245
- <div class="preview-placeholder">
246
- šŸ”· ${afterInfo.format || 'CAD'} File Preview
247
- </div>
248
- </div>
249
- `;
250
- }
251
-
252
- // Calculate changes
253
- let changesSummary = '';
254
- if (status === 'modified' && typeof beforeInfo.parts === 'number' && typeof afterInfo.parts === 'number') {
255
- const partDiff = afterInfo.parts - beforeInfo.parts;
256
- const partChange = partDiff > 0
257
- ? `<span class="badge-success">+${partDiff} parts</span>`
258
- : partDiff < 0
259
- ? `<span class="badge-danger">${partDiff} parts</span>`
260
- : `<span class="badge-neutral">${afterInfo.parts} parts</span>`;
261
-
262
- changesSummary = `
263
- <div class="changes-summary">
264
- <strong>Changes:</strong> ${changesSummary}
265
- </div>
266
- `;
267
- }
268
-
269
- const html = `
270
- <!DOCTYPE html>
271
- <html lang="en">
272
- <head>
273
- <meta charset="UTF-8">
274
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
275
- <title>CAD Diff: ${fileName}</title>
276
- <style>
277
- * { margin: 0; padding: 0; box-sizing: border-box; }
278
- body {
279
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
280
- background: #0d1117;
281
- color: #c9d1d9;
282
- padding: 20px;
283
- }
284
- .container {
285
- max-width: 1200px;
286
- margin: 0 auto;
287
- background: #161b22;
288
- border: 1px solid #30363d;
289
- border-radius: 6px;
290
- overflow: hidden;
291
- }
292
- .header {
293
- padding: 16px 20px;
294
- border-bottom: 1px solid #30363d;
295
- display: flex;
296
- align-items: center;
297
- gap: 12px;
298
- background: #0d1117;
299
- }
300
- .header-badge {
301
- display: inline-block;
302
- padding: 4px 8px;
303
- border-radius: 20px;
304
- font-size: 12px;
305
- font-weight: 600;
306
- }
307
- .header-badge.modified { background: #1f6feb; color: #79c0ff; }
308
- .header-badge.added { background: #238636; color: #3fb950; }
309
- .header-badge.deleted { background: #da3633; color: #f85149; }
310
- .content {
311
- display: grid;
312
- grid-template-columns: 1fr 1fr;
313
- gap: 1px;
314
- background: #30363d;
315
- min-height: 300px;
316
- }
317
- .preview-box {
318
- background: #161b22;
319
- padding: 20px;
320
- display: flex;
321
- flex-direction: column;
322
- gap: 12px;
323
- }
324
- .file-info {
325
- display: flex;
326
- justify-content: space-between;
327
- align-items: center;
328
- gap: 12px;
329
- padding-bottom: 8px;
330
- border-bottom: 1px solid #30363d;
331
- }
332
- .file-info strong {
333
- font-size: 13px;
334
- color: #e6edf3;
335
- word-break: break-all;
336
- }
337
- .file-info .badge {
338
- display: inline-block;
339
- padding: 3px 8px;
340
- border-radius: 12px;
341
- font-size: 11px;
342
- font-weight: 600;
343
- white-space: nowrap;
344
- }
345
- .badge.before { background: #1f6feb; color: #79c0ff; }
346
- .badge.after { background: #238636; color: #3fb950; }
347
- .badge.deleted { background: #da3633; color: #f85149; }
348
- .badge.added { background: #238636; color: #3fb950; }
349
- .stats {
350
- font-size: 12px;
351
- color: #8b949e;
352
- line-height: 1.6;
353
- }
354
- .stats code {
355
- background: #0d1117;
356
- padding: 2px 6px;
357
- border-radius: 3px;
358
- color: #79c0ff;
359
- font-family: "SFMono-Regular", Consolas, monospace;
360
- }
361
- .preview-placeholder {
362
- flex: 1;
363
- display: flex;
364
- align-items: center;
365
- justify-content: center;
366
- background: #0d1117;
367
- border: 1px dashed #30363d;
368
- border-radius: 6px;
369
- color: #6e7681;
370
- font-size: 14px;
371
- text-align: center;
372
- min-height: 200px;
373
- }
374
- .no-content {
375
- flex: 1;
376
- display: flex;
377
- align-items: center;
378
- justify-content: center;
379
- background: #0d1117;
380
- border: 1px dashed #30363d;
381
- border-radius: 6px;
382
- color: #6e7681;
383
- font-size: 13px;
384
- min-height: 200px;
385
- }
386
- .changes-summary {
387
- padding: 12px;
388
- background: #0d1117;
389
- border-radius: 6px;
390
- font-size: 12px;
391
- border-left: 3px solid #1f6feb;
392
- }
393
- .badge-success { color: #3fb950; }
394
- .badge-danger { color: #f85149; }
395
- .badge-neutral { color: #8b949e; }
396
- @media (max-width: 768px) {
397
- .content { grid-template-columns: 1fr; }
398
- .preview-box { min-height: 250px; }
399
- }
400
- </style>
401
- </head>
402
- <body>
403
- <div class="container">
404
- <div class="header">
405
- <span class="header-badge ${status}">${statusBadge}</span>
406
- <code style="color: #79c0ff;">${fileName}</code>
407
- </div>
408
- <div class="content">
409
- ${beforeSection}
410
- ${afterSection}
411
- </div>
412
- ${changesSummary}
413
- </div>
414
- </body>
415
- </html>
416
- `;
417
-
418
- return html;
419
- }
420
-
421
- /**
422
- * Create markdown comment for PR
423
- */
424
- function createMarkdownComment(changes) {
425
- if (changes.length === 0) {
426
- return '';
427
- }
428
-
429
- let markdown = '## šŸ”§ CAD Visual Diff\n\n';
430
- markdown += `Detected **${changes.length}** CAD file change${changes.length !== 1 ? 's' : ''}:\n\n`;
431
-
432
- changes.forEach((change) => {
433
- const fileName = path.basename(change.file);
434
- const icon = change.status === 'added' ? '✨' : change.status === 'deleted' ? 'šŸ—‘ļø' : 'šŸ“';
435
-
436
- markdown += `### ${icon} ${fileName}\n`;
437
-
438
- // Status line
439
- const statusLabel = change.status.charAt(0).toUpperCase() + change.status.slice(1);
440
- markdown += `**Status:** ${statusLabel}\n\n`;
441
-
442
- // Before/After info
443
- if (change.status !== 'added' && change.status !== 'deleted') {
444
- markdown += '| Property | Before | After |\n';
445
- markdown += '|----------|--------|-------|\n';
446
-
447
- const beforeParts = typeof change.beforeInfo.parts === 'number'
448
- ? change.beforeInfo.parts
449
- : change.beforeInfo.size
450
- ? `${change.beforeInfo.size} KB`
451
- : 'N/A';
452
-
453
- const afterParts = typeof change.afterInfo.parts === 'number'
454
- ? change.afterInfo.parts
455
- : change.afterInfo.size
456
- ? `${change.afterInfo.size} KB`
457
- : 'N/A';
458
-
459
- markdown += `| Parts/Size | ${beforeParts} | ${afterParts} |\n`;
460
-
461
- if (change.beforeInfo.format) {
462
- markdown += `| Format | ${change.beforeInfo.format} | ${change.afterInfo.format} |\n`;
463
- }
464
-
465
- if (typeof change.beforeInfo.parts === 'number' && typeof change.afterInfo.parts === 'number') {
466
- const diff = change.afterInfo.parts - change.beforeInfo.parts;
467
- const diffStr = diff > 0 ? `<span style="color:green">+${diff}</span>` : diff < 0 ? `<span style="color:red">${diff}</span>` : '0';
468
- markdown += `| Change | - | ${diffStr} |\n`;
469
- }
470
- } else if (change.status === 'added') {
471
- markdown += `- **Format:** ${change.afterInfo.format || 'Unknown'}\n`;
472
- if (typeof change.afterInfo.parts === 'number') {
473
- markdown += `- **Parts:** ${change.afterInfo.parts}\n`;
474
- }
475
- if (change.afterInfo.size) {
476
- markdown += `- **Size:** ${change.afterInfo.size} KB\n`;
477
- }
478
- } else if (change.status === 'deleted') {
479
- markdown += `- **Format:** ${change.beforeInfo.format || 'Unknown'}\n`;
480
- if (typeof change.beforeInfo.parts === 'number') {
481
- markdown += `- **Parts:** ${change.beforeInfo.parts}\n`;
482
- }
483
- if (change.beforeInfo.size) {
484
- markdown += `- **Size:** ${change.beforeInfo.size} KB\n`;
485
- }
486
- }
487
-
488
- markdown += '\n';
489
- });
490
-
491
- markdown += '> Generated by CAD Visual Diff GitHub Action\n';
492
-
493
- return markdown;
494
- }
495
-
496
- /**
497
- * Main execution
498
- */
499
- async function main() {
500
- try {
501
- console.log('šŸ” Scanning for CAD file changes...');
502
-
503
- // Create output directory
504
- fs.mkdirSync(DIFF_DIR, { recursive: true });
505
-
506
- // Get changed files
507
- const changedFiles = await getChangedFiles();
508
- console.log(`Found ${changedFiles.length} CAD files changed`);
509
-
510
- if (changedFiles.length === 0) {
511
- core.setOutput('comment_body', '');
512
- return;
513
- }
514
-
515
- const baseBranch = github.context.payload.pull_request.base.ref;
516
- const changes = [];
517
-
518
- // Process each changed file
519
- for (const file of changedFiles) {
520
- console.log(`\nšŸ“„ Processing: ${file}`);
521
-
522
- const status = getFileStatus(file, baseBranch);
523
- console.log(` Status: ${status}`);
524
-
525
- let beforeInfo = { parts: 'unknown' };
526
- let afterInfo = { parts: 'unknown' };
527
-
528
- // Get before version
529
- if (status !== 'added') {
530
- const baseContent = getFileFromBase(file, baseBranch);
531
- if (baseContent) {
532
- const tempPath = createTempFile(baseContent, file);
533
- beforeInfo = analyzeCADFile(tempPath);
534
- try {
535
- fs.unlinkSync(tempPath);
536
- } catch { }
537
- } else {
538
- beforeInfo = { error: 'Could not retrieve base version' };
539
- }
540
- } else {
541
- beforeInfo = { error: 'File did not exist' };
542
- }
543
-
544
- // Get after version
545
- if (status !== 'deleted') {
546
- afterInfo = analyzeCADFile(file);
547
- } else {
548
- afterInfo = { error: 'File was deleted' };
549
- }
550
-
551
- console.log(` Before: ${JSON.stringify(beforeInfo)}`);
552
- console.log(` After: ${JSON.stringify(afterInfo)}`);
553
-
554
- // Generate HTML diff
555
- const diffHTML = generateDiffHTML(file, beforeInfo, afterInfo, status);
556
- const htmlPath = path.join(DIFF_DIR, `${path.basename(file, path.extname(file))}-diff.html`);
557
- fs.writeFileSync(htmlPath, diffHTML);
558
- console.log(` Saved: ${htmlPath}`);
559
-
560
- changes.push({
561
- file,
562
- status,
563
- beforeInfo,
564
- afterInfo,
565
- htmlPath,
566
- });
567
- }
568
-
569
- // Create markdown comment
570
- const commentBody = createMarkdownComment(changes);
571
- console.log('\nāœ… Generated markdown comment');
572
-
573
- core.setOutput('comment_body', commentBody);
574
- core.setOutput('changes_count', changes.length);
575
-
576
- } catch (error) {
577
- console.error('āŒ Error:', error.message);
578
- core.setFailed(error.message);
579
- }
580
- }
581
-
582
- // Execute if run directly
583
- if (require.main === module) {
584
- main().catch(error => {
585
- console.error('Fatal error:', error);
586
- process.exit(1);
587
- });
588
- }
589
-
590
- module.exports = { getChangedFiles, analyzeCADFile, generateDiffHTML, createMarkdownComment };