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.
- package/README.md +267 -29
- package/dist/3mf_parser.d.ts +3 -0
- package/dist/3mf_parser.js +216 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1018 -0
- package/dist/index.js.map +1 -0
- package/dist/printers/bambu.d.ts +42 -0
- package/dist/printers/bambu.js +278 -0
- package/dist/printers/creality.d.ts +10 -0
- package/dist/printers/creality.js +90 -0
- package/dist/printers/duet.d.ts +10 -0
- package/dist/printers/duet.js +66 -0
- package/dist/printers/klipper.d.ts +10 -0
- package/dist/printers/klipper.js +62 -0
- package/dist/printers/octoprint.d.ts +10 -0
- package/dist/printers/octoprint.js +97 -0
- package/dist/printers/printer-factory.d.ts +9 -0
- package/dist/printers/printer-factory.js +33 -0
- package/dist/printers/prusa.d.ts +10 -0
- package/dist/printers/prusa.js +93 -0
- package/dist/printers/repetier.d.ts +10 -0
- package/dist/printers/repetier.js +59 -0
- package/dist/stl/stl-manipulator.d.ts +140 -0
- package/dist/stl/stl-manipulator.js +1081 -0
- package/dist/test/stl-manipulator-test.d.ts +2 -0
- package/dist/test/stl-manipulator-test.js +91 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +26 -0
- package/package.json +14 -4
- package/src/3mf_parser.ts +223 -0
- package/src/index.ts +239 -10
- package/src/printers/bambu.ts +305 -100
- package/src/stl/stl-manipulator.ts +350 -117
- package/src/test/3DBenchy.3mf +0 -0
- package/src/test/stl-manipulator-test.js +114 -0
- package/src/test/stl-manipulator-test.ts +124 -0
- package/src/test/tsconfig.json +13 -0
- package/src/types.ts +65 -0
- package/.env.example +0 -17
- package/scripts/mcp-inspector.js +0 -49
- 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
|
-
|
|
499
|
-
|
|
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
|
-
|
|
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,
|
|
1205
|
+
host, port, apiKey, bambuSerial, bambuToken, gcodeFilename
|
|
984
1206
|
);
|
|
985
1207
|
}
|
|
986
1208
|
|
|
987
|
-
return await implementation.startJob(host, port, apiKey,
|
|
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
|
-
|
|
1023
|
-
|
|
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
|
|
1256
|
+
return implementation.setTemperature(host, port, apiKey, component, temperature);
|
|
1028
1257
|
}
|
|
1029
1258
|
|
|
1030
1259
|
async run() {
|