mcp-3d-printer-server 1.2.1 → 1.2.4
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 +257 -75
- package/dist/fulu-orca.d.ts +20 -0
- package/dist/fulu-orca.js +453 -0
- package/dist/index.js +572 -72
- package/dist/printers/prusa.d.ts +3 -0
- package/dist/printers/prusa.js +33 -1
- package/dist/stl/stl-manipulator.d.ts +11 -2
- package/dist/stl/stl-manipulator.js +162 -26
- package/package.json +23 -3
- package/src/fulu-orca.ts +602 -0
- package/src/index.ts +642 -72
- package/src/printers/prusa.ts +38 -1
- package/src/stl/stl-manipulator.ts +217 -29
- package/dist/http-server.d.ts +0 -16
- package/dist/http-server.js +0 -268
- package/dist/sse-server.d.ts +0 -20
- package/dist/sse-server.js +0 -318
- package/src/http-server.ts +0 -328
- package/src/sse-server.ts +0 -409
- package/src/test/Tape+Card+55mm+Ring.stl +0 -0
package/src/printers/prusa.ts
CHANGED
|
@@ -11,13 +11,49 @@ type RequestCandidate = {
|
|
|
11
11
|
export class PrusaImplementation extends PrinterImplementation {
|
|
12
12
|
private buildAuthHeaders(apiKey: string): Record<string, string> {
|
|
13
13
|
return {
|
|
14
|
+
Accept: "application/json",
|
|
14
15
|
"X-Api-Key": apiKey,
|
|
15
16
|
Authorization: `Bearer ${apiKey}`,
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
private buildBaseUrl(host: string, port: string): string {
|
|
20
|
-
|
|
21
|
+
const trimmedHost = host.trim();
|
|
22
|
+
const trimmedPort = port.trim();
|
|
23
|
+
if (!trimmedHost) {
|
|
24
|
+
throw new Error("Prusa host is required.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const rawBaseUrl = /^[a-z][a-z\d+.-]*:\/\//i.test(trimmedHost)
|
|
28
|
+
? trimmedHost
|
|
29
|
+
: `${this.defaultProtocolFor(trimmedHost, trimmedPort)}://${trimmedHost}`;
|
|
30
|
+
const parsed = new URL(rawBaseUrl);
|
|
31
|
+
|
|
32
|
+
if (trimmedPort && !parsed.port && !this.isDefaultPort(parsed.protocol, trimmedPort)) {
|
|
33
|
+
parsed.port = trimmedPort;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return parsed.toString().replace(/\/$/, "");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private defaultProtocolFor(host: string, port: string): "http" | "https" {
|
|
40
|
+
if (port === "443" || this.isPrusaConnectCloudHost(host)) {
|
|
41
|
+
return "https";
|
|
42
|
+
}
|
|
43
|
+
return "http";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private isDefaultPort(protocol: string, port: string): boolean {
|
|
47
|
+
return (protocol === "https:" && port === "443") || (protocol === "http:" && port === "80");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private isPrusaConnectCloudHost(host: string): boolean {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = new URL(/^[a-z][a-z\d+.-]*:\/\//i.test(host) ? host : `https://${host}`);
|
|
53
|
+
return parsed.hostname.toLowerCase() === "connect.prusa3d.com";
|
|
54
|
+
} catch {
|
|
55
|
+
return host.toLowerCase().split(":")[0] === "connect.prusa3d.com";
|
|
56
|
+
}
|
|
21
57
|
}
|
|
22
58
|
|
|
23
59
|
private isFallbackStatus(error: unknown): boolean {
|
|
@@ -86,6 +122,7 @@ export class PrusaImplementation extends PrinterImplementation {
|
|
|
86
122
|
|
|
87
123
|
async getStatus(host: string, port: string, apiKey: string) {
|
|
88
124
|
const response = await this.getWithFallback(host, port, apiKey, [
|
|
125
|
+
"/api/v1/status",
|
|
89
126
|
"/api/v1/printer",
|
|
90
127
|
"/api/printer",
|
|
91
128
|
]);
|
|
@@ -11,6 +11,7 @@ import { execFile } from 'child_process';
|
|
|
11
11
|
|
|
12
12
|
const readFileAsync = promisify(fs.readFile);
|
|
13
13
|
const writeFileAsync = promisify(fs.writeFile);
|
|
14
|
+
const DEFAULT_SLICER_TIMEOUT_MS = 10 * 60 * 1000;
|
|
14
15
|
|
|
15
16
|
// Define types for progress tracking
|
|
16
17
|
export type ProgressCallback = (progress: number, message?: string) => void;
|
|
@@ -21,6 +22,14 @@ export type OperationResult = {
|
|
|
21
22
|
operationId: string;
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
export type SlicerType =
|
|
26
|
+
| 'prusaslicer'
|
|
27
|
+
| 'cura'
|
|
28
|
+
| 'slic3r'
|
|
29
|
+
| 'orcaslicer'
|
|
30
|
+
| 'orcaslicer-bambulab'
|
|
31
|
+
| 'bambustudio';
|
|
32
|
+
|
|
24
33
|
// Define possible transformation operations
|
|
25
34
|
export type TransformationType = 'scale' | 'rotate' | 'translate' | 'extendBase' | 'customModify';
|
|
26
35
|
export type TransformationAxis = 'x' | 'y' | 'z' | 'all';
|
|
@@ -60,6 +69,138 @@ export class STLManipulator extends EventEmitter {
|
|
|
60
69
|
return crypto.randomUUID();
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
private validateSlicerExecutable(slicerPath: string): void {
|
|
73
|
+
if (!slicerPath.trim()) {
|
|
74
|
+
throw new Error("Slicer executable path is required.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const looksLikePath = path.isAbsolute(slicerPath) || /[\\/]/.test(slicerPath);
|
|
78
|
+
if (looksLikePath && !fs.existsSync(slicerPath)) {
|
|
79
|
+
throw new Error(`Slicer executable not found at: ${slicerPath}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private splitProfileList(profileList?: string): string[] {
|
|
84
|
+
return (profileList || "")
|
|
85
|
+
.split(";")
|
|
86
|
+
.map((profile) => profile.trim())
|
|
87
|
+
.filter((profile) => profile.length > 0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private parseOrcaProfileArgs(
|
|
91
|
+
slicerProfile?: string,
|
|
92
|
+
filamentProfile?: string
|
|
93
|
+
): { settingsProfiles: string[]; filamentProfiles: string[] } {
|
|
94
|
+
const [settingsSegment = "", filamentSegment = ""] = (slicerProfile || "").split("|", 2);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
settingsProfiles: this.splitProfileList(settingsSegment),
|
|
98
|
+
filamentProfiles: [
|
|
99
|
+
...this.splitProfileList(filamentSegment),
|
|
100
|
+
...this.splitProfileList(filamentProfile),
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private warnForMissingProfiles(profiles: string[], label: string): void {
|
|
106
|
+
for (const profile of profiles) {
|
|
107
|
+
if (!fs.existsSync(profile)) {
|
|
108
|
+
console.warn(`${label} specified but not found: ${profile}. Slicer might use defaults.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private appendOrcaProfileArgs(
|
|
114
|
+
args: string[],
|
|
115
|
+
profiles: { settingsProfiles: string[]; filamentProfiles: string[] }
|
|
116
|
+
): void {
|
|
117
|
+
for (const profile of profiles.settingsProfiles) {
|
|
118
|
+
args.push('--load-settings', profile);
|
|
119
|
+
}
|
|
120
|
+
for (const profile of profiles.filamentProfiles) {
|
|
121
|
+
args.push('--load-filaments', profile);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private resolveOrcaGcodeOutput(outputDir: string): string {
|
|
126
|
+
const preferredPath = path.join(outputDir, 'plate_1.gcode');
|
|
127
|
+
if (fs.existsSync(preferredPath)) {
|
|
128
|
+
return preferredPath;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const plateOutputs = fs.readdirSync(outputDir)
|
|
132
|
+
.filter((entry) => /^plate_\d+\.gcode$/i.test(entry))
|
|
133
|
+
.sort((a, b) => {
|
|
134
|
+
const aNumber = Number(a.match(/\d+/)?.[0] || 0);
|
|
135
|
+
const bNumber = Number(b.match(/\d+/)?.[0] || 0);
|
|
136
|
+
return aNumber - bNumber;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (plateOutputs.length === 0) {
|
|
140
|
+
throw new Error(`OrcaSlicer finished but no plate_N.gcode file was found in: ${outputDir}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return path.join(outputDir, plateOutputs[0]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private resolveBambuMachinePresetPath(slicerPath: string, printerPreset: string): string {
|
|
147
|
+
if (fs.existsSync(printerPreset)) {
|
|
148
|
+
return printerPreset;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const presetFile = printerPreset.endsWith(".json") ? printerPreset : `${printerPreset}.json`;
|
|
152
|
+
const candidateDirs: string[] = [];
|
|
153
|
+
const macAppMarker = `${path.sep}Contents${path.sep}MacOS${path.sep}`;
|
|
154
|
+
const macAppIndex = slicerPath.indexOf(macAppMarker);
|
|
155
|
+
|
|
156
|
+
if (macAppIndex >= 0) {
|
|
157
|
+
candidateDirs.push(
|
|
158
|
+
path.join(
|
|
159
|
+
slicerPath.slice(0, macAppIndex + `${path.sep}Contents`.length),
|
|
160
|
+
"Resources",
|
|
161
|
+
"profiles",
|
|
162
|
+
"BBL",
|
|
163
|
+
"machine"
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const executableDir = path.dirname(slicerPath);
|
|
169
|
+
candidateDirs.push(
|
|
170
|
+
path.join(executableDir, "..", "Resources", "profiles", "BBL", "machine"),
|
|
171
|
+
path.join(executableDir, "..", "share", "OrcaSlicer", "resources", "profiles", "BBL", "machine"),
|
|
172
|
+
path.join(executableDir, "..", "resources", "profiles", "BBL", "machine"),
|
|
173
|
+
path.join(executableDir, "resources", "profiles", "BBL", "machine")
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
for (const candidateDir of candidateDirs) {
|
|
177
|
+
const candidate = path.resolve(candidateDir, presetFile);
|
|
178
|
+
if (fs.existsSync(candidate)) {
|
|
179
|
+
return candidate;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Could not find Bambu machine preset "${presetFile}" near slicer executable ${slicerPath}. ` +
|
|
185
|
+
"Pass slicer_profile with an explicit machine/process settings file, or use a slicer bundle that includes Resources/profiles/BBL/machine."
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private resolveSlicerTimeoutMs(): number {
|
|
190
|
+
const rawTimeout = process.env.SLICER_TIMEOUT_MS?.trim();
|
|
191
|
+
if (!rawTimeout) {
|
|
192
|
+
return DEFAULT_SLICER_TIMEOUT_MS;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const timeoutMs = Number(rawTimeout);
|
|
196
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
197
|
+
console.warn(`Invalid SLICER_TIMEOUT_MS value "${rawTimeout}", using ${DEFAULT_SLICER_TIMEOUT_MS}.`);
|
|
198
|
+
return DEFAULT_SLICER_TIMEOUT_MS;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return timeoutMs;
|
|
202
|
+
}
|
|
203
|
+
|
|
63
204
|
/**
|
|
64
205
|
* Load STL file and return geometry and bounding box
|
|
65
206
|
*/
|
|
@@ -845,7 +986,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
845
986
|
/**
|
|
846
987
|
* Slice an STL or 3MF file using the specified slicer
|
|
847
988
|
* @param stlFilePath Path to the input STL or 3MF file
|
|
848
|
-
* @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, bambustudio)
|
|
989
|
+
* @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, orcaslicer-bambulab, bambustudio)
|
|
849
990
|
* @param slicerPath Path to the slicer executable
|
|
850
991
|
* @param slicerProfile Optional path to the slicer profile/config file
|
|
851
992
|
* @param progressCallback Optional callback for progress updates
|
|
@@ -853,25 +994,32 @@ export class STLManipulator extends EventEmitter {
|
|
|
853
994
|
*/
|
|
854
995
|
async sliceSTL(
|
|
855
996
|
stlFilePath: string,
|
|
856
|
-
slicerType:
|
|
997
|
+
slicerType: SlicerType,
|
|
857
998
|
slicerPath: string,
|
|
858
999
|
slicerProfile?: string,
|
|
859
1000
|
progressCallback?: ProgressCallback,
|
|
860
|
-
printerPreset?: string
|
|
1001
|
+
printerPreset?: string,
|
|
1002
|
+
filamentProfile?: string
|
|
861
1003
|
): Promise<string> {
|
|
862
1004
|
const operationId = this.generateOperationId();
|
|
863
1005
|
this.activeOperations.set(operationId, true);
|
|
864
1006
|
|
|
865
|
-
|
|
866
|
-
|
|
1007
|
+
this.validateSlicerExecutable(slicerPath);
|
|
1008
|
+
const orcaProfiles = this.parseOrcaProfileArgs(slicerProfile, filamentProfile);
|
|
1009
|
+
if (slicerType === 'orcaslicer') {
|
|
1010
|
+
this.warnForMissingProfiles(orcaProfiles.settingsProfiles, 'Slicer profile');
|
|
1011
|
+
this.warnForMissingProfiles(orcaProfiles.filamentProfiles, 'Filament profile');
|
|
1012
|
+
} else if (slicerProfile && !fs.existsSync(slicerProfile)) {
|
|
1013
|
+
console.warn(`Slicer profile specified but not found: ${slicerProfile}. Slicer might use defaults.`);
|
|
1014
|
+
// Allow proceeding without profile, slicer might handle it
|
|
867
1015
|
}
|
|
868
|
-
if (
|
|
869
|
-
|
|
870
|
-
// Allow proceeding without profile, slicer might handle it
|
|
1016
|
+
if (slicerType !== 'orcaslicer' && filamentProfile && !fs.existsSync(filamentProfile)) {
|
|
1017
|
+
console.warn(`Filament profile specified but not found: ${filamentProfile}. Slicer might use defaults.`);
|
|
871
1018
|
}
|
|
872
1019
|
|
|
873
1020
|
const outputFileName = path.basename(stlFilePath, '.stl') + '.gcode';
|
|
874
1021
|
let outputFilePath = path.join(this.tempDir, outputFileName);
|
|
1022
|
+
let orcaOutputDir: string | undefined;
|
|
875
1023
|
|
|
876
1024
|
let args: string[] = [];
|
|
877
1025
|
|
|
@@ -892,22 +1040,14 @@ export class STLManipulator extends EventEmitter {
|
|
|
892
1040
|
break;
|
|
893
1041
|
|
|
894
1042
|
case 'orcaslicer': // Add OrcaSlicer case
|
|
1043
|
+
orcaOutputDir = path.join(this.tempDir, `${operationId}-orca-output`);
|
|
1044
|
+
fs.mkdirSync(orcaOutputDir, { recursive: true });
|
|
895
1045
|
args = [
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
// Also assuming profile path points to the main .ini or .json config.
|
|
899
|
-
'--load-settings', slicerProfile || '',
|
|
900
|
-
'--output', outputFilePath, // Assuming --output works like PrusaSlicer
|
|
901
|
-
stlFilePath
|
|
1046
|
+
'--slice', '0',
|
|
1047
|
+
'--outputdir', orcaOutputDir,
|
|
902
1048
|
];
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
// '--load-settings', slicerProfile || '',
|
|
906
|
-
// '--outputdir', this.tempDir,
|
|
907
|
-
// stlFilePath
|
|
908
|
-
// ];
|
|
909
|
-
// Remove empty profile arg if not provided
|
|
910
|
-
args = args.filter(arg => arg !== '');
|
|
1049
|
+
this.appendOrcaProfileArgs(args, orcaProfiles);
|
|
1050
|
+
args.push(stlFilePath);
|
|
911
1051
|
break;
|
|
912
1052
|
|
|
913
1053
|
case 'cura':
|
|
@@ -925,8 +1065,10 @@ export class STLManipulator extends EventEmitter {
|
|
|
925
1065
|
}
|
|
926
1066
|
break;
|
|
927
1067
|
|
|
1068
|
+
case 'orcaslicer-bambulab':
|
|
928
1069
|
case 'bambustudio':
|
|
929
|
-
// Bambu
|
|
1070
|
+
// Bambu project slicers: slice and export as 3MF with embedded gcode.
|
|
1071
|
+
// orcaslicer-bambulab is the FULU fork that restores BambuNetwork support.
|
|
930
1072
|
// For 3MF input: slice in place and export sliced 3MF
|
|
931
1073
|
// For STL input: slice and export as 3MF
|
|
932
1074
|
{
|
|
@@ -937,13 +1079,16 @@ export class STLManipulator extends EventEmitter {
|
|
|
937
1079
|
'--slice', '0', // Slice all plates
|
|
938
1080
|
'--export-3mf', bambuOutputPath,
|
|
939
1081
|
];
|
|
940
|
-
//
|
|
1082
|
+
// FULU Orca/Bambu Studio load machine presets as JSON settings files.
|
|
941
1083
|
if (printerPreset && !slicerProfile) {
|
|
942
|
-
args.push('--load-
|
|
1084
|
+
args.push('--load-settings', this.resolveBambuMachinePresetPath(slicerPath, printerPreset));
|
|
943
1085
|
}
|
|
944
1086
|
if (slicerProfile) {
|
|
945
1087
|
args.push('--load-settings', slicerProfile);
|
|
946
1088
|
}
|
|
1089
|
+
if (filamentProfile) {
|
|
1090
|
+
args.push('--load-filaments', filamentProfile);
|
|
1091
|
+
}
|
|
947
1092
|
args.push(stlFilePath);
|
|
948
1093
|
// Override outputFilePath for bambustudio since it produces 3MF, not gcode
|
|
949
1094
|
outputFilePath = bambuOutputPath;
|
|
@@ -959,20 +1104,49 @@ export class STLManipulator extends EventEmitter {
|
|
|
959
1104
|
|
|
960
1105
|
// Execute the slicer
|
|
961
1106
|
await new Promise<void>((resolve, reject) => {
|
|
1107
|
+
const timeoutMs = this.resolveSlicerTimeoutMs();
|
|
1108
|
+
let settled = false;
|
|
1109
|
+
let slicerProcessKilledByTimeout = false;
|
|
1110
|
+
let timeoutHandle: NodeJS.Timeout | undefined;
|
|
1111
|
+
|
|
1112
|
+
const settle = (callback: () => void) => {
|
|
1113
|
+
if (settled) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
settled = true;
|
|
1117
|
+
if (timeoutHandle) {
|
|
1118
|
+
clearTimeout(timeoutHandle);
|
|
1119
|
+
}
|
|
1120
|
+
callback();
|
|
1121
|
+
};
|
|
1122
|
+
|
|
962
1123
|
const process = execFile(slicerPath, args, (error, stdout, stderr) => {
|
|
963
1124
|
if (error) {
|
|
964
1125
|
console.error(`Slicer Error: ${error.message}`);
|
|
965
1126
|
console.error(`Slicer Stderr: ${stderr}`);
|
|
966
|
-
|
|
1127
|
+
const timeoutMessage = slicerProcessKilledByTimeout
|
|
1128
|
+
? ` Slicer exceeded timeout ${timeoutMs}ms.`
|
|
1129
|
+
: "";
|
|
1130
|
+
settle(() => reject(new Error(`Slicer failed: ${error.message}.${timeoutMessage} Stderr: ${stderr}`)));
|
|
967
1131
|
} else {
|
|
968
1132
|
console.log(`Slicer Stdout: ${stdout}`);
|
|
969
1133
|
if (stderr) {
|
|
970
1134
|
console.warn(`Slicer Stderr: ${stderr}`); // Log stderr even on success
|
|
971
1135
|
}
|
|
972
|
-
resolve();
|
|
1136
|
+
settle(() => resolve());
|
|
973
1137
|
}
|
|
974
1138
|
});
|
|
975
1139
|
|
|
1140
|
+
timeoutHandle = setTimeout(() => {
|
|
1141
|
+
slicerProcessKilledByTimeout = true;
|
|
1142
|
+
try {
|
|
1143
|
+
process.kill("SIGKILL");
|
|
1144
|
+
} catch (killError) {
|
|
1145
|
+
console.error("Error attempting to kill timed-out slicer process:", killError);
|
|
1146
|
+
}
|
|
1147
|
+
settle(() => reject(new Error(`Slicer exceeded timeout ${timeoutMs}ms.`)));
|
|
1148
|
+
}, timeoutMs);
|
|
1149
|
+
|
|
976
1150
|
// Optional: Add listeners for stdout/stderr for real-time progress if slicer provides it
|
|
977
1151
|
// process.stdout?.on('data', (data) => { console.log(`Slicer stdout: ${data}`); });
|
|
978
1152
|
// process.stderr?.on('data', (data) => { console.log(`Slicer stderr: ${data}`); });
|
|
@@ -983,10 +1157,10 @@ export class STLManipulator extends EventEmitter {
|
|
|
983
1157
|
clearInterval(checkCancel);
|
|
984
1158
|
try {
|
|
985
1159
|
process.kill(); // Attempt to kill the slicer process
|
|
986
|
-
reject(new Error("Slicing operation cancelled"));
|
|
1160
|
+
settle(() => reject(new Error("Slicing operation cancelled")));
|
|
987
1161
|
} catch (killError) {
|
|
988
1162
|
console.error("Error attempting to kill slicer process:", killError);
|
|
989
|
-
reject(new Error("Slicing operation cancelled, but failed to kill process."));
|
|
1163
|
+
settle(() => reject(new Error("Slicing operation cancelled, but failed to kill process.")));
|
|
990
1164
|
}
|
|
991
1165
|
}
|
|
992
1166
|
}, 500);
|
|
@@ -1000,6 +1174,17 @@ export class STLManipulator extends EventEmitter {
|
|
|
1000
1174
|
// This check might be redundant if the promise rejected on kill, but good safety
|
|
1001
1175
|
throw new Error("Slicing operation cancelled after process finished");
|
|
1002
1176
|
}
|
|
1177
|
+
|
|
1178
|
+
if (slicerType === 'orcaslicer') {
|
|
1179
|
+
if (!orcaOutputDir) {
|
|
1180
|
+
throw new Error("Internal error: OrcaSlicer output directory was not initialized.");
|
|
1181
|
+
}
|
|
1182
|
+
const orcaGcodePath = this.resolveOrcaGcodeOutput(orcaOutputDir);
|
|
1183
|
+
if (fs.existsSync(outputFilePath)) {
|
|
1184
|
+
fs.unlinkSync(outputFilePath);
|
|
1185
|
+
}
|
|
1186
|
+
fs.renameSync(orcaGcodePath, outputFilePath);
|
|
1187
|
+
}
|
|
1003
1188
|
|
|
1004
1189
|
if (!fs.existsSync(outputFilePath)) {
|
|
1005
1190
|
throw new Error(`Slicer finished but output file not found: ${outputFilePath}`);
|
|
@@ -1024,6 +1209,9 @@ export class STLManipulator extends EventEmitter {
|
|
|
1024
1209
|
});
|
|
1025
1210
|
throw error;
|
|
1026
1211
|
} finally {
|
|
1212
|
+
if (orcaOutputDir && fs.existsSync(orcaOutputDir)) {
|
|
1213
|
+
fs.rmSync(orcaOutputDir, { recursive: true, force: true });
|
|
1214
|
+
}
|
|
1027
1215
|
this.activeOperations.delete(operationId);
|
|
1028
1216
|
}
|
|
1029
1217
|
}
|
package/dist/http-server.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
declare class ThreeDPrinterMCPServerHTTP {
|
|
3
|
-
private server;
|
|
4
|
-
private printerFactory;
|
|
5
|
-
private stlManipulator;
|
|
6
|
-
private app;
|
|
7
|
-
constructor();
|
|
8
|
-
private setupExpressApp;
|
|
9
|
-
private handleJSONRPCRequest;
|
|
10
|
-
setupErrorHandling(): void;
|
|
11
|
-
setupHandlers(): void;
|
|
12
|
-
setupResourceHandlers(): void;
|
|
13
|
-
setupToolHandlers(): void;
|
|
14
|
-
run(): Promise<void>;
|
|
15
|
-
}
|
|
16
|
-
export default ThreeDPrinterMCPServerHTTP;
|