generate_prusaslicer_config
Generate custom PrusaSlicer configuration files (.ini) for specific print goals, materials, and printers. Create optimized profiles by specifying print intentions, materials, nozzle sizes, and custom settings.
Instructions
Génère un fichier .ini compatible PrusaSlicer à partir d'une intention ou de paramètres custom. Le fichier peut être chargé directement avec --load dans PrusaSlicer.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| printer | No | Nom de l'imprimante | Generic |
| nozzle | No | Diamètre de buse en mm | |
| goal | Yes | Intention d'impression | |
| material | No | Matériau | PLA |
| stl_path | No | Chemin STL pour analyse auto | |
| output_path | No | Chemin de sortie pour le .ini (sinon fichier temporaire) | |
| custom_settings | No | Paramètres custom PrusaSlicer à overrider (ex: {"layer_height": 0.15}) |
Implementation Reference
- src/tools/generate-config.ts:33-96 (handler)Main handler function for generate_prusaslicer_config tool. Parses STL (if provided), generates profile recommendations via recommendProfile(), converts to INI settings, applies custom overrides, writes the config file, and returns formatted output with usage instructions.
async ({ printer, nozzle, goal, material, stl_path, output_path, custom_settings }) => { try { // Mesh analysis if STL provided let meshAnalysis: MeshAnalysis | undefined; if (stl_path && existsSync(stl_path)) { const stl = await parseStl(stl_path); meshAnalysis = analyzeMesh(stl); } // Generate recommendation const profile = recommendProfile(printer, nozzle, goal, material, meshAnalysis); const settings = profileToIniSettings(profile); // Apply custom overrides (preserve MVS if not explicitly set) if (custom_settings) { const hasMvs = "max_volumetric_speed" in custom_settings || "filament_max_volumetric_speed" in custom_settings; for (const [key, value] of Object.entries(custom_settings)) { settings[key] = value; } // Ensure MVS is always present — critical for PrusaSlicer speed limiting if (!hasMvs && !settings["filament_max_volumetric_speed"]) { settings["filament_max_volumetric_speed"] = settings["max_volumetric_speed"] ?? 15; } } // Determine output path const finalPath = output_path ?? join(tmpdir(), `prusaslicer-${profile.goal}-${Date.now()}.ini`); await writeRawIniFile(settings, finalPath); // Also return the ini content as text for immediate use const iniContent = serializeIni(settings); const lines = [ `## Config PrusaSlicer générée`, `**Fichier** : ${finalPath}`, `**Objectif** : ${profile.goal} | **Matériau** : ${profile.material} | **Buse** : ${profile.nozzle}mm`, "", "```ini", iniContent.trim(), "```", "", `Usage PrusaSlicer CLI :`, "```", `prusa-slicer-console.exe --load "${finalPath}" --export-gcode model.stl`, "```", ]; return { content: [{ type: "text" as const, text: lines.join("\n") }], }; } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Erreur de génération : ${error instanceof Error ? error.message : String(error)}`, }], }; } }, - src/tools/generate-config.ts:12-98 (registration)Tool registration via registerGenerateConfig(). Defines the tool name 'generate_prusaslicer_config', metadata (title, description), Zod input schema (printer, nozzle, goal, material, stl_path, output_path, custom_settings), and the async handler function.
export function registerGenerateConfig(server: McpServer) { server.registerTool( "generate_prusaslicer_config", { title: "Générer un fichier config PrusaSlicer", description: "Génère un fichier .ini compatible PrusaSlicer à partir d'une intention ou de paramètres custom. " + "Le fichier peut être chargé directement avec --load dans PrusaSlicer.", inputSchema: { printer: z.string().default("Generic").describe("Nom de l'imprimante"), nozzle: z.number().default(0.4).describe("Diamètre de buse en mm"), goal: z.string().describe("Intention d'impression"), material: z.string().default("PLA").describe("Matériau"), stl_path: z.string().optional().describe("Chemin STL pour analyse auto"), output_path: z.string().optional().describe("Chemin de sortie pour le .ini (sinon fichier temporaire)"), custom_settings: z .record(z.string(), z.union([z.string(), z.number()])) .optional() .describe("Paramètres custom PrusaSlicer à overrider (ex: {\"layer_height\": 0.15})"), }, }, async ({ printer, nozzle, goal, material, stl_path, output_path, custom_settings }) => { try { // Mesh analysis if STL provided let meshAnalysis: MeshAnalysis | undefined; if (stl_path && existsSync(stl_path)) { const stl = await parseStl(stl_path); meshAnalysis = analyzeMesh(stl); } // Generate recommendation const profile = recommendProfile(printer, nozzle, goal, material, meshAnalysis); const settings = profileToIniSettings(profile); // Apply custom overrides (preserve MVS if not explicitly set) if (custom_settings) { const hasMvs = "max_volumetric_speed" in custom_settings || "filament_max_volumetric_speed" in custom_settings; for (const [key, value] of Object.entries(custom_settings)) { settings[key] = value; } // Ensure MVS is always present — critical for PrusaSlicer speed limiting if (!hasMvs && !settings["filament_max_volumetric_speed"]) { settings["filament_max_volumetric_speed"] = settings["max_volumetric_speed"] ?? 15; } } // Determine output path const finalPath = output_path ?? join(tmpdir(), `prusaslicer-${profile.goal}-${Date.now()}.ini`); await writeRawIniFile(settings, finalPath); // Also return the ini content as text for immediate use const iniContent = serializeIni(settings); const lines = [ `## Config PrusaSlicer générée`, `**Fichier** : ${finalPath}`, `**Objectif** : ${profile.goal} | **Matériau** : ${profile.material} | **Buse** : ${profile.nozzle}mm`, "", "```ini", iniContent.trim(), "```", "", `Usage PrusaSlicer CLI :`, "```", `prusa-slicer-console.exe --load "${finalPath}" --export-gcode model.stl`, "```", ]; return { content: [{ type: "text" as const, text: lines.join("\n") }], }; } catch (error) { return { isError: true, content: [{ type: "text" as const, text: `Erreur de génération : ${error instanceof Error ? error.message : String(error)}`, }], }; } }, ); } - src/tools/generate-config.ts:20-31 (schema)Zod input schema validation for the tool parameters. Defines printer (string), nozzle (number), goal (string), material (string), stl_path (optional string), output_path (optional string), and custom_settings (optional record of string/number values).
inputSchema: { printer: z.string().default("Generic").describe("Nom de l'imprimante"), nozzle: z.number().default(0.4).describe("Diamètre de buse en mm"), goal: z.string().describe("Intention d'impression"), material: z.string().default("PLA").describe("Matériau"), stl_path: z.string().optional().describe("Chemin STL pour analyse auto"), output_path: z.string().optional().describe("Chemin de sortie pour le .ini (sinon fichier temporaire)"), custom_settings: z .record(z.string(), z.union([z.string(), z.number()])) .optional() .describe("Paramètres custom PrusaSlicer à overrider (ex: {\"layer_height\": 0.15})"), }, - src/profile-engine.ts:258-587 (helper)Core recommendation engine function recommendProfile(). Takes printer, nozzle, goal, material, and optional mesh analysis to generate a RecommendedProfile with all settings including MVS calculations, nozzle adjustments, support/brim decisions, and detailed warnings based on Bible FDM rules.
export function recommendProfile( printer: string, nozzle: number, goal: string, material: string, meshAnalysis?: MeshAnalysis, options?: { inputShaper?: boolean }, ): RecommendedProfile { // Validate nozzle diameter if (!nozzle || nozzle <= 0 || nozzle > 3) { nozzle = 0.4; // safe fallback } // Resolve goal const resolvedGoal = resolveGoal(goal); const preset = GOALS[resolvedGoal]; // Resolve material const mat = MATERIALS[material.toUpperCase()] ?? MATERIALS["PLA"]; // Nozzle specs (Bible FDM rules) const nozzleSpec = getNozzleSpec(nozzle); // Input Shaper mode (Bible FDM: higher accel/speed if firmware supports it) const inputShaper = options?.inputShaper ?? false; // Base speeds (mm/s) — higher base if Input Shaper enabled const baseSpeed = inputShaper ? 80 : 60; const baseTravelSpeed = inputShaper ? 200 : 150; // Compute settings const warnings: string[] = []; // Input Shaper warning if (inputShaper) { warnings.push("Mode Input Shaper activé — vitesses et accélérations plus élevées (Bible FDM: vérifier correspondance profil ⇄ firmware)"); } // Layer height: nozzle × ratio, clamped to [25%, 80%] of nozzle (Bible FDM rule) const rawLayerHeight = round2(nozzle * preset.layerRatio); const layerHeight = clamp(rawLayerHeight, nozzleSpec.minLayerHeight, nozzleSpec.maxLayerHeight); // Line width: PrusaSlicer default = 1.125×nozzle, or wider for speed const lineWidth = resolvedGoal === "speed" ? round2(nozzle * 1.2) : nozzleSpec.autoExtrusionWidth; // Print speed — compute but then check against MVS let printSpeed = Math.round(baseSpeed * preset.speedMultiplier); // MVS check: volumetric_flow = layer_height × line_width × speed // Bible: MVS is THE central parameter — caps speed automatically const maxVolumetricFlow = mat.maxVolumetricSpeed; const theoreticalFlow = layerHeight * lineWidth * printSpeed; let mvsLimited = false; if (theoreticalFlow > maxVolumetricFlow) { const cappedSpeed = Math.floor(maxVolumetricFlow / (layerHeight * lineWidth)); warnings.push( `Débit volumique capé par MVS (${maxVolumetricFlow} mm³/s pour ${mat.name}) : ` + `${printSpeed}mm/s → ${cappedSpeed}mm/s. ` + `Astuce Bible FDM : réduire la hauteur de couche peut donner du "détail gratuit" sans augmenter le temps.` ); printSpeed = cappedSpeed; mvsLimited = true; } // TPU needs slow speed (Bible: vitesse cadrée via MVS) if (mat.name === "TPU") { printSpeed = Math.min(printSpeed, 25); if (!mvsLimited) { warnings.push("TPU: vitesse limitée à 25mm/s max. Direct drive recommandé."); } } const travelSpeed = Math.round(baseTravelSpeed * Math.min(preset.speedMultiplier, 1.5)); const firstLayerSpeed = Math.round(printSpeed * 0.5); // Infill let infill = preset.infill; let fillPattern = preset.fillPattern; if (resolvedGoal === "strong" && meshAnalysis) { if (meshAnalysis.volume < 5000) { infill = 80; warnings.push("Pièce très petite (<5cm³) — infill augmenté à 80% pour la solidité"); } } // Perimeters const perimeters = preset.perimeters; // Supports — based on mesh analysis let supportMaterial = false; let supportStyle = "grid"; // Bible: Top contact Z distance = 50-75% of layer height const supportContactDistance = round2(layerHeight * 0.6); if (meshAnalysis && meshAnalysis.overhangPercent > 5) { supportMaterial = true; supportStyle = meshAnalysis.overhangPercent > 20 ? "snug" : "grid"; warnings.push( `${meshAnalysis.overhangPercent.toFixed(1)}% de faces en overhang détectées — supports activés` ); } // Brim — if small footprint or ABS/ASA/Nylon/PC or tall piece let brimWidth = 0; const needsBrim = mat.requiresEnclosure || (meshAnalysis && meshAnalysis.boundingBox.size.z > 3 * Math.max( meshAnalysis.boundingBox.size.x, meshAnalysis.boundingBox.size.y )); if (needsBrim) { brimWidth = 5; warnings.push("Brim activé (5mm) — matériau sujet au warping ou pièce haute/étroite"); } // Acceleration let acceleration = 1000; if (resolvedGoal === "speed") acceleration = 2500; if (resolvedGoal === "quality") acceleration = 500; if (mat.name === "TPU") acceleration = 500; // Cooling (Bible: detailed per material) const fanSpeed = mat.fanSpeed; const minFanSpeed = mat.minFanSpeed; const bridgeFanSpeed = mat.bridgeFanSpeed ?? 100; let minLayerTime = 10; if (resolvedGoal === "quality") { minLayerTime = 15; } // Nozzle temp adjustment for large nozzles (Bible: +10-20°C for 0.6mm+ nozzle to increase flow) let nozzleTemp = mat.nozzleTemp; let firstLayerNozzleTemp = mat.firstLayerNozzleTemp; if (nozzle >= 0.6 && resolvedGoal === "speed") { nozzleTemp += 15; firstLayerNozzleTemp += 10; warnings.push( `Buse ≥0.6mm en mode speed : temp buse +15°C (${nozzleTemp}°C) pour suivre le débit volumique (Bible FDM : PLA imprimable 10-20°C plus chaud avec buse 0.6mm)` ); } else if (resolvedGoal === "speed") { nozzleTemp += 10; firstLayerNozzleTemp += 5; warnings.push("Mode speed : température buse +10°C pour suivre le débit"); } // Bed temp const bedTemp = mat.bedTemp; const firstLayerBedTemp = mat.firstLayerBedTemp ?? bedTemp; // Drying warnings for hygroscopic materials (Bible FDM) const DRYING_INFO: Record<string, string> = { NYLON: "Nylon : séchage obligatoire 12h@80°C. Hygroscopique — stocker sous vide avec dessiccant.", PC: "PC : séchage recommandé 8h@80°C. Sensible à l'humidité.", TPU: "TPU : séchage recommandé 4h@50°C. Absorbe l'humidité rapidement.", PETG: "PETG : séchage recommandé 4-6h@65°C si stringing/bulles persistants. Modérément hygroscopique.", PVA: "PVA : séchage obligatoire 8h@45°C. Extrêmement hygroscopique — utiliser immédiatement après séchage.", BVOH: "BVOH : séchage obligatoire 6h@50°C. Très hygroscopique.", }; const dryInfo = DRYING_INFO[mat.name.toUpperCase()]; if (dryInfo) { warnings.push(`Bible FDM — ${dryInfo}`); } // Enclosure warnings (Bible FDM) if (mat.requiresEnclosure) { warnings.push(`${mat.name}: enceinte fermée obligatoire — ${mat.notes}`); } // Recommended surface (Bible FDM) if (mat.recommendedSurface) { warnings.push(`Surface plateau recommandée : ${mat.recommendedSurface}`); } // Extrusion multiplier (Bible: calibration fine du débit réel, default 1.0) const extrusionMultiplier = 1.0; // Build the recommendation const settings: RecommendedProfile["settings"] = { layer_height: { value: layerHeight, reason: `Buse ${nozzle}mm — plage utilisable ${nozzleSpec.minLayerHeight}-${nozzleSpec.maxLayerHeight}mm (Bible FDM: 25%-80% du diamètre buse). ` + `Ratio ${preset.layerRatio} → ${rawLayerHeight}mm (${preset.description})`, }, line_width: { value: lineWidth, reason: `PrusaSlicer auto = ${nozzleSpec.autoExtrusionWidth}mm (1.125×buse). ` + `${resolvedGoal === "speed" ? "Mode speed: 1.2×buse pour débit max." : "Valeur standard PrusaSlicer."}`, }, perimeters: { value: perimeters, reason: resolvedGoal === "strong" ? "5 parois pour résistance mécanique maximale" : resolvedGoal === "vase" ? "1 paroi — mode vase spirale" : `${perimeters} parois — ${preset.description.toLowerCase()}`, }, infill_density: { value: infill, reason: resolvedGoal === "strong" ? `${infill}% — haute densité pour résistance` : resolvedGoal === "vase" ? "0% — mode vase, pas de remplissage" : `${infill}% — suffisant pour ${preset.description.toLowerCase()}`, }, fill_pattern: { value: fillPattern, reason: fillPattern === "gyroid" ? "Gyroid — isotrope, bonne résistance dans toutes les directions, beau rendu" : fillPattern === "cubic" ? "Cubic — résistance 3D optimale pour pièces structurelles" : "Grid — simple et rapide", }, nozzle_temperature: { value: nozzleTemp, reason: `${mat.name} officiel Prusa : ${mat.nozzleTemp}°C${nozzleTemp !== mat.nozzleTemp ? ` (ajusté +${nozzleTemp - mat.nozzleTemp}°C pour débit)` : ""}`, }, first_layer_nozzle_temperature: { value: firstLayerNozzleTemp, reason: `Première couche ${firstLayerNozzleTemp}°C — légèrement plus chaude pour meilleure adhésion (Bible FDM)`, }, bed_temperature: { value: bedTemp, reason: `${mat.name} — ${bedTemp}°C (valeur Prusa officielle)`, }, first_layer_bed_temperature: { value: firstLayerBedTemp, reason: firstLayerBedTemp !== bedTemp ? `Première couche : ${firstLayerBedTemp}°C (5°C+ pour adhésion initiale — Bible FDM)` : `${firstLayerBedTemp}°C — même que le reste`, }, print_speed: { value: printSpeed, reason: mvsLimited ? `Limité par MVS ${maxVolumetricFlow} mm³/s (${mat.name}). Formule: MVS = layer_height × extrusion_width × speed` : `Base ${baseSpeed}mm/s × ${preset.speedMultiplier} (${resolvedGoal})${mat.name === "TPU" ? " — limité pour TPU" : ""}`, }, travel_speed: { value: travelSpeed, reason: `Déplacement rapide — ${travelSpeed}mm/s`, }, first_layer_speed: { value: firstLayerSpeed, reason: `50% de la vitesse d'impression — adhésion première couche (Bible FDM : 1ère couche dépend de calibration Z et temp)`, }, support_material: { value: supportMaterial, reason: meshAnalysis ? supportMaterial ? `Overhangs détectés (${meshAnalysis.overhangPercent.toFixed(1)}%) — supports nécessaires` : `Pas d'overhangs significatifs (${meshAnalysis.overhangPercent.toFixed(1)}%)` : "Pas d'analyse mesh disponible — à évaluer visuellement", }, support_material_style: { value: supportStyle, reason: supportStyle === "snug" ? "Beaucoup d'overhangs — support snug pour meilleur contact" : "Support grid — standard, facile à retirer", }, support_material_contact_distance: { value: supportContactDistance, reason: `${supportContactDistance}mm — ~60% de la hauteur de couche (Bible FDM : 50-75% recommandé pour bon retrait)`, }, brim_width: { value: brimWidth, reason: brimWidth > 0 ? "Brim pour améliorer l'adhésion et réduire le warping" : "Pas de brim nécessaire — bonne surface de contact", }, fan_speed: { value: fanSpeed, reason: fanSpeed === 0 ? `${mat.name} — ventilateur désactivé (sinon délamination/warping — Bible FDM)` : `${mat.name} — ventilateur à ${fanSpeed}% pour refroidissement optimal`, }, min_fan_speed: { value: minFanSpeed, reason: `Minimum ${minFanSpeed}%`, }, bridge_fan_speed: { value: bridgeFanSpeed, reason: `${bridgeFanSpeed}% pour les ponts — refroidissement accru nécessaire pour solidifier le pont (Bible FDM)`, }, min_layer_time: { value: minLayerTime, reason: `${minLayerTime}s minimum par couche — laisse le temps au plastique de refroidir`, }, retraction_length: { value: mat.retractionLength, reason: mat.name === "TPU" ? `${mat.retractionLength}mm — minimale pour flexible (Bible FDM: rétraction délicate TPU)` : mat.name === "PETG" ? `${mat.retractionLength}mm — Bible FDM: PETG max 0.8-1mm rétraction` : `${mat.retractionLength}mm — standard ${mat.name}. Bible FDM: max 2mm MK3, 3.2mm MINI (bowden).`, }, retraction_speed: { value: mat.retractionSpeed, reason: `${mat.retractionSpeed}mm/s — vitesse de rétraction ${mat.name}`, }, max_volumetric_speed: { value: maxVolumetricFlow, reason: `${maxVolumetricFlow} mm³/s — Bible FDM : valeur officielle ${mat.name}. ` + `Le MVS est LE levier central PrusaSlicer : il limite auto les vitesses quand nécessaire ` + `sans recalculer manuellement (Filament Settings prime sur Print Settings).`, }, extrusion_multiplier: { value: extrusionMultiplier, reason: `${extrusionMultiplier} — calibration fine du débit réel. Bible FDM : calibrer EM avant d'ajuster rétraction (un mauvais EM imite stringing/blobs).`, }, acceleration: { value: acceleration, reason: resolvedGoal === "speed" ? "2500mm/s² — accélération haute. Bible FDM : trop haut = ringing/layer shift." : resolvedGoal === "quality" ? "500mm/s² — accélération basse pour réduire ringing (vibrations)" : `1000mm/s² — compromis standard. Bible FDM : ajuster si layer shift ou ringing.`, }, }; return { goal: resolvedGoal, printer, nozzle, material: mat.name, settings, warnings, }; } - src/ini-writer.ts:42-117 (helper)Helper function profileToIniSettings() that converts a RecommendedProfile to PrusaSlicer .ini key-value pairs, handling all PrusaSlicer-specific formatting, key mappings, derived speed calculations, and material-specific settings like wipe and retraction.
export function profileToIniSettings(profile: RecommendedProfile): PrusaSlicerSettings { const result: PrusaSlicerSettings = {}; for (const [key, setting] of Object.entries(profile.settings)) { const iniKey = KEY_MAP[key] ?? key; let value = setting.value; // PrusaSlicer-specific formatting if (key === "infill_density") { value = `${value}%`; } else if (key === "support_material") { value = value ? 1 : 0; } else if (key === "first_layer_speed") { value = `${value}mm/s`; } result[iniKey] = value; } const printSpeed = profile.settings.print_speed.value as number; const lh = profile.settings.layer_height.value as number; // ─── First layer height ──────────────────────────────────── // Bible FDM: first layer slightly thicker for better adhesion // PrusaSlicer default: typically 0.2mm or same as layer_height result["first_layer_height"] = Math.max(lh, 0.2); // ─── Derived speed settings ──────────────────────────────── // All speeds derived from perimeter_speed for consistency result["infill_speed"] = Math.round(printSpeed * 1.2); // infill can go faster result["solid_infill_speed"] = Math.round(printSpeed * 0.8); // solid infill slightly slower result["top_solid_infill_speed"] = Math.round(printSpeed * 0.5); // top surface = visible, slow result["external_perimeter_speed"] = Math.round(printSpeed * 0.5); // external = visible result["small_perimeter_speed"] = Math.round(printSpeed * 0.5); result["bridge_speed"] = Math.round(printSpeed * 0.4); // slow for clean bridges result["gap_fill_speed"] = Math.round(printSpeed * 0.5); result["support_material_speed"] = Math.round(printSpeed * 0.8); // ─── Retraction settings ─────────────────────────────────── result["retract_before_travel"] = 2; result["retract_lift"] = 0.2; result["retract_layer_change"] = 1; // Bible FDM: recommended on // Wipe: conditionnel — désactivé pour TPU/flexibles (Bible FDM: rétraction délicate) const matUpper = profile.material.toUpperCase().replace(/\s*\(.*\)/, ""); // strip "(Polycarbonate)" etc result["wipe"] = NO_WIPE_MATERIALS.has(matUpper) ? 0 : 1; // ─── Top/bottom layers ───────────────────────────────────── const shellThickness = 0.8; // mm — standard shell const solidLayers = Math.max(3, Math.ceil(shellThickness / lh)); result["top_solid_layers"] = solidLayers; result["bottom_solid_layers"] = solidLayers; // ─── Nozzle diameter ─────────────────────────────────────── result["nozzle_diameter"] = profile.nozzle; // First layer extrusion width (wider for adhesion) result["first_layer_extrusion_width"] = Math.round(profile.nozzle * 1.4 * 100) / 100; // ─── Filament MVS ────────────────────────────────────────── result["filament_max_volumetric_speed"] = profile.settings.max_volumetric_speed.value; // ─── Seam position ───────────────────────────────────────── // Bible FDM: seam position matters for blobs/zits — "nearest" reduces travel result["seam_position"] = "nearest"; // ─── Elephant foot compensation ──────────────────────────── // Bible FDM: ajuster si base "écrasée" result["elefant_foot_compensation"] = 0.1; // PrusaSlicer uses this exact spelling // ─── Overhang speed ──────────────────────────────────────── // Bible FDM: ajuster supports/overhang speed plutôt que fan (surtout ASA) result["overhangs"] = 1; // enable overhang detection for speed reduction return result; }