cyclecad 2.0.1 → 2.1.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/IMPLEMENTATION_GUIDE.md +502 -0
- package/INTEGRATION-GUIDE.md +377 -0
- package/MODULES_PHASES_6_7.md +780 -0
- package/app/index.html +106 -2
- 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 +967 -0
- package/app/js/modules/assembly-module.js +47 -3
- package/app/js/modules/cam-module.js +1067 -0
- package/app/js/modules/collaboration-module.js +1102 -0
- package/app/js/modules/data-module.js +1656 -0
- package/app/js/modules/drawing-module.js +54 -8
- package/app/js/modules/formats-module.js +1173 -0
- package/app/js/modules/inspection-module.js +937 -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 +957 -0
- package/app/js/modules/rendering-module.js +1306 -0
- package/app/js/modules/scripting-module.js +955 -0
- package/app/js/modules/simulation-module.js +60 -3
- package/app/js/modules/sketch-module.js +1032 -90
- package/app/js/modules/step-module.js +47 -6
- package/app/js/modules/surface-module.js +728 -0
- package/app/js/modules/version-module.js +1410 -0
- package/app/js/modules/viewport-module.js +95 -8
- package/app/test-agent-v2.html +881 -1316
- 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/.github/scripts/cad-diff.js +0 -590
- package/.github/workflows/cad-diff.yml +0 -117
|
@@ -0,0 +1,937 @@
|
|
|
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
|
+
// HELP ENTRIES
|
|
791
|
+
// ============================================================================
|
|
792
|
+
|
|
793
|
+
export const helpEntries = [
|
|
794
|
+
{
|
|
795
|
+
id: 'inspection-mass-properties',
|
|
796
|
+
title: 'Mass Properties',
|
|
797
|
+
category: 'Inspection',
|
|
798
|
+
description: 'Calculate volume, mass, surface area, center of gravity, and moment of inertia',
|
|
799
|
+
shortcut: 'I, M',
|
|
800
|
+
content: `
|
|
801
|
+
Get comprehensive mass properties for any part including:
|
|
802
|
+
- Volume and mass (with material density)
|
|
803
|
+
- Surface area
|
|
804
|
+
- Center of gravity (CoG)
|
|
805
|
+
- Moment of inertia (Ixx, Iyy, Izz)
|
|
806
|
+
- Bounding box dimensions
|
|
807
|
+
|
|
808
|
+
Select a part and click the Mass Properties button.
|
|
809
|
+
`
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
id: 'inspection-interference',
|
|
813
|
+
title: 'Interference Detection',
|
|
814
|
+
category: 'Inspection',
|
|
815
|
+
description: 'Check if parts overlap and measure intersection volume',
|
|
816
|
+
shortcut: 'I, I',
|
|
817
|
+
content: `
|
|
818
|
+
Detect and analyze geometric interference between parts:
|
|
819
|
+
- Check if two or more parts intersect
|
|
820
|
+
- Measure intersection volume
|
|
821
|
+
- Identify interfering face pairs
|
|
822
|
+
- Export interference regions
|
|
823
|
+
|
|
824
|
+
Select 2+ parts and run interference check.
|
|
825
|
+
`
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
id: 'inspection-curvature',
|
|
829
|
+
title: 'Curvature Analysis',
|
|
830
|
+
category: 'Inspection',
|
|
831
|
+
description: 'Visualize surface curvature with color mapping',
|
|
832
|
+
shortcut: 'I, C',
|
|
833
|
+
content: `
|
|
834
|
+
Analyze and visualize surface curvature:
|
|
835
|
+
- Gaussian, mean, and principal curvatures
|
|
836
|
+
- Heatmap color visualization
|
|
837
|
+
- Export curvature data as CSV
|
|
838
|
+
- Identify sharp edges and discontinuities
|
|
839
|
+
`
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
id: 'inspection-draft',
|
|
843
|
+
title: 'Draft Analysis',
|
|
844
|
+
category: 'Inspection',
|
|
845
|
+
description: 'Check draft angles for injection molding',
|
|
846
|
+
shortcut: 'I, D',
|
|
847
|
+
content: `
|
|
848
|
+
Analyze part draft for injection molding:
|
|
849
|
+
- Set pull direction and minimum draft angle
|
|
850
|
+
- Identify faces with insufficient draft
|
|
851
|
+
- Recommend draft angles (typically 2-5°)
|
|
852
|
+
- Export problem areas report
|
|
853
|
+
|
|
854
|
+
Default: 5° pull on Z-axis
|
|
855
|
+
`
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
id: 'inspection-wall-thickness',
|
|
859
|
+
title: 'Wall Thickness Check',
|
|
860
|
+
category: 'Inspection',
|
|
861
|
+
description: 'Detect thin walls and manufacturing issues',
|
|
862
|
+
shortcut: 'I, W',
|
|
863
|
+
content: `
|
|
864
|
+
Validate wall thickness for manufacturing:
|
|
865
|
+
- Set minimum acceptable thickness
|
|
866
|
+
- Identify thin-wall areas (typically min 2mm)
|
|
867
|
+
- Suggest thickness improvements
|
|
868
|
+
- Check for internal voids
|
|
869
|
+
|
|
870
|
+
Default minimum: 2mm
|
|
871
|
+
`
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
id: 'inspection-deviation',
|
|
875
|
+
title: 'Deviation Analysis',
|
|
876
|
+
category: 'Inspection',
|
|
877
|
+
description: 'Compare two versions of a part',
|
|
878
|
+
shortcut: 'I, E',
|
|
879
|
+
content: `
|
|
880
|
+
Analyze differences between two part versions:
|
|
881
|
+
- Calculate maximum deviation
|
|
882
|
+
- Generate heatmap of differences
|
|
883
|
+
- Identify moved or modified regions
|
|
884
|
+
- Set tolerance for comparison
|
|
885
|
+
|
|
886
|
+
Select 2 parts and run deviation analysis.
|
|
887
|
+
`
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
id: 'inspection-clearance',
|
|
891
|
+
title: 'Clearance Measurement',
|
|
892
|
+
category: 'Inspection',
|
|
893
|
+
description: 'Measure minimum distance between parts',
|
|
894
|
+
shortcut: 'I, C, L',
|
|
895
|
+
content: `
|
|
896
|
+
Measure clearances between parts:
|
|
897
|
+
- Calculate minimum distance
|
|
898
|
+
- Identify closest points
|
|
899
|
+
- Check assembly fit
|
|
900
|
+
- Warn if parts overlap
|
|
901
|
+
|
|
902
|
+
Select 2 parts for clearance check.
|
|
903
|
+
`
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
id: 'inspection-measure',
|
|
907
|
+
title: 'Measurement Tools',
|
|
908
|
+
category: 'Inspection',
|
|
909
|
+
description: 'Measure distances, angles, and areas',
|
|
910
|
+
shortcut: 'M',
|
|
911
|
+
content: `
|
|
912
|
+
Precision measurement tools:
|
|
913
|
+
- Distance: between any two points
|
|
914
|
+
- Angle: between three points
|
|
915
|
+
- Radius: of curved edges
|
|
916
|
+
- Area: of selected faces
|
|
917
|
+
|
|
918
|
+
Click points in 3D to measure.
|
|
919
|
+
`
|
|
920
|
+
}
|
|
921
|
+
];
|
|
922
|
+
|
|
923
|
+
export default {
|
|
924
|
+
init,
|
|
925
|
+
getMassProperties,
|
|
926
|
+
detectInterference,
|
|
927
|
+
analyzeCurvature,
|
|
928
|
+
analyzeDraft,
|
|
929
|
+
checkWallThickness,
|
|
930
|
+
analyzeDeviation,
|
|
931
|
+
measureClearance,
|
|
932
|
+
measureDistance,
|
|
933
|
+
measureAngle,
|
|
934
|
+
generateReport,
|
|
935
|
+
formatReportAsHTML,
|
|
936
|
+
helpEntries
|
|
937
|
+
};
|