mcp-3d-printer-server 0.2.3 → 1.0.1

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.
Files changed (41) hide show
  1. package/README.md +267 -29
  2. package/dist/3mf_parser.d.ts +3 -0
  3. package/dist/3mf_parser.js +216 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +1018 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/printers/bambu.d.ts +42 -0
  8. package/dist/printers/bambu.js +278 -0
  9. package/dist/printers/creality.d.ts +10 -0
  10. package/dist/printers/creality.js +90 -0
  11. package/dist/printers/duet.d.ts +10 -0
  12. package/dist/printers/duet.js +66 -0
  13. package/dist/printers/klipper.d.ts +10 -0
  14. package/dist/printers/klipper.js +62 -0
  15. package/dist/printers/octoprint.d.ts +10 -0
  16. package/dist/printers/octoprint.js +97 -0
  17. package/dist/printers/printer-factory.d.ts +9 -0
  18. package/dist/printers/printer-factory.js +33 -0
  19. package/dist/printers/prusa.d.ts +10 -0
  20. package/dist/printers/prusa.js +93 -0
  21. package/dist/printers/repetier.d.ts +10 -0
  22. package/dist/printers/repetier.js +59 -0
  23. package/dist/stl/stl-manipulator.d.ts +140 -0
  24. package/dist/stl/stl-manipulator.js +1081 -0
  25. package/dist/test/stl-manipulator-test.d.ts +2 -0
  26. package/dist/test/stl-manipulator-test.js +91 -0
  27. package/dist/types.d.ts +75 -0
  28. package/dist/types.js +26 -0
  29. package/package.json +14 -4
  30. package/src/3mf_parser.ts +223 -0
  31. package/src/index.ts +239 -10
  32. package/src/printers/bambu.ts +305 -100
  33. package/src/stl/stl-manipulator.ts +350 -117
  34. package/src/test/3DBenchy.3mf +0 -0
  35. package/src/test/stl-manipulator-test.js +114 -0
  36. package/src/test/stl-manipulator-test.ts +124 -0
  37. package/src/test/tsconfig.json +13 -0
  38. package/src/types.ts +65 -0
  39. package/.env.example +0 -17
  40. package/scripts/mcp-inspector.js +0 -49
  41. package/tsconfig.json +0 -16
package/src/index.ts CHANGED
@@ -17,6 +17,8 @@ import path from "path";
17
17
  import * as THREE from 'three';
18
18
  import { PrinterFactory } from "./printers/printer-factory.js";
19
19
  import { STLManipulator } from "./stl/stl-manipulator.js";
20
+ import { parse3MF, ThreeMFData } from './3mf_parser.js';
21
+ import { BambuImplementation } from "./printers/bambu.js";
20
22
 
21
23
  // Load environment variables from .env file
22
24
  dotenv.config();
@@ -238,7 +240,7 @@ class ThreeDPrinterMCPServer {
238
240
  },
239
241
  slicer_type: {
240
242
  type: "string",
241
- description: "Type of slicer to use (prusaslicer, cura, slic3r) (default: value from env)"
243
+ description: "Type of slicer to use (prusaslicer, cura, slic3r, orcaslicer) (default: value from env)"
242
244
  },
243
245
  slicer_path: {
244
246
  type: "string",
@@ -495,8 +497,87 @@ class ThreeDPrinterMCPServer {
495
497
  required: ["stl_path"]
496
498
  }
497
499
  },
498
- // ...other tools with similar structure
499
- // Abbreviated for clarity, would include all other tools
500
+ {
501
+ name: "print_3mf",
502
+ description: "Print a 3MF file on a Bambu Lab printer, potentially overriding settings.",
503
+ inputSchema: {
504
+ type: "object",
505
+ properties: {
506
+ three_mf_path: {
507
+ type: "string",
508
+ description: "Path to the 3MF file to print."
509
+ },
510
+ host: {
511
+ type: "string",
512
+ description: "Hostname or IP address of the Bambu printer (default: value from env)"
513
+ },
514
+ bambu_serial: {
515
+ type: "string",
516
+ description: "Serial number for the Bambu Lab printer (default: value from env)"
517
+ },
518
+ bambu_token: {
519
+ type: "string",
520
+ description: "Access token for the Bambu Lab printer (default: value from env)"
521
+ },
522
+ layer_height: { type: "number", description: "Override layer height (mm)." },
523
+ nozzle_temperature: { type: "number", description: "Override nozzle temperature (°C)." },
524
+ bed_temperature: { type: "number", description: "Override bed temperature (°C)." },
525
+ support_enabled: { type: "boolean", description: "Override support generation." },
526
+ ams_mapping: {
527
+ type: "object",
528
+ description: "Override AMS filament mapping (e.g., {\"Generic PLA\": 0, \"Generic PETG\": 1}).",
529
+ additionalProperties: { type: "number" }
530
+ }
531
+ },
532
+ required: ["three_mf_path"]
533
+ }
534
+ },
535
+ {
536
+ name: "merge_vertices",
537
+ description: "Merge vertices in an STL file that are closer than the specified tolerance.",
538
+ inputSchema: {
539
+ type: "object",
540
+ properties: {
541
+ stl_path: {
542
+ type: "string",
543
+ description: "Path to the STL file to modify."
544
+ },
545
+ tolerance: {
546
+ type: "number",
547
+ description: "Maximum distance between vertices to merge (in mm, default: 0.01)."
548
+ }
549
+ },
550
+ required: ["stl_path"]
551
+ }
552
+ },
553
+ {
554
+ name: "center_model",
555
+ description: "Translate the model so its geometric center is at the origin (0,0,0).",
556
+ inputSchema: {
557
+ type: "object",
558
+ properties: {
559
+ stl_path: {
560
+ type: "string",
561
+ description: "Path to the STL file to center."
562
+ }
563
+ },
564
+ required: ["stl_path"]
565
+ }
566
+ },
567
+ {
568
+ name: "lay_flat",
569
+ description: "Attempt to rotate the model so its largest flat face lies on the XY plane (Z=0).",
570
+ inputSchema: {
571
+ type: "object",
572
+ properties: {
573
+ stl_path: {
574
+ type: "string",
575
+ description: "Path to the STL file to lay flat."
576
+ }
577
+ },
578
+ required: ["stl_path"]
579
+ }
580
+ }
500
581
  ]
501
582
  };
502
583
  });
@@ -847,6 +928,144 @@ class ThreeDPrinterMCPServer {
847
928
  );
848
929
  break;
849
930
 
931
+ case "print_3mf":
932
+ if (!args?.three_mf_path) {
933
+ throw new Error("Missing required parameter: three_mf_path");
934
+ }
935
+ if (type.toLowerCase() !== 'bambu') {
936
+ throw new Error("The print_3mf tool currently only supports Bambu printers.");
937
+ }
938
+ if (!bambuSerial || !bambuToken) {
939
+ throw new Error("Bambu serial number and access token are required for print_3mf.");
940
+ }
941
+
942
+ const threeMFPath = String(args.three_mf_path);
943
+
944
+ // Define variables needed outside the parse try block
945
+ let implementation: BambuImplementation;
946
+ let threeMfFilename: string;
947
+ let projectName: string;
948
+ let finalAmsMapping: number[] | undefined;
949
+ let useAMS: boolean;
950
+ let printOptions: any; // Use a more specific type later if possible
951
+
952
+ try {
953
+ // --- Parse 3MF ---
954
+ const parsed3MFData = await parse3MF(threeMFPath);
955
+ console.log(`Successfully parsed 3MF file: ${threeMFPath}`);
956
+ let parsedAmsMapping: number[] | undefined = undefined;
957
+ // ... (Extract default AMS mapping logic) ...
958
+ if (parsed3MFData.slicerConfig?.ams_mapping) {
959
+ const slots = Object.values(parsed3MFData.slicerConfig.ams_mapping)
960
+ .filter(v => typeof v === 'number') as number[];
961
+ if (slots.length > 0) {
962
+ parsedAmsMapping = slots.sort((a, b) => a - b);
963
+ console.log("Extracted default AMS mapping from 3MF:", parsedAmsMapping);
964
+ } else {
965
+ console.log("AMS mapping found in 3MF, but no valid slots extracted.");
966
+ }
967
+ } else {
968
+ console.log("No default AMS mapping found in 3MF slicer config.");
969
+ }
970
+
971
+ // --- Gather Overrides and Determine Final Options ---
972
+ finalAmsMapping = parsedAmsMapping; // Start with parsed
973
+ useAMS = args?.use_ams !== undefined ? Boolean(args.use_ams) : (!!finalAmsMapping && finalAmsMapping.length > 0);
974
+ // ... (Process user AMS mapping override logic) ...
975
+ if (args?.ams_mapping) {
976
+ let userMappingOverride: number[] | undefined = undefined;
977
+ if (Array.isArray(args.ams_mapping)) {
978
+ userMappingOverride = args.ams_mapping.filter(v => typeof v === 'number');
979
+ } else if (typeof args.ams_mapping === 'object') {
980
+ userMappingOverride = Object.values(args.ams_mapping)
981
+ .filter(v => typeof v === 'number')
982
+ .sort((a, b) => a - b) as number[];
983
+ }
984
+
985
+ if (userMappingOverride && userMappingOverride.length > 0) {
986
+ console.log("Applying user AMS mapping override:", userMappingOverride);
987
+ finalAmsMapping = userMappingOverride;
988
+ useAMS = true; // Force useAMS if override provided
989
+ } else {
990
+ console.warn("Received ams_mapping override, but it was empty or invalid.");
991
+ }
992
+ }
993
+ // ... (Handle explicit use_ams=false) ...
994
+ if (args?.use_ams === false) {
995
+ console.log("User explicitly disabled AMS.");
996
+ finalAmsMapping = undefined;
997
+ useAMS = false;
998
+ }
999
+ if (!finalAmsMapping || finalAmsMapping.length === 0) {
1000
+ useAMS = false;
1001
+ }
1002
+
1003
+ // --- Prepare Implementation and Print Options ---
1004
+ const factoryImplementation = this.printerFactory.getImplementation('bambu');
1005
+ if (!(factoryImplementation instanceof BambuImplementation)) {
1006
+ throw new Error("Internal error: Could not get Bambu printer implementation.");
1007
+ }
1008
+ implementation = factoryImplementation; // Assign to outer scope variable
1009
+
1010
+ threeMfFilename = path.basename(threeMFPath); // Assign to outer scope variable
1011
+ projectName = threeMfFilename.replace(/\.3mf$/i, ''); // Assign to outer scope variable
1012
+
1013
+ printOptions = { // Assign to outer scope variable
1014
+ useAMS: useAMS,
1015
+ amsMapping: finalAmsMapping,
1016
+ bedLeveling: args?.bed_leveling !== undefined ? Boolean(args.bed_leveling) : undefined,
1017
+ flowCalibration: args?.flow_calibration !== undefined ? Boolean(args.flow_calibration) : undefined,
1018
+ vibrationCalibration: args?.vibration_calibration !== undefined ? Boolean(args.vibration_calibration) : undefined,
1019
+ layerInspect: args?.layer_inspect !== undefined ? Boolean(args.layer_inspect) : undefined,
1020
+ timelapse: args?.timelapse !== undefined ? Boolean(args.timelapse) : undefined,
1021
+ // md5: parsed3MFData?.metadata?.md5
1022
+ };
1023
+
1024
+ } catch (error) { // Catch parsing or setup errors
1025
+ console.error(`Error processing 3MF or setting up print:`, error);
1026
+ throw new Error(`Failed during 3MF processing: ${(error as Error).message}`);
1027
+ }
1028
+
1029
+ // --- Call Implementation (Now variables are in scope) ---
1030
+ try {
1031
+ result = await implementation.print3mf(host, bambuSerial, bambuToken, {
1032
+ projectName: projectName,
1033
+ filePath: threeMFPath,
1034
+ plateIndex: 0,
1035
+ ...printOptions // Spread the final options
1036
+ });
1037
+ result = `Print command for ${threeMfFilename} sent successfully.`;
1038
+ } catch (printError) {
1039
+ console.error(`Error starting 3MF print for ${threeMfFilename}:`, printError);
1040
+ throw new Error(`Failed to start print: ${(printError as Error).message}`);
1041
+ }
1042
+
1043
+ break;
1044
+
1045
+ case "merge_vertices":
1046
+ if (!args?.stl_path) {
1047
+ throw new Error("Missing required parameter: stl_path");
1048
+ }
1049
+ result = await this.stlManipulator.mergeVertices(
1050
+ String(args.stl_path),
1051
+ args.tolerance !== undefined ? Number(args.tolerance) : undefined // Pass tolerance if provided
1052
+ );
1053
+ break;
1054
+
1055
+ case "center_model":
1056
+ if (!args?.stl_path) {
1057
+ throw new Error("Missing required parameter: stl_path");
1058
+ }
1059
+ result = await this.stlManipulator.centerModel(String(args.stl_path));
1060
+ break;
1061
+
1062
+ case "lay_flat":
1063
+ if (!args?.stl_path) {
1064
+ throw new Error("Missing required parameter: stl_path");
1065
+ }
1066
+ result = await this.stlManipulator.layFlat(String(args.stl_path));
1067
+ break;
1068
+
850
1069
  default:
851
1070
  throw new Error(`Unknown tool: ${name}`);
852
1071
  }
@@ -974,17 +1193,20 @@ class ThreeDPrinterMCPServer {
974
1193
  apiKey: string,
975
1194
  bambuSerial: string,
976
1195
  bambuToken: string,
977
- filename: string
1196
+ gcodeFilename: string
978
1197
  ) {
979
1198
  const implementation = this.printerFactory.getImplementation(type);
980
1199
 
981
1200
  if (type.toLowerCase() === "bambu") {
1201
+ // Bambu startJob likely uses G-code filename, not 3MF.
1202
+ // Keep this as is for starting pre-sliced G-code files.
1203
+ // The print_3mf tool handles starting 3MF prints.
982
1204
  return await (implementation as any).startJob(
983
- host, port, apiKey, bambuSerial, bambuToken, filename
1205
+ host, port, apiKey, bambuSerial, bambuToken, gcodeFilename
984
1206
  );
985
1207
  }
986
1208
 
987
- return await implementation.startJob(host, port, apiKey, filename);
1209
+ return await implementation.startJob(host, port, apiKey, gcodeFilename);
988
1210
  }
989
1211
 
990
1212
  async cancelPrint(
@@ -1019,12 +1241,19 @@ class ThreeDPrinterMCPServer {
1019
1241
  const implementation = this.printerFactory.getImplementation(type);
1020
1242
 
1021
1243
  if (type.toLowerCase() === "bambu") {
1022
- return await (implementation as any).setTemperature(
1023
- host, port, apiKey, bambuSerial, bambuToken, component, temperature
1024
- );
1244
+ const bambuImplementation = this.printerFactory.getImplementation('bambu');
1245
+
1246
+ if (bambuImplementation instanceof BambuImplementation && typeof bambuImplementation.setTemperature === 'function') {
1247
+ return await bambuImplementation.setTemperature(
1248
+ host, bambuSerial, bambuToken, component, temperature
1249
+ );
1250
+ } else {
1251
+ console.warn('setTemperature not fully implemented for Bambu via direct commands yet.');
1252
+ return { status: 'Command sent (implementation pending)'}; // Avoid throwing error if method doesn't exist
1253
+ }
1025
1254
  }
1026
1255
 
1027
- return await implementation.setTemperature(host, port, apiKey, component, temperature);
1256
+ return implementation.setTemperature(host, port, apiKey, component, temperature);
1028
1257
  }
1029
1258
 
1030
1259
  async run() {