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.
- package/DELIVERABLES.txt +296 -445
- package/ENHANCEMENT_COMPLETION_REPORT.md +383 -0
- package/ENHANCEMENT_SUMMARY.txt +308 -0
- package/FEATURE_INVENTORY.md +235 -0
- package/FUSION360_FEATURES_SUMMARY.md +452 -0
- package/FUSION360_PARITY_ENHANCEMENTS.md +461 -0
- package/FUSION360_PARITY_SUMMARY.md +520 -0
- package/FUSION360_QUICK_REFERENCE.md +351 -0
- package/IMPLEMENTATION_GUIDE.md +502 -0
- package/INTEGRATION-GUIDE.md +377 -0
- package/MODULES_PHASES_6_7.md +780 -0
- package/MODULE_API_REFERENCE.md +712 -0
- package/MODULE_INVENTORY.txt +264 -0
- package/app/index.html +1345 -4930
- package/app/js/app.js +1312 -514
- package/app/js/brep-kernel.js +1353 -455
- package/app/js/help-module.js +1437 -0
- package/app/js/kernel.js +364 -40
- package/app/js/modules/animation-module.js +1461 -0
- package/app/js/modules/assembly-module.js +47 -3
- package/app/js/modules/cam-module.js +1572 -0
- package/app/js/modules/collaboration-module.js +1615 -0
- package/app/js/modules/constraint-module.js +1266 -0
- package/app/js/modules/data-module.js +1054 -0
- package/app/js/modules/drawing-module.js +54 -8
- package/app/js/modules/formats-module.js +873 -0
- package/app/js/modules/inspection-module.js +1330 -0
- package/app/js/modules/mesh-module-enhanced.js +880 -0
- package/app/js/modules/mesh-module.js +968 -0
- package/app/js/modules/operations-module.js +40 -7
- package/app/js/modules/plugin-module.js +1554 -0
- package/app/js/modules/rendering-module.js +1766 -0
- package/app/js/modules/scripting-module.js +1073 -0
- package/app/js/modules/simulation-module.js +60 -3
- package/app/js/modules/sketch-module.js +2029 -91
- package/app/js/modules/step-module.js +47 -6
- package/app/js/modules/surface-module.js +1040 -0
- package/app/js/modules/version-module.js +1830 -0
- package/app/js/modules/viewport-module.js +95 -8
- package/app/test-agent-v2.html +881 -1316
- package/cycleCAD-Architecture-v2.pptx +0 -0
- package/docs/ARCHITECTURE.html +838 -1408
- package/docs/DEVELOPER-GUIDE.md +1504 -0
- package/docs/TUTORIAL.md +740 -0
- package/package.json +1 -1
- package/~$cycleCAD-Architecture-v2.pptx +0 -0
- package/.github/scripts/cad-diff.js +0 -590
- 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
|
+
};
|