mcp-3d-printer-server 1.2.29 → 1.2.30

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.30] – 2026-11-04
4
+
5
+ ### Added
6
+
7
+ - `slice_stl` and `slice_with_template` take `quality` (`draft`, `standard`, `fine`) and `strength` (`standard`, `strong`, `max`). The server turns them into the slicer's own settings: Draft and Fine set the layer height for the nozzle in use (0.28 and 0.12 mm on a 0.4 mm nozzle, scaled with it), Strong and Max set walls and infill (4 walls at 25%, 6 at 40%), and Standard leaves the resolved preset alone. An explicit `layer_height`, `wall_loops` or `sparse_infill_density` still wins. Clients such as Counterpart can now send the person's choice instead of carrying slicer numbers. Checked with stand-in slicers only. For PrusaSlicer, Slic3r and generic OrcaSlicer the layer height scales with `nozzle_diameter` (or `NOZZLE_DIAMETER`), which must match the profile's nozzle; Cura takes only Standard. Same change as bambu-printer-mcp 1.1.56.
8
+
3
9
  ## [1.2.29] – 2026-11-04
4
10
 
5
11
  ### Changed
package/README.md CHANGED
@@ -688,6 +688,8 @@ Options for the Bambu-compatible path:
688
688
  | `scale`, `rotate`, `rotate_x`, `rotate_y` | Transform before slicing (uniform scale; rotations in degrees). For OrcaSlicer STL input the server applies them to a mesh copy in Orca's order to avoid a CLI crash; see [OrcaSlicer CLI limits](https://github.com/DMontgomery40/mcp-3D-printer-server/blob/main/docs/SLICING.md#bambu-compatible-slicing) |
689
689
  | `enable_timelapse`, `allow_mix_temp` | Insert timelapse parking moves; allow filaments with different temperature needs on one plate |
690
690
 
691
+ `slice_stl` and `slice_with_template` take `quality` (`draft`, `standard`, `fine`) and `strength` (`standard`, `strong`, `max`), so a client can send the person's choice without slicer numbers. The server picks the layer height for the nozzle (0.28 and 0.12 mm on a 0.4 mm nozzle; it scales with `nozzle_diameter`, or `NOZZLE_DIAMETER` when that's not given, which for PrusaSlicer, Slic3r and generic OrcaSlicer must match the nozzle in `slicer_profile`) and the walls and infill (4 walls at 25%, 6 at 40%). Standard keeps the preset; explicit values win. Cura takes only Standard.
692
+
691
693
  `layer_height` (mm), `wall_loops` (a whole number), and `sparse_infill_density` (such as `"15%"`) are written over the selected profile on every slicer: as the process keys `layer_height`, `wall_loops`, and `sparse_infill_density` for Bambu Studio and OrcaSlicer, and as `--layer-height`, `--perimeters`, and `--fill-density` for PrusaSlicer and Slic3r. They win over a template. Cura refuses them; set them in its definition file. Your profile files are never modified.
692
694
 
693
695
  `enable_support` (true or false), `support_type` (`normal(auto)`, `tree(auto)`, `normal(manual)` or `tree(manual)`) and `support_threshold_angle` (0 to 90 degrees; 0 lets the slicer decide) are written over the selected process profile as the same-named Bambu Studio and OrcaSlicer keys. PrusaSlicer, Slic3r and Cura refuse them; set supports in their own profile.
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ import * as THREE from 'three';
19
19
  import { PrinterFactory } from "./printers/printer-factory.js";
20
20
  import { STLManipulator, SUPPORT_TYPES } from "./stl/stl-manipulator.js";
21
21
  import { parsePauseLayers, readPlateGcode, pausedLayers, machinePauseGcode } from "./slicer/layer-pauses.js";
22
+ import { PRINT_QUALITY_PROPERTIES, withPrintQuality } from "./slicer/print-quality.js";
22
23
  import { CounterpartService } from "./counterpart/service.js";
23
24
  import { CAMERA_STREAM_TOOLS } from "./counterpart/camera-tools.js";
24
25
  import { annotateTools } from "./tool-annotations.js";
@@ -306,6 +307,7 @@ const SLICE_TOOL_PROPERTIES = {
306
307
  min_save: { type: "boolean", description: "Bambu-compatible slicing: write a smaller output 3MF (--min-save)." },
307
308
  skip_modified_gcodes: { type: "boolean", description: "Bambu-compatible slicing: ignore custom G-code embedded in an input 3MF (--skip-modified-gcodes)." },
308
309
  slice_plate: { type: "number", description: "Bambu-compatible slicing: plate number to slice; 0 slices all plates (default)." },
310
+ ...PRINT_QUALITY_PROPERTIES,
309
311
  layer_height: {
310
312
  type: "number", exclusiveMinimum: 0, maximum: 2,
311
313
  description: "Layer height in mm, written over the selected process profile (Orca/Bambu Studio layer_height; PrusaSlicer --layer-height). Not mapped for Cura.",
@@ -748,7 +750,7 @@ class ThreeDPrinterMCPServer {
748
750
  if (!args?.stl_path) {
749
751
  throw new Error("Missing required parameter: stl_path");
750
752
  }
751
- const processOverrides = parseProcessOverrides(args);
753
+ const processOverrides = parseProcessOverrides(withPrintQuality(args, Number(args.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER)));
752
754
  const slicerType = normalizeSlicerType(String(args.slicer_type || DEFAULT_SLICER_TYPE));
753
755
  const explicitModel = typeof args.bambu_model === "string" && args.bambu_model.trim() ? args.bambu_model : undefined;
754
756
  // Generic OrcaSlicer keeps its user-supplied machine profile unless the
@@ -0,0 +1,26 @@
1
+ /**
2
+ * slice_stl's `quality` and `strength` (PRD §8.4): a client's print-sheet choice, mapped here to the
3
+ * slicer's own numbers so no client carries layer heights, wall counts or infill. Draft and Fine set
4
+ * the layer height for the nozzle in use (0.28 and 0.12 mm on a 0.4 mm nozzle, scaled with it); Strong
5
+ * and Max set walls and infill. Standard sends nothing: the resolved preset decides. An explicit
6
+ * layer_height, wall_loops or sparse_infill_density in the same call wins over these.
7
+ */
8
+ export declare const PRINT_QUALITIES: readonly ["draft", "standard", "fine"];
9
+ export declare const PRINT_STRENGTHS: readonly ["standard", "strong", "max"];
10
+ export type PrintQuality = (typeof PRINT_QUALITIES)[number];
11
+ export type PrintStrength = (typeof PRINT_STRENGTHS)[number];
12
+ /** The call's arguments with `quality` and `strength` written out as slicer settings. */
13
+ export declare function withPrintQuality(args: Record<string, unknown>, nozzleDiameter?: number): Record<string, unknown>;
14
+ /** slice_stl input-schema properties for both arguments. */
15
+ export declare const PRINT_QUALITY_PROPERTIES: {
16
+ readonly quality: {
17
+ readonly type: "string";
18
+ readonly enum: readonly ["draft", "standard", "fine"];
19
+ readonly description: "Print quality: draft (thicker layers, faster), standard (the preset's own layer height) or fine (thinner layers). The server picks the layer height for the nozzle in use; an explicit layer_height wins.";
20
+ };
21
+ readonly strength: {
22
+ readonly type: "string";
23
+ readonly enum: readonly ["standard", "strong", "max"];
24
+ readonly description: "Part strength: standard (the preset's walls and infill), strong or max (more walls and denser infill). The server picks the values; explicit wall_loops or sparse_infill_density win.";
25
+ };
26
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * slice_stl's `quality` and `strength` (PRD §8.4): a client's print-sheet choice, mapped here to the
3
+ * slicer's own numbers so no client carries layer heights, wall counts or infill. Draft and Fine set
4
+ * the layer height for the nozzle in use (0.28 and 0.12 mm on a 0.4 mm nozzle, scaled with it); Strong
5
+ * and Max set walls and infill. Standard sends nothing: the resolved preset decides. An explicit
6
+ * layer_height, wall_loops or sparse_infill_density in the same call wins over these.
7
+ */
8
+ export const PRINT_QUALITIES = ["draft", "standard", "fine"];
9
+ export const PRINT_STRENGTHS = ["standard", "strong", "max"];
10
+ /** Layer heights on the reference 0.4 mm nozzle; other nozzles scale from it. */
11
+ const REFERENCE_NOZZLE_MM = 0.4;
12
+ const LAYER_MM_ON_REFERENCE = { draft: 0.28, fine: 0.12 };
13
+ const STRENGTH = {
14
+ strong: { wall_loops: 4, sparse_infill_density: "25%" },
15
+ max: { wall_loops: 6, sparse_infill_density: "40%" },
16
+ };
17
+ /** The call's arguments with `quality` and `strength` written out as slicer settings. */
18
+ export function withPrintQuality(args, nozzleDiameter) {
19
+ if (args.quality === undefined && args.strength === undefined)
20
+ return args;
21
+ const quality = args.quality === undefined ? "standard" : args.quality;
22
+ const strength = args.strength === undefined ? "standard" : args.strength;
23
+ if (typeof quality !== "string" || !PRINT_QUALITIES.includes(quality)) {
24
+ throw new Error(`quality must be one of ${PRINT_QUALITIES.join(", ")}.`);
25
+ }
26
+ if (typeof strength !== "string" || !PRINT_STRENGTHS.includes(strength)) {
27
+ throw new Error(`strength must be one of ${PRINT_STRENGTHS.join(", ")}.`);
28
+ }
29
+ const out = { ...args };
30
+ const scale = typeof nozzleDiameter === "number" && Number.isFinite(nozzleDiameter) && nozzleDiameter > 0 ? nozzleDiameter / REFERENCE_NOZZLE_MM : 1;
31
+ const layer = LAYER_MM_ON_REFERENCE[quality];
32
+ if (layer !== undefined && out.layer_height === undefined)
33
+ out.layer_height = Math.round(layer * scale * 100) / 100;
34
+ const walls = STRENGTH[strength];
35
+ if (walls) {
36
+ if (out.wall_loops === undefined)
37
+ out.wall_loops = walls.wall_loops;
38
+ if (out.sparse_infill_density === undefined)
39
+ out.sparse_infill_density = walls.sparse_infill_density;
40
+ }
41
+ return out;
42
+ }
43
+ /** slice_stl input-schema properties for both arguments. */
44
+ export const PRINT_QUALITY_PROPERTIES = {
45
+ quality: {
46
+ type: "string", enum: [...PRINT_QUALITIES],
47
+ description: "Print quality: draft (thicker layers, faster), standard (the preset's own layer height) or fine (thinner layers). The server picks the layer height for the nozzle in use; an explicit layer_height wins.",
48
+ },
49
+ strength: {
50
+ type: "string", enum: [...PRINT_STRENGTHS],
51
+ description: "Part strength: standard (the preset's walls and infill), strong or max (more walls and denser infill). The server picks the values; explicit wall_loops or sparse_infill_density win.",
52
+ },
53
+ };
@@ -1019,7 +1019,7 @@ export class STLManipulator extends EventEmitter {
1019
1019
  break;
1020
1020
  case 'cura':
1021
1021
  if (Object.keys(orcaProcessOverrides(processOverrides)).length) {
1022
- throw new SlicerError('preparation', 'layer_height, wall_loops, sparse_infill_density and the support settings are not mapped for Cura; set them in the Cura definition passed as slicer_profile.', { slicerType, slicerPath });
1022
+ throw new SlicerError('preparation', 'quality, strength, layer_height, wall_loops, sparse_infill_density and the support settings are not mapped for Cura; set them in the Cura definition passed as slicer_profile.', { slicerType, slicerPath });
1023
1023
  }
1024
1024
  // CuraEngine CLI args are different, often requiring -s for settings
1025
1025
  // Example: curaengine slice -v -j cura_settings.json -s layer_height=0.2 -o output.gcode -l input.stl
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-3d-printer-server",
3
3
  "mcpName": "io.github.DMontgomery40/mcp-3d-printer-server",
4
- "version": "1.2.29",
4
+ "version": "1.2.30",
5
5
  "description": "MCP server for connecting Claude with 3D printer management systems",
6
6
  "main": "dist/index.js",
7
7
  "repository": "https://github.com/DMontgomery40/mcp-3D-printer-server",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/DMontgomery40/mcp-3D-printer-server",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.2.29",
10
+ "version": "1.2.30",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "mcp-3d-printer-server",
15
- "version": "1.2.29",
15
+ "version": "1.2.30",
16
16
  "runtimeHint": "npx",
17
17
  "transport": {
18
18
  "type": "stdio"
package/src/index.ts CHANGED
@@ -36,6 +36,7 @@ import * as THREE from 'three';
36
36
  import { PrinterFactory } from "./printers/printer-factory.js";
37
37
  import { STLManipulator, SUPPORT_TYPES, type BambuSliceOptions, type ProcessOverrides, type SliceOutput } from "./stl/stl-manipulator.js";
38
38
  import { parsePauseLayers, readPlateGcode, pausedLayers, machinePauseGcode } from "./slicer/layer-pauses.js";
39
+ import { PRINT_QUALITY_PROPERTIES, withPrintQuality } from "./slicer/print-quality.js";
39
40
  import { CounterpartService, type BambuControlRoute, type PrinterTarget } from "./counterpart/service.js";
40
41
  import {CAMERA_STREAM_TOOLS} from "./counterpart/camera-tools.js";
41
42
  import { annotateTools } from "./tool-annotations.js";
@@ -384,6 +385,7 @@ const SLICE_TOOL_PROPERTIES = {
384
385
  min_save: { type: "boolean", description: "Bambu-compatible slicing: write a smaller output 3MF (--min-save)." },
385
386
  skip_modified_gcodes: { type: "boolean", description: "Bambu-compatible slicing: ignore custom G-code embedded in an input 3MF (--skip-modified-gcodes)." },
386
387
  slice_plate: { type: "number", description: "Bambu-compatible slicing: plate number to slice; 0 slices all plates (default)." },
388
+ ...PRINT_QUALITY_PROPERTIES,
387
389
  layer_height: {
388
390
  type: "number", exclusiveMinimum: 0, maximum: 2,
389
391
  description: "Layer height in mm, written over the selected process profile (Orca/Bambu Studio layer_height; PrusaSlicer --layer-height). Not mapped for Cura.",
@@ -904,7 +906,7 @@ class ThreeDPrinterMCPServer {
904
906
  if (!args?.stl_path) {
905
907
  throw new Error("Missing required parameter: stl_path");
906
908
  }
907
- const processOverrides = parseProcessOverrides(args);
909
+ const processOverrides = parseProcessOverrides(withPrintQuality(args, Number(args.nozzle_diameter || DEFAULT_NOZZLE_DIAMETER)));
908
910
  const slicerType = normalizeSlicerType(String(args.slicer_type || DEFAULT_SLICER_TYPE));
909
911
  const explicitModel =
910
912
  typeof args.bambu_model === "string" && args.bambu_model.trim() ? args.bambu_model : undefined;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * slice_stl's `quality` and `strength` (PRD §8.4): a client's print-sheet choice, mapped here to the
3
+ * slicer's own numbers so no client carries layer heights, wall counts or infill. Draft and Fine set
4
+ * the layer height for the nozzle in use (0.28 and 0.12 mm on a 0.4 mm nozzle, scaled with it); Strong
5
+ * and Max set walls and infill. Standard sends nothing: the resolved preset decides. An explicit
6
+ * layer_height, wall_loops or sparse_infill_density in the same call wins over these.
7
+ */
8
+ export const PRINT_QUALITIES = ["draft", "standard", "fine"] as const;
9
+ export const PRINT_STRENGTHS = ["standard", "strong", "max"] as const;
10
+
11
+ export type PrintQuality = (typeof PRINT_QUALITIES)[number];
12
+ export type PrintStrength = (typeof PRINT_STRENGTHS)[number];
13
+
14
+ /** Layer heights on the reference 0.4 mm nozzle; other nozzles scale from it. */
15
+ const REFERENCE_NOZZLE_MM = 0.4;
16
+ const LAYER_MM_ON_REFERENCE: Partial<Record<PrintQuality, number>> = { draft: 0.28, fine: 0.12 };
17
+ const STRENGTH: Partial<Record<PrintStrength, { wall_loops: number; sparse_infill_density: string }>> = {
18
+ strong: { wall_loops: 4, sparse_infill_density: "25%" },
19
+ max: { wall_loops: 6, sparse_infill_density: "40%" },
20
+ };
21
+
22
+ /** The call's arguments with `quality` and `strength` written out as slicer settings. */
23
+ export function withPrintQuality(args: Record<string, unknown>, nozzleDiameter?: number): Record<string, unknown> {
24
+ if (args.quality === undefined && args.strength === undefined) return args;
25
+ const quality = args.quality === undefined ? "standard" : args.quality;
26
+ const strength = args.strength === undefined ? "standard" : args.strength;
27
+ if (typeof quality !== "string" || !(PRINT_QUALITIES as readonly string[]).includes(quality)) {
28
+ throw new Error(`quality must be one of ${PRINT_QUALITIES.join(", ")}.`);
29
+ }
30
+ if (typeof strength !== "string" || !(PRINT_STRENGTHS as readonly string[]).includes(strength)) {
31
+ throw new Error(`strength must be one of ${PRINT_STRENGTHS.join(", ")}.`);
32
+ }
33
+ const out: Record<string, unknown> = { ...args };
34
+ const scale = typeof nozzleDiameter === "number" && Number.isFinite(nozzleDiameter) && nozzleDiameter > 0 ? nozzleDiameter / REFERENCE_NOZZLE_MM : 1;
35
+ const layer = LAYER_MM_ON_REFERENCE[quality as PrintQuality];
36
+ if (layer !== undefined && out.layer_height === undefined) out.layer_height = Math.round(layer * scale * 100) / 100;
37
+ const walls = STRENGTH[strength as PrintStrength];
38
+ if (walls) {
39
+ if (out.wall_loops === undefined) out.wall_loops = walls.wall_loops;
40
+ if (out.sparse_infill_density === undefined) out.sparse_infill_density = walls.sparse_infill_density;
41
+ }
42
+ return out;
43
+ }
44
+
45
+ /** slice_stl input-schema properties for both arguments. */
46
+ export const PRINT_QUALITY_PROPERTIES = {
47
+ quality: {
48
+ type: "string", enum: [...PRINT_QUALITIES],
49
+ description: "Print quality: draft (thicker layers, faster), standard (the preset's own layer height) or fine (thinner layers). The server picks the layer height for the nozzle in use; an explicit layer_height wins.",
50
+ },
51
+ strength: {
52
+ type: "string", enum: [...PRINT_STRENGTHS],
53
+ description: "Part strength: standard (the preset's walls and infill), strong or max (more walls and denser infill). The server picks the values; explicit wall_loops or sparse_infill_density win.",
54
+ },
55
+ } as const;
@@ -1317,7 +1317,7 @@ export class STLManipulator extends EventEmitter {
1317
1317
  case 'cura':
1318
1318
  if (Object.keys(orcaProcessOverrides(processOverrides)).length) {
1319
1319
  throw new SlicerError('preparation',
1320
- 'layer_height, wall_loops, sparse_infill_density and the support settings are not mapped for Cura; set them in the Cura definition passed as slicer_profile.',
1320
+ 'quality, strength, layer_height, wall_loops, sparse_infill_density and the support settings are not mapped for Cura; set them in the Cura definition passed as slicer_profile.',
1321
1321
  { slicerType, slicerPath });
1322
1322
  }
1323
1323
  // CuraEngine CLI args are different, often requiring -s for settings