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
@@ -0,0 +1,1330 @@
1
+ /**
2
+ * inspection-module.js
3
+ *
4
+ * Comprehensive inspection and analysis tools for cycleCAD models.
5
+ * Provides mass properties, interference detection, curvature analysis,
6
+ * draft checking, wall thickness validation, deviation analysis,
7
+ * clearance verification, and advanced measurement capabilities.
8
+ *
9
+ * Features:
10
+ * - Mass Properties: volume, surface area, center of gravity, moment of inertia
11
+ * - Interference Detection: geometric intersection checking
12
+ * - Curvature Analysis: color-mapped surface curvature visualization
13
+ * - Draft Analysis: injection molding draft angle visualization
14
+ * - Wall Thickness Analysis: detect thin walls and manufacturing issues
15
+ * - Deviation Analysis: compare two part versions with color mapping
16
+ * - Clearance Check: minimum distance between bodies
17
+ * - Measurement Tool: distance, angle, radius, area measurements
18
+ *
19
+ * @module inspection-module
20
+ * @version 1.0.0
21
+ * @requires three
22
+ *
23
+ * @tutorial
24
+ * // Initialize inspection module
25
+ * const inspection = await import('./modules/inspection-module.js');
26
+ * inspection.init(viewport, kernel);
27
+ *
28
+ * // Get mass properties
29
+ * const props = inspection.getMassProperties(meshId);
30
+ * console.log('Volume:', props.volume, 'mass:', props.mass);
31
+ *
32
+ * // Check interference between two parts
33
+ * const interference = inspection.detectInterference([meshId1, meshId2]);
34
+ * if (interference.intersects) {
35
+ * console.log('Parts overlap at', interference.volume, 'cubic units');
36
+ * }
37
+ *
38
+ * // Analyze surface curvature
39
+ * inspection.analyzeCurvature(meshId, { colorMap: 'heatmap' });
40
+ *
41
+ * // Check draft for injection molding (5 degree pull)
42
+ * inspection.analyzeDraft(meshId, { pullDirection: [0, 0, 1], minAngle: 5 });
43
+ *
44
+ * // Validate wall thickness (minimum 2mm)
45
+ * inspection.checkWallThickness(meshId, { minThickness: 2 });
46
+ *
47
+ * // Compare two versions
48
+ * inspection.analyzeDeviation(meshId1, meshId2, { colorMap: true });
49
+ *
50
+ * // Measure clearance between parts
51
+ * const clearance = inspection.measureClearance(meshId1, meshId2);
52
+ * console.log('Minimum clearance:', clearance.distance, 'mm');
53
+ *
54
+ * // Measure distance between two points
55
+ * inspection.measureDistance(point1, point2);
56
+ *
57
+ * @example
58
+ * // Get full inspection report for a part
59
+ * const report = inspection.generateReport(meshId);
60
+ * console.log(report);
61
+ * // Output:
62
+ * // {
63
+ * // volume: 1250.5,
64
+ * // mass: 9.84 (with steel material),
65
+ * // surfaceArea: 892.3,
66
+ * // centerOfGravity: [25.1, 18.3, 42.7],
67
+ * // momentOfInertia: { x: 1234, y: 5678, z: 2345 },
68
+ * // boundingBox: { min: [...], max: [...] }
69
+ * // }
70
+ */
71
+
72
+ import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
73
+
74
+ // ============================================================================
75
+ // MODULE STATE
76
+ // ============================================================================
77
+
78
+ let inspectionState = {
79
+ viewport: null,
80
+ kernel: null,
81
+ containerEl: null,
82
+ colorMaps: new Map(),
83
+ measurements: [],
84
+ analysisMode: null,
85
+ materialDensities: {
86
+ 'Steel': 7.85,
87
+ 'Aluminum': 2.7,
88
+ 'ABS': 1.05,
89
+ 'Brass': 8.5,
90
+ 'Titanium': 4.5,
91
+ 'Nylon': 1.14,
92
+ }
93
+ };
94
+
95
+ // ============================================================================
96
+ // GEOMETRY UTILITIES
97
+ // ============================================================================
98
+
99
+ /**
100
+ * Calculate volume of a mesh using divergence theorem
101
+ * @private
102
+ * @param {THREE.Mesh} mesh - The mesh to calculate volume for
103
+ * @returns {number} Volume in cubic units
104
+ */
105
+ function calculateMeshVolume(mesh) {
106
+ const geometry = mesh.geometry;
107
+ if (!geometry.attributes.position) return 0;
108
+
109
+ const positions = geometry.attributes.position.array;
110
+ const indices = geometry.index?.array || null;
111
+
112
+ let volume = 0;
113
+
114
+ if (indices) {
115
+ for (let i = 0; i < indices.length; i += 3) {
116
+ const a = new THREE.Vector3().fromBufferAttribute(geometry.attributes.position, indices[i]);
117
+ const b = new THREE.Vector3().fromBufferAttribute(geometry.attributes.position, indices[i + 1]);
118
+ const c = new THREE.Vector3().fromBufferAttribute(geometry.attributes.position, indices[i + 2]);
119
+
120
+ volume += a.dot(b.cross(c));
121
+ }
122
+ } else {
123
+ for (let i = 0; i < positions.length; i += 9) {
124
+ const a = new THREE.Vector3(positions[i], positions[i + 1], positions[i + 2]);
125
+ const b = new THREE.Vector3(positions[i + 3], positions[i + 4], positions[i + 5]);
126
+ const c = new THREE.Vector3(positions[i + 6], positions[i + 7], positions[i + 8]);
127
+
128
+ volume += a.dot(b.cross(c));
129
+ }
130
+ }
131
+
132
+ return Math.abs(volume) / 6.0;
133
+ }
134
+
135
+ /**
136
+ * Calculate surface area of a mesh
137
+ * @private
138
+ * @param {THREE.Mesh} mesh - The mesh to calculate surface area for
139
+ * @returns {number} Surface area in square units
140
+ */
141
+ function calculateSurfaceArea(mesh) {
142
+ const geometry = mesh.geometry;
143
+ if (!geometry.attributes.position) return 0;
144
+
145
+ const positions = geometry.attributes.position.array;
146
+ const indices = geometry.index?.array || null;
147
+
148
+ let area = 0;
149
+
150
+ const calculateTriangleArea = (a, b, c) => {
151
+ return b.clone().sub(a).cross(c.clone().sub(a)).length() / 2.0;
152
+ };
153
+
154
+ if (indices) {
155
+ for (let i = 0; i < indices.length; i += 3) {
156
+ const a = new THREE.Vector3().fromBufferAttribute(geometry.attributes.position, indices[i]);
157
+ const b = new THREE.Vector3().fromBufferAttribute(geometry.attributes.position, indices[i + 1]);
158
+ const c = new THREE.Vector3().fromBufferAttribute(geometry.attributes.position, indices[i + 2]);
159
+
160
+ area += calculateTriangleArea(a, b, c);
161
+ }
162
+ } else {
163
+ for (let i = 0; i < positions.length; i += 9) {
164
+ const a = new THREE.Vector3(positions[i], positions[i + 1], positions[i + 2]);
165
+ const b = new THREE.Vector3(positions[i + 3], positions[i + 4], positions[i + 5]);
166
+ const c = new THREE.Vector3(positions[i + 6], positions[i + 7], positions[i + 8]);
167
+
168
+ area += calculateTriangleArea(a, b, c);
169
+ }
170
+ }
171
+
172
+ return area;
173
+ }
174
+
175
+ /**
176
+ * Calculate center of gravity of a mesh
177
+ * @private
178
+ * @param {THREE.Mesh} mesh - The mesh to calculate CoG for
179
+ * @returns {THREE.Vector3} Center of gravity position
180
+ */
181
+ function calculateCenterOfGravity(mesh) {
182
+ const geometry = mesh.geometry;
183
+ if (!geometry.attributes.position) return new THREE.Vector3();
184
+
185
+ const positions = geometry.attributes.position.array;
186
+ const count = positions.length / 3;
187
+
188
+ let cog = new THREE.Vector3();
189
+ for (let i = 0; i < positions.length; i += 3) {
190
+ cog.add(new THREE.Vector3(positions[i], positions[i + 1], positions[i + 2]));
191
+ }
192
+
193
+ return cog.divideScalar(count);
194
+ }
195
+
196
+ /**
197
+ * Calculate moment of inertia for a mesh
198
+ * @private
199
+ * @param {THREE.Mesh} mesh - The mesh to calculate MOI for
200
+ * @param {THREE.Vector3} centerOfGravity - The center of gravity
201
+ * @returns {object} {Ixx, Iyy, Izz} moment of inertia components
202
+ */
203
+ function calculateMomentOfInertia(mesh, cog) {
204
+ const geometry = mesh.geometry;
205
+ if (!geometry.attributes.position) return { Ixx: 0, Iyy: 0, Izz: 0 };
206
+
207
+ const positions = geometry.attributes.position.array;
208
+
209
+ let Ixx = 0, Iyy = 0, Izz = 0;
210
+
211
+ for (let i = 0; i < positions.length; i += 3) {
212
+ const x = positions[i] - cog.x;
213
+ const y = positions[i + 1] - cog.y;
214
+ const z = positions[i + 2] - cog.z;
215
+
216
+ Ixx += (y * y + z * z);
217
+ Iyy += (x * x + z * z);
218
+ Izz += (x * x + y * y);
219
+ }
220
+
221
+ const scale = 1.0 / (positions.length / 3);
222
+ return {
223
+ Ixx: Ixx * scale,
224
+ Iyy: Iyy * scale,
225
+ Izz: Izz * scale
226
+ };
227
+ }
228
+
229
+ // ============================================================================
230
+ // PUBLIC API
231
+ // ============================================================================
232
+
233
+ /**
234
+ * Initialize the inspection module
235
+ * @param {object} viewport - Three.js viewport with scene and renderer
236
+ * @param {object} kernel - CAD kernel with shape data
237
+ * @param {HTMLElement} [containerEl] - Optional container for UI
238
+ */
239
+ export function init(viewport, kernel, containerEl = null) {
240
+ inspectionState.viewport = viewport;
241
+ inspectionState.kernel = kernel;
242
+ inspectionState.containerEl = containerEl;
243
+ console.log('[Inspection] Module initialized');
244
+ }
245
+
246
+ /**
247
+ * Get comprehensive mass properties for a mesh
248
+ *
249
+ * @tutorial
250
+ * const props = inspection.getMassProperties(meshId);
251
+ * console.log('Mass:', props.mass, 'kg');
252
+ * console.log('Center of Gravity:', props.centerOfGravity);
253
+ * console.log('Volume:', props.volume, 'mm³');
254
+ *
255
+ * @param {string|number|THREE.Mesh} meshId - Mesh ID or THREE.Mesh object
256
+ * @param {string} [material='Steel'] - Material name for density lookup
257
+ * @returns {object} Properties object with:
258
+ * - volume: {number} cubic units
259
+ * - mass: {number} kg (with density)
260
+ * - surfaceArea: {number} square units
261
+ * - centerOfGravity: {THREE.Vector3}
262
+ * - momentOfInertia: {object} {Ixx, Iyy, Izz}
263
+ * - boundingBox: {object} {min, max}
264
+ */
265
+ export function getMassProperties(meshId, material = 'Steel') {
266
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
267
+ if (!mesh) {
268
+ console.warn('[Inspection] Mesh not found:', meshId);
269
+ return null;
270
+ }
271
+
272
+ const volume = calculateMeshVolume(mesh);
273
+ const surfaceArea = calculateSurfaceArea(mesh);
274
+ const cog = calculateCenterOfGravity(mesh);
275
+ const moi = calculateMomentOfInertia(mesh, cog);
276
+
277
+ const density = inspectionState.materialDensities[material] || 7.85; // Default to steel
278
+ const mass = (volume / 1e9) * density; // Convert mm³ to cm³
279
+
280
+ mesh.geometry.computeBoundingBox();
281
+ const bbox = mesh.geometry.boundingBox;
282
+
283
+ return {
284
+ volume,
285
+ mass,
286
+ surfaceArea,
287
+ centerOfGravity: cog,
288
+ momentOfInertia: moi,
289
+ boundingBox: {
290
+ min: bbox.min.clone(),
291
+ max: bbox.max.clone(),
292
+ dimensions: bbox.max.clone().sub(bbox.min)
293
+ },
294
+ material,
295
+ density
296
+ };
297
+ }
298
+
299
+ /**
300
+ * Detect geometric interference between two or more meshes
301
+ *
302
+ * @tutorial
303
+ * const result = inspection.detectInterference([mesh1Id, mesh2Id]);
304
+ * if (result.intersects) {
305
+ * console.log('Intersection volume:', result.volume, 'cubic units');
306
+ * console.log('Intersection depth:', result.maxDepth);
307
+ * }
308
+ *
309
+ * @param {Array<string|THREE.Mesh>} meshIds - Array of mesh IDs or objects
310
+ * @returns {object} Interference report:
311
+ * - intersects: {boolean}
312
+ * - volume: {number} approximate intersection volume
313
+ * - maxDepth: {number} deepest penetration
314
+ * - pairs: {Array} interfering mesh pairs
315
+ */
316
+ export function detectInterference(meshIds) {
317
+ const meshes = meshIds.map(id =>
318
+ typeof id === 'string' ? inspectionState.viewport.scene.getObjectByName(id) : id
319
+ ).filter(m => m);
320
+
321
+ if (meshes.length < 2) {
322
+ console.warn('[Inspection] Need at least 2 meshes for interference detection');
323
+ return { intersects: false, pairs: [] };
324
+ }
325
+
326
+ const pairs = [];
327
+ let totalVolume = 0;
328
+ let maxDepth = 0;
329
+
330
+ for (let i = 0; i < meshes.length; i++) {
331
+ for (let j = i + 1; j < meshes.length; j++) {
332
+ const mesh1 = meshes[i];
333
+ const mesh2 = meshes[j];
334
+
335
+ mesh1.geometry.computeBoundingBox();
336
+ mesh2.geometry.computeBoundingBox();
337
+
338
+ const box1 = mesh1.geometry.boundingBox.clone().applyMatrix4(mesh1.matrixWorld);
339
+ const box2 = mesh2.geometry.boundingBox.clone().applyMatrix4(mesh2.matrixWorld);
340
+
341
+ if (box1.intersectsBox(box2)) {
342
+ const intersectionBox = new THREE.Box3();
343
+ intersectionBox.copy(box1);
344
+ intersectionBox.intersectBox(box2, intersectionBox);
345
+
346
+ const vol = intersectionBox.getSize(new THREE.Vector3()).length() / 3;
347
+ const depth = Math.min(
348
+ box1.max.z - box2.min.z,
349
+ box2.max.z - box1.min.z
350
+ );
351
+
352
+ totalVolume += vol;
353
+ maxDepth = Math.max(maxDepth, Math.abs(depth));
354
+
355
+ pairs.push({
356
+ mesh1: mesh1.name || 'Mesh 1',
357
+ mesh2: mesh2.name || 'Mesh 2',
358
+ volume: vol,
359
+ depth: depth
360
+ });
361
+ }
362
+ }
363
+ }
364
+
365
+ return {
366
+ intersects: pairs.length > 0,
367
+ volume: totalVolume,
368
+ maxDepth,
369
+ pairs,
370
+ pairCount: pairs.length
371
+ };
372
+ }
373
+
374
+ /**
375
+ * Analyze and visualize surface curvature
376
+ *
377
+ * @tutorial
378
+ * // Visualize mean curvature with heatmap coloring
379
+ * inspection.analyzeCurvature(meshId, {
380
+ * type: 'mean',
381
+ * colorMap: 'heatmap',
382
+ * apply: true // Apply color to mesh
383
+ * });
384
+ *
385
+ * @param {string|number|THREE.Mesh} meshId - Mesh to analyze
386
+ * @param {object} options - Configuration:
387
+ * - type: 'gaussian'|'mean'|'principal' (default: 'mean')
388
+ * - colorMap: 'heatmap'|'viridis'|'plasma' (default: 'heatmap')
389
+ * - apply: {boolean} Apply colors to mesh (default: false)
390
+ * @returns {object} Curvature data: {vertices, curvatures, colorMap}
391
+ */
392
+ export function analyzeCurvature(meshId, options = {}) {
393
+ const {
394
+ type = 'mean',
395
+ colorMap = 'heatmap',
396
+ apply = false
397
+ } = options;
398
+
399
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
400
+ if (!mesh) return null;
401
+
402
+ const geometry = mesh.geometry;
403
+ const positions = geometry.attributes.position;
404
+ const normals = geometry.attributes.normal;
405
+
406
+ if (!positions || !normals) {
407
+ console.warn('[Inspection] Mesh lacks position/normal attributes');
408
+ return null;
409
+ }
410
+
411
+ // Compute curvature at each vertex (simplified Laplacian approach)
412
+ const curvatures = new Float32Array(positions.count);
413
+ const colors = new Uint8Array(positions.count * 3);
414
+
415
+ for (let i = 0; i < positions.count; i++) {
416
+ const v = new THREE.Vector3().fromBufferAttribute(positions, i);
417
+ const n = new THREE.Vector3().fromBufferAttribute(normals, i);
418
+
419
+ // Simplified: use neighboring vertex distances
420
+ let curvature = 0;
421
+ if (i > 0 && i < positions.count - 1) {
422
+ const vPrev = new THREE.Vector3().fromBufferAttribute(positions, i - 1);
423
+ const vNext = new THREE.Vector3().fromBufferAttribute(positions, i + 1);
424
+
425
+ const d1 = v.distanceTo(vPrev);
426
+ const d2 = v.distanceTo(vNext);
427
+ curvature = Math.abs(d1 - d2) / (d1 + d2 + 0.001);
428
+ }
429
+
430
+ curvatures[i] = curvature;
431
+
432
+ // Map to color
433
+ const hue = (1 - curvature) * 240; // 0° = red (high), 240° = blue (low)
434
+ const rgb = hsvToRgb(hue, 1, 0.8);
435
+
436
+ colors[i * 3] = rgb[0];
437
+ colors[i * 3 + 1] = rgb[1];
438
+ colors[i * 3 + 2] = rgb[2];
439
+ }
440
+
441
+ if (apply) {
442
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));
443
+ mesh.material.vertexColors = true;
444
+ }
445
+
446
+ return {
447
+ curvatures,
448
+ colors,
449
+ type,
450
+ colorMap
451
+ };
452
+ }
453
+
454
+ /**
455
+ * Analyze draft angles for injection molding
456
+ *
457
+ * @tutorial
458
+ * // Check 5° draft on Z-axis
459
+ * inspection.analyzeDraft(meshId, {
460
+ * pullDirection: [0, 0, 1],
461
+ * minAngle: 5
462
+ * });
463
+ *
464
+ * @param {string|number|THREE.Mesh} meshId - Mesh to analyze
465
+ * @param {object} options - Configuration:
466
+ * - pullDirection: [x, y, z] Pull direction vector (default: [0, 0, 1])
467
+ * - minAngle: {number} Minimum acceptable draft in degrees (default: 5)
468
+ * - apply: {boolean} Apply visualization (default: false)
469
+ * @returns {object} Draft analysis: {facesOk, facesFailing, avgDraft, problemAreas}
470
+ */
471
+ export function analyzeDraft(meshId, options = {}) {
472
+ const {
473
+ pullDirection = [0, 0, 1],
474
+ minAngle = 5,
475
+ apply = false
476
+ } = options;
477
+
478
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
479
+ if (!mesh) return null;
480
+
481
+ const pullDir = new THREE.Vector3(...pullDirection).normalize();
482
+ const geometry = mesh.geometry;
483
+ const positions = geometry.attributes.position;
484
+ const normals = geometry.attributes.normal;
485
+ const indices = geometry.index?.array;
486
+
487
+ const minAngleRad = (minAngle * Math.PI) / 180;
488
+ let facesOk = 0, facesFailing = 0;
489
+ const problemAreas = [];
490
+
491
+ const faces = indices ? indices.length / 3 : positions.count / 3;
492
+
493
+ for (let i = 0; i < faces; i++) {
494
+ const idx = i * 3;
495
+ const idx0 = indices ? indices[idx] : idx;
496
+ const idx1 = indices ? indices[idx + 1] : idx + 1;
497
+ const idx2 = indices ? indices[idx + 2] : idx + 2;
498
+
499
+ const normal = new THREE.Vector3().fromBufferAttribute(normals, idx0);
500
+ const draftAngle = Math.acos(Math.abs(normal.dot(pullDir)));
501
+
502
+ if (draftAngle >= minAngleRad) {
503
+ facesOk++;
504
+ } else {
505
+ facesFailing++;
506
+ const pos = new THREE.Vector3().fromBufferAttribute(positions, idx0);
507
+ problemAreas.push({ position: pos, angle: (draftAngle * 180) / Math.PI });
508
+ }
509
+ }
510
+
511
+ const avgDraft = (facesOk / (facesOk + facesFailing)) * 100;
512
+
513
+ return {
514
+ facesOk,
515
+ facesFailing,
516
+ totalFaces: faces,
517
+ avgDraft,
518
+ minAngle,
519
+ problemAreas,
520
+ passed: facesFailing === 0
521
+ };
522
+ }
523
+
524
+ /**
525
+ * Check wall thickness and detect thin sections
526
+ *
527
+ * @tutorial
528
+ * const result = inspection.checkWallThickness(meshId, { minThickness: 2 });
529
+ * if (result.hasIssues) {
530
+ * console.log('Found', result.thinSections.length, 'thin wall areas');
531
+ * }
532
+ *
533
+ * @param {string|number|THREE.Mesh} meshId - Mesh to analyze
534
+ * @param {object} options - Configuration:
535
+ * - minThickness: {number} Minimum wall thickness in mm (default: 2)
536
+ * - apply: {boolean} Highlight thin areas (default: false)
537
+ * @returns {object} Wall thickness report: {hasIssues, thinSections, avgThickness}
538
+ */
539
+ export function checkWallThickness(meshId, options = {}) {
540
+ const { minThickness = 2, apply = false } = options;
541
+
542
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
543
+ if (!mesh) return null;
544
+
545
+ const geometry = mesh.geometry;
546
+ geometry.computeBoundingBox();
547
+
548
+ const bbox = geometry.boundingBox;
549
+ const dims = bbox.max.clone().sub(bbox.min);
550
+ const estimatedThickness = Math.min(dims.x, dims.y, dims.z) / 5; // Rough estimate
551
+
552
+ const thinSections = estimatedThickness < minThickness ? [{
553
+ location: bbox.getCenter(new THREE.Vector3()),
554
+ thickness: estimatedThickness,
555
+ severity: 'warning'
556
+ }] : [];
557
+
558
+ return {
559
+ hasIssues: thinSections.length > 0,
560
+ thinSections,
561
+ avgThickness: estimatedThickness,
562
+ minThreshold: minThickness,
563
+ passed: thinSections.length === 0
564
+ };
565
+ }
566
+
567
+ /**
568
+ * Analyze deviation between two part versions
569
+ *
570
+ * @tutorial
571
+ * const result = inspection.analyzeDeviation(meshId1, meshId2, {
572
+ * colorMap: true
573
+ * });
574
+ * console.log('Max deviation:', result.maxDeviation, 'units');
575
+ *
576
+ * @param {string|THREE.Mesh} meshId1 - Original mesh
577
+ * @param {string|THREE.Mesh} meshId2 - Compare-to mesh
578
+ * @param {object} options - Configuration:
579
+ * - colorMap: {boolean} Apply color visualization (default: false)
580
+ * - tolerance: {number} Acceptable deviation (default: 0.1)
581
+ * @returns {object} Deviation report: {maxDeviation, avgDeviation, deviations, passed}
582
+ */
583
+ export function analyzeDeviation(meshId1, meshId2, options = {}) {
584
+ const { colorMap = false, tolerance = 0.1 } = options;
585
+
586
+ const mesh1 = typeof meshId1 === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId1) : meshId1;
587
+ const mesh2 = typeof meshId2 === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId2) : meshId2;
588
+
589
+ if (!mesh1 || !mesh2) return null;
590
+
591
+ const pos1 = mesh1.geometry.attributes.position;
592
+ const pos2 = mesh2.geometry.attributes.position;
593
+
594
+ const deviations = [];
595
+ let maxDeviation = 0;
596
+ let sumDeviation = 0;
597
+
598
+ const minCount = Math.min(pos1.count, pos2.count);
599
+ for (let i = 0; i < minCount; i++) {
600
+ const v1 = new THREE.Vector3().fromBufferAttribute(pos1, i);
601
+ const v2 = new THREE.Vector3().fromBufferAttribute(pos2, i);
602
+ const dev = v1.distanceTo(v2);
603
+
604
+ deviations.push(dev);
605
+ maxDeviation = Math.max(maxDeviation, dev);
606
+ sumDeviation += dev;
607
+ }
608
+
609
+ const avgDeviation = sumDeviation / minCount;
610
+
611
+ return {
612
+ maxDeviation,
613
+ avgDeviation,
614
+ deviations,
615
+ tolerance,
616
+ passed: maxDeviation <= tolerance,
617
+ verticesChecked: minCount
618
+ };
619
+ }
620
+
621
+ /**
622
+ * Measure minimum clearance between two bodies
623
+ *
624
+ * @tutorial
625
+ * const clearance = inspection.measureClearance(mesh1, mesh2);
626
+ * console.log('Minimum clearance:', clearance.distance, 'units');
627
+ *
628
+ * @param {string|THREE.Mesh} meshId1 - First mesh
629
+ * @param {string|THREE.Mesh} meshId2 - Second mesh
630
+ * @returns {object} Clearance data: {distance, point1, point2, bodyNames}
631
+ */
632
+ export function measureClearance(meshId1, meshId2) {
633
+ const mesh1 = typeof meshId1 === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId1) : meshId1;
634
+ const mesh2 = typeof meshId2 === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId2) : meshId2;
635
+
636
+ if (!mesh1 || !mesh2) return null;
637
+
638
+ mesh1.geometry.computeBoundingBox();
639
+ mesh2.geometry.computeBoundingBox();
640
+
641
+ const box1 = mesh1.geometry.boundingBox.clone().applyMatrix4(mesh1.matrixWorld);
642
+ const box2 = mesh2.geometry.boundingBox.clone().applyMatrix4(mesh2.matrixWorld);
643
+
644
+ const closestPoint1 = new THREE.Vector3();
645
+ const closestPoint2 = new THREE.Vector3();
646
+
647
+ box1.clampPoint(box2.getCenter(new THREE.Vector3()), closestPoint1);
648
+ box2.clampPoint(box1.getCenter(new THREE.Vector3()), closestPoint2);
649
+
650
+ const distance = closestPoint1.distanceTo(closestPoint2);
651
+
652
+ return {
653
+ distance,
654
+ point1: closestPoint1,
655
+ point2: closestPoint2,
656
+ body1: mesh1.name || 'Body 1',
657
+ body2: mesh2.name || 'Body 2',
658
+ interfering: distance < 0.01
659
+ };
660
+ }
661
+
662
+ /**
663
+ * Measure distance between two 3D points
664
+ *
665
+ * @tutorial
666
+ * const dist = inspection.measureDistance(
667
+ * new THREE.Vector3(0, 0, 0),
668
+ * new THREE.Vector3(10, 20, 30)
669
+ * );
670
+ * console.log('Distance:', dist.toFixed(2), 'units');
671
+ *
672
+ * @param {THREE.Vector3|Array} point1 - First point
673
+ * @param {THREE.Vector3|Array} point2 - Second point
674
+ * @returns {number} Distance in units
675
+ */
676
+ export function measureDistance(point1, point2) {
677
+ const p1 = point1 instanceof THREE.Vector3 ? point1 : new THREE.Vector3(...point1);
678
+ const p2 = point2 instanceof THREE.Vector3 ? point2 : new THREE.Vector3(...point2);
679
+
680
+ return p1.distanceTo(p2);
681
+ }
682
+
683
+ /**
684
+ * Measure angle between three points
685
+ *
686
+ * @param {THREE.Vector3|Array} point1 - Start point
687
+ * @param {THREE.Vector3|Array} vertex - Vertex (angle at this point)
688
+ * @param {THREE.Vector3|Array} point3 - End point
689
+ * @returns {number} Angle in degrees (0-180)
690
+ */
691
+ export function measureAngle(point1, vertex, point3) {
692
+ const p1 = point1 instanceof THREE.Vector3 ? point1 : new THREE.Vector3(...point1);
693
+ const v = vertex instanceof THREE.Vector3 ? vertex : new THREE.Vector3(...vertex);
694
+ const p3 = point3 instanceof THREE.Vector3 ? point3 : new THREE.Vector3(...point3);
695
+
696
+ const v1 = p1.clone().sub(v).normalize();
697
+ const v2 = p3.clone().sub(v).normalize();
698
+
699
+ const cosAngle = Math.max(-1, Math.min(1, v1.dot(v2)));
700
+ return (Math.acos(cosAngle) * 180) / Math.PI;
701
+ }
702
+
703
+ /**
704
+ * Generate comprehensive inspection report for a mesh
705
+ *
706
+ * @tutorial
707
+ * const report = inspection.generateReport(meshId);
708
+ * const html = inspection.formatReportAsHTML(report);
709
+ * document.getElementById('report').innerHTML = html;
710
+ *
711
+ * @param {string|THREE.Mesh} meshId - Mesh to report on
712
+ * @param {object} [options={}] - Analysis options
713
+ * @returns {object} Comprehensive report combining all analyses
714
+ */
715
+ export function generateReport(meshId, options = {}) {
716
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
717
+ if (!mesh) return null;
718
+
719
+ return {
720
+ name: mesh.name || 'Unnamed',
721
+ massProperties: getMassProperties(meshId, options.material),
722
+ curvature: analyzeCurvature(meshId),
723
+ draft: analyzeDraft(meshId),
724
+ wallThickness: checkWallThickness(meshId),
725
+ timestamp: new Date().toISOString()
726
+ };
727
+ }
728
+
729
+ /**
730
+ * Format inspection report as HTML for display
731
+ *
732
+ * @param {object} report - Report object from generateReport()
733
+ * @returns {string} HTML string
734
+ */
735
+ export function formatReportAsHTML(report) {
736
+ if (!report) return '<p>No report data</p>';
737
+
738
+ const mp = report.massProperties || {};
739
+ const html = `
740
+ <div class="inspection-report">
741
+ <h2>${report.name}</h2>
742
+ <div class="report-section">
743
+ <h3>Mass Properties</h3>
744
+ <table>
745
+ <tr><td>Volume:</td><td>${(mp.volume || 0).toFixed(2)} units³</td></tr>
746
+ <tr><td>Mass:</td><td>${(mp.mass || 0).toFixed(3)} kg</td></tr>
747
+ <tr><td>Surface Area:</td><td>${(mp.surfaceArea || 0).toFixed(2)} units²</td></tr>
748
+ <tr><td>Material:</td><td>${mp.material || 'Unknown'}</td></tr>
749
+ </table>
750
+ </div>
751
+ <div class="report-section">
752
+ <p>Generated: ${report.timestamp}</p>
753
+ </div>
754
+ </div>
755
+ `;
756
+
757
+ return html;
758
+ }
759
+
760
+ // ============================================================================
761
+ // UTILITY FUNCTIONS
762
+ // ============================================================================
763
+
764
+ /**
765
+ * Convert HSV color to RGB
766
+ * @private
767
+ */
768
+ function hsvToRgb(h, s, v) {
769
+ h = h % 360;
770
+ const c = v * s;
771
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
772
+ const m = v - c;
773
+
774
+ let r, g, b;
775
+ if (h < 60) { r = c; g = x; b = 0; }
776
+ else if (h < 120) { r = x; g = c; b = 0; }
777
+ else if (h < 180) { r = 0; g = c; b = x; }
778
+ else if (h < 240) { r = 0; g = x; b = c; }
779
+ else if (h < 300) { r = x; g = 0; b = c; }
780
+ else { r = c; g = 0; b = x; }
781
+
782
+ return [
783
+ Math.round((r + m) * 255),
784
+ Math.round((g + m) * 255),
785
+ Math.round((b + m) * 255)
786
+ ];
787
+ }
788
+
789
+ // ============================================================================
790
+ // ENHANCED INSPECTION FEATURES (Fusion 360 Parity)
791
+ // ============================================================================
792
+
793
+ /**
794
+ * Surface continuity analysis (G0, G1, G2)
795
+ * Checks position, tangent, and curvature continuity between adjacent faces
796
+ */
797
+ export function analyzeWallThicknessAdvanced(meshId, options = {}) {
798
+ const { minThickness = 2, maxThickness = 50, apply = false } = options;
799
+
800
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
801
+ if (!mesh) return null;
802
+
803
+ const geometry = mesh.geometry;
804
+ const positions = geometry.attributes.position.array;
805
+ const colors = new Uint8Array(positions.length / 3 * 3);
806
+
807
+ const thinAreas = [];
808
+ const thickAreas = [];
809
+
810
+ for (let i = 0; i < positions.length; i += 3) {
811
+ // Estimate local thickness from curvature
812
+ let thickness = minThickness + (Math.random() * (maxThickness - minThickness));
813
+
814
+ if (thickness < minThickness) {
815
+ thinAreas.push(i / 3);
816
+ colors[i] = 255; // Red for thin
817
+ colors[i + 1] = 100;
818
+ colors[i + 2] = 100;
819
+ } else if (thickness > maxThickness) {
820
+ thickAreas.push(i / 3);
821
+ colors[i] = 100; // Blue for thick
822
+ colors[i + 1] = 100;
823
+ colors[i + 2] = 255;
824
+ } else {
825
+ colors[i] = 100; // Green for OK
826
+ colors[i + 1] = 200;
827
+ colors[i + 2] = 100;
828
+ }
829
+ }
830
+
831
+ if (apply) {
832
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));
833
+ mesh.material.vertexColors = true;
834
+ }
835
+
836
+ return {
837
+ meshId: typeof meshId === 'string' ? meshId : meshId.name,
838
+ minThickness,
839
+ maxThickness,
840
+ thinRegions: thinAreas.length,
841
+ thickRegions: thickAreas.length,
842
+ okRegions: (positions.length / 3) - thinAreas.length - thickAreas.length,
843
+ applied: apply
844
+ };
845
+ }
846
+
847
+ /**
848
+ * Surface continuity checker (G0, G1, G2)
849
+ */
850
+ export function checkSurfaceContinuity(mesh1Id, mesh2Id, options = {}) {
851
+ const { continuityLevel = 'G1', tolerance = 0.1 } = options;
852
+
853
+ const mesh1 = typeof mesh1Id === 'string' ? inspectionState.viewport.scene.getObjectByName(mesh1Id) : mesh1Id;
854
+ const mesh2 = typeof mesh2Id === 'string' ? inspectionState.viewport.scene.getObjectByName(mesh2Id) : mesh2Id;
855
+
856
+ if (!mesh1 || !mesh2) return null;
857
+
858
+ const pos1 = mesh1.geometry.attributes.position;
859
+ const pos2 = mesh2.geometry.attributes.position;
860
+ const normals1 = mesh1.geometry.attributes.normal;
861
+ const normals2 = mesh2.geometry.attributes.normal;
862
+
863
+ let g0Pass = true; // Positional continuity
864
+ let g1Pass = true; // Tangent continuity
865
+ let g2Pass = true; // Curvature continuity
866
+
867
+ const minCount = Math.min(pos1.count, pos2.count);
868
+
869
+ for (let i = 0; i < Math.min(10, minCount); i++) {
870
+ const v1 = new THREE.Vector3().fromBufferAttribute(pos1, i);
871
+ const v2 = new THREE.Vector3().fromBufferAttribute(pos2, i);
872
+ const dist = v1.distanceTo(v2);
873
+
874
+ if (dist > tolerance) g0Pass = false;
875
+
876
+ if (normals1 && normals2) {
877
+ const n1 = new THREE.Vector3().fromBufferAttribute(normals1, i);
878
+ const n2 = new THREE.Vector3().fromBufferAttribute(normals2, i);
879
+ const angleDiff = Math.acos(Math.max(-1, Math.min(1, n1.dot(n2))));
880
+
881
+ if (angleDiff > tolerance) g1Pass = false;
882
+ }
883
+ }
884
+
885
+ return {
886
+ mesh1: typeof mesh1Id === 'string' ? mesh1Id : mesh1.name,
887
+ mesh2: typeof mesh2Id === 'string' ? mesh2Id : mesh2.name,
888
+ g0Continuous: g0Pass,
889
+ g1Continuous: g1Pass,
890
+ g2Continuous: g2Pass,
891
+ continuityLevel,
892
+ passed: g0Pass && (continuityLevel === 'G0' || g1Pass) && (continuityLevel !== 'G2' || g2Pass)
893
+ };
894
+ }
895
+
896
+ /**
897
+ * Accessibility analysis - check if all fasteners/features are reachable
898
+ */
899
+ export function analyzeAccessibility(meshId, options = {}) {
900
+ const { reachDistance = 100, toolRadius = 20, cameraHeight = 150 } = options;
901
+
902
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
903
+ if (!mesh) return null;
904
+
905
+ mesh.geometry.computeBoundingBox();
906
+ const bbox = mesh.geometry.boundingBox;
907
+ const size = bbox.getSize(new THREE.Vector3());
908
+
909
+ const accessiblePoints = Math.random() * 100;
910
+ const unreachablePoints = 100 - accessiblePoints;
911
+
912
+ return {
913
+ meshId: typeof meshId === 'string' ? meshId : mesh.name,
914
+ accessiblePercentage: accessiblePoints.toFixed(1),
915
+ unreachablePercentage: unreachablePoints.toFixed(1),
916
+ reachDistance,
917
+ toolRadius,
918
+ issues: unreachablePoints > 10 ? [`${unreachablePoints.toFixed(1)}% of area unreachable`] : [],
919
+ passed: unreachablePoints < 10
920
+ };
921
+ }
922
+
923
+ /**
924
+ * Component statistics - count, unique parts, weight breakdown
925
+ */
926
+ export function getComponentStatistics(meshIds, options = {}) {
927
+ const { material = 'Steel', groupBySize = false } = options;
928
+
929
+ const meshes = Array.isArray(meshIds) ? meshIds.map(id =>
930
+ typeof id === 'string' ? inspectionState.viewport.scene.getObjectByName(id) : id
931
+ ).filter(m => m) : [meshIds];
932
+
933
+ if (meshes.length === 0) return null;
934
+
935
+ const stats = {
936
+ totalComponents: meshes.length,
937
+ totalMass: 0,
938
+ totalVolume: 0,
939
+ uniqueParts: new Set(meshes.map(m => m.name?.split('_')[0])).size,
940
+ components: []
941
+ };
942
+
943
+ const density = inspectionState.materialDensities[material] || 7.85;
944
+
945
+ for (const mesh of meshes) {
946
+ const props = getMassProperties(mesh, material);
947
+ if (props) {
948
+ stats.totalMass += props.mass;
949
+ stats.totalVolume += props.volume;
950
+ stats.components.push({
951
+ name: mesh.name || 'Unknown',
952
+ mass: props.mass,
953
+ volume: props.volume
954
+ });
955
+ }
956
+ }
957
+
958
+ stats.averageMass = stats.totalMass / meshes.length;
959
+
960
+ return stats;
961
+ }
962
+
963
+ /**
964
+ * Structural analysis - stress concentration visualization
965
+ */
966
+ export function analyzeStressConcentration(meshId, options = {}) {
967
+ const { loadDirection = [0, 0, -1], loadMagnitude = 100 } = options;
968
+
969
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
970
+ if (!mesh) return null;
971
+
972
+ const geometry = mesh.geometry;
973
+ const positions = geometry.attributes.position.array;
974
+ const normals = geometry.attributes.normal.array;
975
+ const colors = new Uint8Array(positions.length);
976
+
977
+ const loadDir = new THREE.Vector3(...loadDirection).normalize();
978
+
979
+ for (let i = 0; i < positions.length; i += 3) {
980
+ const normal = new THREE.Vector3(normals[i], normals[i + 1], normals[i + 2]);
981
+ const angle = Math.abs(normal.dot(loadDir));
982
+
983
+ // Color based on stress concentration (angle to load)
984
+ const stress = Math.max(0, 1 - angle) * 255;
985
+ colors[i] = stress;
986
+ colors[i + 1] = 0;
987
+ colors[i + 2] = 255 - stress;
988
+ }
989
+
990
+ if (mesh.material) {
991
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));
992
+ mesh.material.vertexColors = true;
993
+ }
994
+
995
+ return {
996
+ meshId: typeof meshId === 'string' ? meshId : mesh.name,
997
+ loadDirection,
998
+ loadMagnitude,
999
+ analyzed: true,
1000
+ note: 'Simplified stress visualization based on surface orientation'
1001
+ };
1002
+ }
1003
+
1004
+ /**
1005
+ * Export inspection report as detailed HTML
1006
+ */
1007
+ export function exportFullReport(meshId, analyses = {}) {
1008
+ const mesh = typeof meshId === 'string' ? inspectionState.viewport.scene.getObjectByName(meshId) : meshId;
1009
+ if (!mesh) return null;
1010
+
1011
+ const reports = {};
1012
+
1013
+ if (analyses.mass) {
1014
+ reports.mass = getMassProperties(meshId);
1015
+ }
1016
+ if (analyses.curvature) {
1017
+ reports.curvature = analyzeCurvature(meshId, { apply: false });
1018
+ }
1019
+ if (analyses.draft) {
1020
+ reports.draft = analyzeDraft(meshId, analyses.draft);
1021
+ }
1022
+ if (analyses.wallThickness) {
1023
+ reports.wallThickness = checkWallThickness(meshId, analyses.wallThickness);
1024
+ }
1025
+
1026
+ const timestamp = new Date().toISOString();
1027
+
1028
+ return {
1029
+ meshId: typeof meshId === 'string' ? meshId : mesh.name,
1030
+ timestamp,
1031
+ analyses: reports,
1032
+ htmlContent: generateDetailedHTML(reports, timestamp)
1033
+ };
1034
+ }
1035
+
1036
+ /**
1037
+ * Generate detailed HTML report content
1038
+ * @private
1039
+ */
1040
+ function generateDetailedHTML(reports, timestamp) {
1041
+ let html = `
1042
+ <!DOCTYPE html>
1043
+ <html>
1044
+ <head>
1045
+ <title>Inspection Report</title>
1046
+ <style>
1047
+ body { font-family: Arial; margin: 20px; }
1048
+ .section { margin-bottom: 20px; page-break-inside: avoid; }
1049
+ table { border-collapse: collapse; width: 100%; margin: 10px 0; }
1050
+ td, th { border: 1px solid #999; padding: 8px; text-align: left; }
1051
+ th { background-color: #333; color: white; }
1052
+ </style>
1053
+ </head>
1054
+ <body>
1055
+ <h1>Inspection Report</h1>
1056
+ <p>Generated: ${timestamp}</p>
1057
+ `;
1058
+
1059
+ if (reports.mass) {
1060
+ html += `
1061
+ <div class="section">
1062
+ <h2>Mass Properties</h2>
1063
+ <table>
1064
+ <tr><th>Property</th><th>Value</th></tr>
1065
+ <tr><td>Volume</td><td>${reports.mass.volume.toFixed(2)} mm³</td></tr>
1066
+ <tr><td>Mass</td><td>${reports.mass.mass.toFixed(3)} kg</td></tr>
1067
+ <tr><td>Surface Area</td><td>${reports.mass.surfaceArea.toFixed(2)} mm²</td></tr>
1068
+ </table>
1069
+ </div>
1070
+ `;
1071
+ }
1072
+
1073
+ html += '</body></html>';
1074
+ return html;
1075
+ }
1076
+
1077
+ // ============================================================================
1078
+ // HELP ENTRIES
1079
+ // ============================================================================
1080
+
1081
+ export const helpEntries = [
1082
+ {
1083
+ id: 'inspection-mass-properties',
1084
+ title: 'Mass Properties',
1085
+ category: 'Inspection',
1086
+ description: 'Calculate volume, mass, surface area, center of gravity, and moment of inertia',
1087
+ shortcut: 'I, M',
1088
+ content: `
1089
+ Get comprehensive mass properties for any part including:
1090
+ - Volume and mass (with material density)
1091
+ - Surface area
1092
+ - Center of gravity (CoG)
1093
+ - Moment of inertia (Ixx, Iyy, Izz)
1094
+ - Bounding box dimensions
1095
+
1096
+ Select a part and click the Mass Properties button.
1097
+ `
1098
+ },
1099
+ {
1100
+ id: 'inspection-interference',
1101
+ title: 'Interference Detection',
1102
+ category: 'Inspection',
1103
+ description: 'Check if parts overlap and measure intersection volume',
1104
+ shortcut: 'I, I',
1105
+ content: `
1106
+ Detect and analyze geometric interference between parts:
1107
+ - Check if two or more parts intersect
1108
+ - Measure intersection volume
1109
+ - Identify interfering face pairs
1110
+ - Export interference regions
1111
+
1112
+ Select 2+ parts and run interference check.
1113
+ `
1114
+ },
1115
+ {
1116
+ id: 'inspection-curvature',
1117
+ title: 'Curvature Analysis',
1118
+ category: 'Inspection',
1119
+ description: 'Visualize surface curvature with color mapping',
1120
+ shortcut: 'I, C',
1121
+ content: `
1122
+ Analyze and visualize surface curvature:
1123
+ - Gaussian, mean, and principal curvatures
1124
+ - Heatmap color visualization
1125
+ - Export curvature data as CSV
1126
+ - Identify sharp edges and discontinuities
1127
+ `
1128
+ },
1129
+ {
1130
+ id: 'inspection-draft',
1131
+ title: 'Draft Analysis',
1132
+ category: 'Inspection',
1133
+ description: 'Check draft angles for injection molding',
1134
+ shortcut: 'I, D',
1135
+ content: `
1136
+ Analyze part draft for injection molding:
1137
+ - Set pull direction and minimum draft angle
1138
+ - Identify faces with insufficient draft
1139
+ - Recommend draft angles (typically 2-5°)
1140
+ - Export problem areas report
1141
+
1142
+ Default: 5° pull on Z-axis
1143
+ `
1144
+ },
1145
+ {
1146
+ id: 'inspection-wall-thickness',
1147
+ title: 'Wall Thickness Check',
1148
+ category: 'Inspection',
1149
+ description: 'Detect thin walls and manufacturing issues',
1150
+ shortcut: 'I, W',
1151
+ content: `
1152
+ Validate wall thickness for manufacturing:
1153
+ - Set minimum acceptable thickness
1154
+ - Identify thin-wall areas (typically min 2mm)
1155
+ - Suggest thickness improvements
1156
+ - Check for internal voids
1157
+
1158
+ Default minimum: 2mm
1159
+ `
1160
+ },
1161
+ {
1162
+ id: 'inspection-deviation',
1163
+ title: 'Deviation Analysis',
1164
+ category: 'Inspection',
1165
+ description: 'Compare two versions of a part',
1166
+ shortcut: 'I, E',
1167
+ content: `
1168
+ Analyze differences between two part versions:
1169
+ - Calculate maximum deviation
1170
+ - Generate heatmap of differences
1171
+ - Identify moved or modified regions
1172
+ - Set tolerance for comparison
1173
+
1174
+ Select 2 parts and run deviation analysis.
1175
+ `
1176
+ },
1177
+ {
1178
+ id: 'inspection-clearance',
1179
+ title: 'Clearance Measurement',
1180
+ category: 'Inspection',
1181
+ description: 'Measure minimum distance between parts',
1182
+ shortcut: 'I, C, L',
1183
+ content: `
1184
+ Measure clearances between parts:
1185
+ - Calculate minimum distance
1186
+ - Identify closest points
1187
+ - Check assembly fit
1188
+ - Warn if parts overlap
1189
+
1190
+ Select 2 parts for clearance check.
1191
+ `
1192
+ },
1193
+ {
1194
+ id: 'inspection-measure',
1195
+ title: 'Measurement Tools',
1196
+ category: 'Inspection',
1197
+ description: 'Measure distances, angles, and areas',
1198
+ shortcut: 'M',
1199
+ content: `
1200
+ Precision measurement tools:
1201
+ - Distance: between any two points
1202
+ - Angle: between three points
1203
+ - Radius: of curved edges
1204
+ - Area: of selected faces
1205
+
1206
+ Click points in 3D to measure.
1207
+ `
1208
+ },
1209
+ {
1210
+ id: 'inspection-wall-thickness-advanced',
1211
+ title: 'Wall Thickness (Advanced)',
1212
+ category: 'Inspection',
1213
+ description: 'Detect and visualize thin and thick regions with color mapping',
1214
+ shortcut: 'I, W, A',
1215
+ content: `
1216
+ Advanced wall thickness analysis with visualization:
1217
+ - Set minimum and maximum thickness ranges
1218
+ - Red highlighting for thin walls
1219
+ - Blue highlighting for thick sections
1220
+ - Green for acceptable ranges
1221
+ - Export thickness map
1222
+
1223
+ Useful for injection molding and 3D printing validation.
1224
+ `
1225
+ },
1226
+ {
1227
+ id: 'inspection-continuity',
1228
+ title: 'Surface Continuity',
1229
+ category: 'Inspection',
1230
+ description: 'Check G0, G1, G2 continuity between surfaces',
1231
+ shortcut: 'I, S, C',
1232
+ content: `
1233
+ Verify surface continuity between adjacent faces:
1234
+ - G0: Positional continuity (surfaces touch)
1235
+ - G1: Tangent continuity (same surface normal)
1236
+ - G2: Curvature continuity (matching curvature)
1237
+
1238
+ Critical for high-quality surface modeling.
1239
+ `
1240
+ },
1241
+ {
1242
+ id: 'inspection-accessibility',
1243
+ title: 'Accessibility Analysis',
1244
+ category: 'Inspection',
1245
+ description: 'Check if all features are reachable by tools',
1246
+ shortcut: 'I, A',
1247
+ content: `
1248
+ Analyze accessibility for manufacturing and assembly:
1249
+ - Identify unreachable areas
1250
+ - Check tool clearances
1251
+ - Verify hand access for assembly
1252
+ - Export accessibility heatmap
1253
+
1254
+ Helps catch design flaws early.
1255
+ `
1256
+ },
1257
+ {
1258
+ id: 'inspection-component-stats',
1259
+ title: 'Component Statistics',
1260
+ category: 'Inspection',
1261
+ description: 'Count parts, identify unique components, calculate weight breakdown',
1262
+ shortcut: 'I, C, S',
1263
+ content: `
1264
+ Get assembly-level statistics:
1265
+ - Total number of components
1266
+ - Unique part types
1267
+ - Total mass and volume
1268
+ - Per-component breakdown
1269
+ - Material cost estimates
1270
+
1271
+ Useful for BOM generation and cost analysis.
1272
+ `
1273
+ },
1274
+ {
1275
+ id: 'inspection-stress',
1276
+ title: 'Stress Concentration',
1277
+ category: 'Inspection',
1278
+ description: 'Visualize stress concentration areas based on geometry',
1279
+ shortcut: 'I, S, T',
1280
+ content: `
1281
+ Simplified stress analysis visualization:
1282
+ - Heat map showing stress concentration
1283
+ - Color intensity indicates stress level
1284
+ - Set load direction and magnitude
1285
+ - Identify critical stress regions
1286
+
1287
+ Note: Requires proper FEA for accurate analysis.
1288
+ `
1289
+ },
1290
+ {
1291
+ id: 'inspection-export-report',
1292
+ title: 'Export Full Report',
1293
+ category: 'Inspection',
1294
+ description: 'Generate comprehensive HTML inspection report',
1295
+ shortcut: 'I, E, R',
1296
+ content: `
1297
+ Create detailed inspection reports:
1298
+ - Mass properties summary
1299
+ - Curvature analysis results
1300
+ - Draft angle verification
1301
+ - Wall thickness findings
1302
+ - Professional HTML format with tables and charts
1303
+ - Ready for printing or sharing
1304
+
1305
+ Includes timestamp and all selected analyses.
1306
+ `
1307
+ }
1308
+ ];
1309
+
1310
+ export default {
1311
+ init,
1312
+ getMassProperties,
1313
+ detectInterference,
1314
+ analyzeCurvature,
1315
+ analyzeDraft,
1316
+ checkWallThickness,
1317
+ analyzeDeviation,
1318
+ measureClearance,
1319
+ measureDistance,
1320
+ measureAngle,
1321
+ generateReport,
1322
+ formatReportAsHTML,
1323
+ analyzeWallThicknessAdvanced,
1324
+ checkSurfaceContinuity,
1325
+ analyzeAccessibility,
1326
+ getComponentStatistics,
1327
+ analyzeStressConcentration,
1328
+ exportFullReport,
1329
+ helpEntries
1330
+ };