Skip to main content
Glama
Noosbai
by Noosbai

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
NameRequiredDescriptionDefault
file_pathYesChemin absolu vers le fichier STL ou 3MF

Implementation Reference

  • 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)}`,
          }],
        };
      }
    },
  • 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"),
    },
  • 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);
  • 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,
      };
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Noosbai/PrusaMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server