analyze_mesh
Analyze STL or 3MF files to check dimensions, volume, surface area, overhang percentage, fine detail detection, and manifold verification for 3D printing preparation.
Instructions
Analyse un fichier STL ou 3MF et retourne : dimensions, volume, surface, pourcentage d'overhangs, détection de détails fins, et vérification manifold.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Chemin absolu vers le fichier STL ou 3MF |
Implementation Reference
- src/tools/analyze-mesh.ts:31-92 (handler)Main handler function that executes the analyze_mesh tool logic - validates file path, parses STL/3MF files, calls analyzeMesh helper, and formats results with dimensions, volume, surface area, overhangs, fine details, and manifold status
async ({ file_path }) => { try { if (!existsSync(file_path)) { return { isError: true, content: [{ type: "text" as const, text: `Fichier non trouvé : ${file_path}` }], }; } const lower = file_path.toLowerCase(); if (!lower.endsWith(".stl") && !lower.endsWith(".3mf")) { return { isError: true, content: [{ type: "text" as const, text: "Formats supportés : STL, 3MF" }], }; } console.error(`[analyze_mesh] Parsing ${file_path}...`); const mesh = await parseModel(file_path); console.error(`[analyze_mesh] Parsed ${mesh.triangles.length} triangles, analyzing...`); const analysis = analyzeMesh(mesh); const bb = analysis.boundingBox; const lines = [ `## Analyse Mesh : ${mesh.name}`, "", `**Dimensions (XYZ)** : ${bb.size.x.toFixed(2)} × ${bb.size.y.toFixed(2)} × ${bb.size.z.toFixed(2)} mm`, `**Volume** : ${analysis.volume.toFixed(2)} mm³ (${(analysis.volume / 1000).toFixed(2)} cm³)`, `**Surface** : ${analysis.surfaceArea.toFixed(2)} mm²`, `**Triangles** : ${analysis.triangleCount.toLocaleString()}`, "", `### Overhangs`, `- Faces en overhang (>45°) : **${analysis.overhangPercent.toFixed(1)}%** (${analysis.overhangTriangles} triangles)`, analysis.overhangPercent > 5 ? `- Supports probablement nécessaires` : `- Peu d'overhangs — pas de support nécessaire a priori`, "", `### Détails fins`, `- Petits triangles : ${analysis.smallDetailPercent.toFixed(1)}%`, analysis.hasSmallDetails ? `- Détails fins détectés — layer height fine recommandée` : `- Pas de détails fins critiques`, "", `### Manifold`, analysis.isManifold ? `- Mesh manifold (fermé, prêt pour l'impression)` : `- Mesh non-manifold (${analysis.nonManifoldEdges} arêtes problématiques) — repair recommandé`, ]; return { content: [{ type: "text" as const, text: lines.join("\n") }], }; } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Erreur d'analyse : ${error instanceof Error ? error.message : String(error)}`, }], }; } }, - src/tools/analyze-mesh.ts:27-29 (schema)Input schema definition using zod - defines that the tool accepts a single 'file_path' string parameter
inputSchema: { file_path: z.string().describe("Chemin absolu vers le fichier STL ou 3MF"), }, - src/tools/analyze-mesh.ts:19-93 (registration)Tool registration function that registers 'analyze_mesh' with the MCP server, including title, description, input schema, and handler callback
export function registerAnalyzeMesh(server: McpServer) { server.registerTool( "analyze_mesh", { title: "Analyser un mesh 3D", description: "Analyse un fichier STL ou 3MF et retourne : dimensions, volume, surface, " + "pourcentage d'overhangs, détection de détails fins, et vérification manifold.", inputSchema: { file_path: z.string().describe("Chemin absolu vers le fichier STL ou 3MF"), }, }, async ({ file_path }) => { try { if (!existsSync(file_path)) { return { isError: true, content: [{ type: "text" as const, text: `Fichier non trouvé : ${file_path}` }], }; } const lower = file_path.toLowerCase(); if (!lower.endsWith(".stl") && !lower.endsWith(".3mf")) { return { isError: true, content: [{ type: "text" as const, text: "Formats supportés : STL, 3MF" }], }; } console.error(`[analyze_mesh] Parsing ${file_path}...`); const mesh = await parseModel(file_path); console.error(`[analyze_mesh] Parsed ${mesh.triangles.length} triangles, analyzing...`); const analysis = analyzeMesh(mesh); const bb = analysis.boundingBox; const lines = [ `## Analyse Mesh : ${mesh.name}`, "", `**Dimensions (XYZ)** : ${bb.size.x.toFixed(2)} × ${bb.size.y.toFixed(2)} × ${bb.size.z.toFixed(2)} mm`, `**Volume** : ${analysis.volume.toFixed(2)} mm³ (${(analysis.volume / 1000).toFixed(2)} cm³)`, `**Surface** : ${analysis.surfaceArea.toFixed(2)} mm²`, `**Triangles** : ${analysis.triangleCount.toLocaleString()}`, "", `### Overhangs`, `- Faces en overhang (>45°) : **${analysis.overhangPercent.toFixed(1)}%** (${analysis.overhangTriangles} triangles)`, analysis.overhangPercent > 5 ? `- Supports probablement nécessaires` : `- Peu d'overhangs — pas de support nécessaire a priori`, "", `### Détails fins`, `- Petits triangles : ${analysis.smallDetailPercent.toFixed(1)}%`, analysis.hasSmallDetails ? `- Détails fins détectés — layer height fine recommandée` : `- Pas de détails fins critiques`, "", `### Manifold`, analysis.isManifold ? `- Mesh manifold (fermé, prêt pour l'impression)` : `- Mesh non-manifold (${analysis.nonManifoldEdges} arêtes problématiques) — repair recommandé`, ]; return { content: [{ type: "text" as const, text: lines.join("\n") }], }; } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Erreur d'analyse : ${error instanceof Error ? error.message : String(error)}`, }], }; } }, ); - src/index.ts:42-42 (registration)Registration call in main server initialization - invokes registerAnalyzeMesh to add the tool to the MCP server
registerAnalyzeMesh(server); - src/mesh-analyzer.ts:6-91 (helper)Core analysis helper function that computes mesh geometry: triangle count, bounding box, volume (via signed tetrahedron method), surface area, overhang detection (>45° faces), fine detail detection, and manifold edge checking
export function analyzeMesh(stl: StlData): MeshAnalysis { const { triangles } = stl; const n = triangles.length; if (n === 0) { return { triangleCount: 0, boundingBox: { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 }, size: { x: 0, y: 0, z: 0 } }, volume: 0, surfaceArea: 0, overhangPercent: 0, overhangTriangles: 0, hasSmallDetails: false, smallDetailPercent: 0, isManifold: false, nonManifoldEdges: 0, }; } // Single pass for volume, surface area, bounding box, overhangs, triangle areas let volume = 0; let surfaceArea = 0; let overhangCount = 0; const areas: number[] = new Array(n); const bbMin: Vec3 = { x: Infinity, y: Infinity, z: Infinity }; const bbMax: Vec3 = { x: -Infinity, y: -Infinity, z: -Infinity }; for (let i = 0; i < n; i++) { const t = triangles[i]; // Bounding box updateBB(bbMin, bbMax, t.v1); updateBB(bbMin, bbMax, t.v2); updateBB(bbMin, bbMax, t.v3); // Signed volume (tetrahedron with origin) volume += signedTetraVolume(t.v1, t.v2, t.v3); // Surface area via cross product const area = triangleArea(t.v1, t.v2, t.v3); surfaceArea += area; areas[i] = area; // Overhang detection: face normal Z component < cos(45°) ≈ -0.707 // If the normal points downward (nz < 0) beyond 45°, it's an overhang. // We compute the actual normal from vertices for accuracy. const normal = computeNormal(t.v1, t.v2, t.v3); if (normal.z < -0.707) { overhangCount++; } } // Small detail detection: triangles with area < 10% of median area const sortedAreas = [...areas].sort((a, b) => a - b); const medianArea = sortedAreas[Math.floor(n / 2)]; const smallThreshold = medianArea * 0.1; let smallDetailCount = 0; for (let i = 0; i < n; i++) { if (areas[i] < smallThreshold && areas[i] > 0) { smallDetailCount++; } } // Manifold check: each edge must be shared by exactly 2 triangles const { isManifold, nonManifoldEdges } = checkManifold(triangles); const size: Vec3 = { x: bbMax.x - bbMin.x, y: bbMax.y - bbMin.y, z: bbMax.z - bbMin.z, }; return { triangleCount: n, boundingBox: { min: bbMin, max: bbMax, size }, volume: Math.abs(volume), surfaceArea, overhangPercent: (overhangCount / n) * 100, overhangTriangles: overhangCount, hasSmallDetails: smallDetailCount > n * 0.05, smallDetailPercent: (smallDetailCount / n) * 100, isManifold, nonManifoldEdges, }; }