bambu-printer-mcp 1.0.0
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/LICENSE +339 -0
- package/README.md +85 -0
- 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 +700 -0
- package/dist/printers/bambu.d.ts +50 -0
- package/dist/printers/bambu.js +350 -0
- package/dist/stl/stl-manipulator.d.ts +140 -0
- package/dist/stl/stl-manipulator.js +1102 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +1 -0
- package/package.json +52 -0
- package/src/3mf_parser.ts +223 -0
- package/src/index.ts +830 -0
- package/src/printers/bambu.ts +480 -0
- package/src/stl/stl-manipulator.ts +1377 -0
- package/src/types.ts +58 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
interface BambuPrintOptionsInternal {
|
|
2
|
+
projectName: string;
|
|
3
|
+
filePath: string;
|
|
4
|
+
useAMS?: boolean;
|
|
5
|
+
plateIndex?: number;
|
|
6
|
+
bedLeveling?: boolean;
|
|
7
|
+
flowCalibration?: boolean;
|
|
8
|
+
vibrationCalibration?: boolean;
|
|
9
|
+
layerInspect?: boolean;
|
|
10
|
+
timelapse?: boolean;
|
|
11
|
+
amsMapping?: number[];
|
|
12
|
+
md5?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class BambuImplementation {
|
|
15
|
+
private printerStore;
|
|
16
|
+
constructor();
|
|
17
|
+
private getPrinter;
|
|
18
|
+
private resolveProjectFileMetadata;
|
|
19
|
+
getStatus(host: string, serial: string, token: string): Promise<any>;
|
|
20
|
+
print3mf(host: string, serial: string, token: string, options: BambuPrintOptionsInternal): Promise<any>;
|
|
21
|
+
cancelJob(host: string, serial: string, token: string): Promise<any>;
|
|
22
|
+
setTemperature(host: string, serial: string, token: string, component: string, temperature: number): Promise<{
|
|
23
|
+
status: string;
|
|
24
|
+
message: string;
|
|
25
|
+
command: string;
|
|
26
|
+
}>;
|
|
27
|
+
getFiles(host: string, serial: string, token: string): Promise<{
|
|
28
|
+
files: string[];
|
|
29
|
+
directories: Record<string, string[]>;
|
|
30
|
+
}>;
|
|
31
|
+
getFile(host: string, serial: string, token: string, filename: string): Promise<{
|
|
32
|
+
name: string;
|
|
33
|
+
exists: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
uploadFile(host: string, serial: string, token: string, filePath: string, filename: string, print: boolean): Promise<Record<string, unknown>>;
|
|
36
|
+
startJob(host: string, serial: string, token: string, filename: string): Promise<{
|
|
37
|
+
status: string;
|
|
38
|
+
message: string;
|
|
39
|
+
file: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Upload a file to the printer via FTP using basic-ftp directly.
|
|
43
|
+
* Bypasses bambu-js's sendFile which has a double-path bug (ensureDir CDs
|
|
44
|
+
* into the target directory, then uploadFrom uses the full relative path
|
|
45
|
+
* again, resulting in e.g. /cache/cache/file.3mf).
|
|
46
|
+
*/
|
|
47
|
+
private ftpUpload;
|
|
48
|
+
disconnectAll(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import JSZip from "jszip";
|
|
5
|
+
import { Client as FTPClient } from "basic-ftp";
|
|
6
|
+
import { BambuPrinter } from "bambu-js";
|
|
7
|
+
import { BambuClient, GCodeFileCommand, GCodeLineCommand, PushAllCommand, UpdateStateCommand, } from "bambu-node";
|
|
8
|
+
class BambuClientStore {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.printers = new Map();
|
|
11
|
+
this.initialConnectionPromises = new Map();
|
|
12
|
+
}
|
|
13
|
+
async getPrinter(host, serial, token) {
|
|
14
|
+
const key = `${host}-${serial}`;
|
|
15
|
+
if (this.printers.has(key)) {
|
|
16
|
+
return this.printers.get(key);
|
|
17
|
+
}
|
|
18
|
+
if (this.initialConnectionPromises.has(key)) {
|
|
19
|
+
await this.initialConnectionPromises.get(key);
|
|
20
|
+
if (this.printers.has(key)) {
|
|
21
|
+
return this.printers.get(key);
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`Existing Bambu client connection for ${key} failed.`);
|
|
24
|
+
}
|
|
25
|
+
const printer = new BambuClient({
|
|
26
|
+
host,
|
|
27
|
+
serialNumber: serial,
|
|
28
|
+
accessToken: token,
|
|
29
|
+
});
|
|
30
|
+
printer.on("client:connect", () => {
|
|
31
|
+
this.printers.set(key, printer);
|
|
32
|
+
this.initialConnectionPromises.delete(key);
|
|
33
|
+
});
|
|
34
|
+
printer.on("client:error", () => {
|
|
35
|
+
this.printers.delete(key);
|
|
36
|
+
this.initialConnectionPromises.delete(key);
|
|
37
|
+
});
|
|
38
|
+
printer.on("client:disconnect", () => {
|
|
39
|
+
this.printers.delete(key);
|
|
40
|
+
this.initialConnectionPromises.delete(key);
|
|
41
|
+
});
|
|
42
|
+
const connectPromise = printer.connect().then(() => { });
|
|
43
|
+
this.initialConnectionPromises.set(key, connectPromise);
|
|
44
|
+
try {
|
|
45
|
+
await connectPromise;
|
|
46
|
+
return printer;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.initialConnectionPromises.delete(key);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async disconnectAll() {
|
|
54
|
+
const disconnectPromises = [];
|
|
55
|
+
for (const printer of this.printers.values()) {
|
|
56
|
+
disconnectPromises.push((async () => {
|
|
57
|
+
try {
|
|
58
|
+
await printer.disconnect();
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error("Failed to disconnect Bambu client", error);
|
|
62
|
+
}
|
|
63
|
+
})());
|
|
64
|
+
}
|
|
65
|
+
await Promise.allSettled(disconnectPromises);
|
|
66
|
+
this.printers.clear();
|
|
67
|
+
this.initialConnectionPromises.clear();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export class BambuImplementation {
|
|
71
|
+
constructor() {
|
|
72
|
+
this.printerStore = new BambuClientStore();
|
|
73
|
+
}
|
|
74
|
+
async getPrinter(host, serial, token) {
|
|
75
|
+
return this.printerStore.getPrinter(host, serial, token);
|
|
76
|
+
}
|
|
77
|
+
async resolveProjectFileMetadata(localThreeMfPath, plateIndex) {
|
|
78
|
+
const archive = await fs.readFile(localThreeMfPath);
|
|
79
|
+
const zip = await JSZip.loadAsync(archive);
|
|
80
|
+
const plateEntries = Object.values(zip.files).filter((entry) => !entry.dir && /^Metadata\/plate_\d+\.gcode$/i.test(entry.name));
|
|
81
|
+
if (plateEntries.length === 0) {
|
|
82
|
+
throw new Error("3MF does not contain any Metadata/plate_<n>.gcode entries. Re-slice and export a printable 3MF.");
|
|
83
|
+
}
|
|
84
|
+
let selectedEntry = plateEntries.sort((a, b) => a.name.localeCompare(b.name))[0];
|
|
85
|
+
if (plateIndex !== undefined) {
|
|
86
|
+
const expectedEntryName = `Metadata/plate_${plateIndex + 1}.gcode`;
|
|
87
|
+
const matchedEntry = plateEntries.find((entry) => entry.name.toLowerCase() === expectedEntryName.toLowerCase());
|
|
88
|
+
if (!matchedEntry) {
|
|
89
|
+
const available = plateEntries.map((entry) => entry.name).join(", ");
|
|
90
|
+
throw new Error(`Requested plateIndex=${plateIndex} (${expectedEntryName}) not present in 3MF. Available: ${available}`);
|
|
91
|
+
}
|
|
92
|
+
selectedEntry = matchedEntry;
|
|
93
|
+
}
|
|
94
|
+
const gcodeBuffer = await selectedEntry.async("nodebuffer");
|
|
95
|
+
const md5 = createHash("md5").update(gcodeBuffer).digest("hex");
|
|
96
|
+
return {
|
|
97
|
+
plateFileName: path.posix.basename(selectedEntry.name),
|
|
98
|
+
plateInternalPath: selectedEntry.name,
|
|
99
|
+
md5,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async getStatus(host, serial, token) {
|
|
103
|
+
try {
|
|
104
|
+
const printer = await this.getPrinter(host, serial, token);
|
|
105
|
+
try {
|
|
106
|
+
await printer.executeCommand(new PushAllCommand());
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.warn("PushAllCommand failed, continuing with cached status", error);
|
|
110
|
+
}
|
|
111
|
+
if (!printer.data || Object.keys(printer.data).length === 0) {
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
113
|
+
}
|
|
114
|
+
const data = printer.data;
|
|
115
|
+
return {
|
|
116
|
+
status: data.gcode_state || "UNKNOWN",
|
|
117
|
+
connected: true,
|
|
118
|
+
temperatures: {
|
|
119
|
+
nozzle: {
|
|
120
|
+
actual: data.nozzle_temper || 0,
|
|
121
|
+
target: data.nozzle_target_temper || 0,
|
|
122
|
+
},
|
|
123
|
+
bed: {
|
|
124
|
+
actual: data.bed_temper || 0,
|
|
125
|
+
target: data.bed_target_temper || 0,
|
|
126
|
+
},
|
|
127
|
+
chamber: data.chamber_temper || data.frame_temper || 0,
|
|
128
|
+
},
|
|
129
|
+
print: {
|
|
130
|
+
filename: data.subtask_name || data.gcode_file || "None",
|
|
131
|
+
progress: data.mc_percent || 0,
|
|
132
|
+
timeRemaining: data.mc_remaining_time || 0,
|
|
133
|
+
currentLayer: data.layer_num || 0,
|
|
134
|
+
totalLayers: data.total_layer_num || 0,
|
|
135
|
+
},
|
|
136
|
+
ams: data.ams || null,
|
|
137
|
+
model: data.model || "Unknown",
|
|
138
|
+
serial,
|
|
139
|
+
raw: data,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`Failed to get Bambu status for ${serial}:`, error);
|
|
144
|
+
return { status: "error", connected: false, error: error.message };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async print3mf(host, serial, token, options) {
|
|
148
|
+
if (!options.filePath.toLowerCase().endsWith(".3mf")) {
|
|
149
|
+
throw new Error("print3mf requires a .3mf input file.");
|
|
150
|
+
}
|
|
151
|
+
const projectMetadata = await this.resolveProjectFileMetadata(options.filePath, options.plateIndex);
|
|
152
|
+
const remoteFileName = path.basename(options.filePath);
|
|
153
|
+
const remoteProjectPath = `cache/${remoteFileName}`;
|
|
154
|
+
// Upload via basic-ftp directly (bypasses bambu-js double-path bug)
|
|
155
|
+
await this.ftpUpload(host, token, options.filePath, `/cache/${remoteFileName}`);
|
|
156
|
+
// Send project_file command via bambu-node MQTT (bypasses bambu-js
|
|
157
|
+
// hardcoded use_ams=true and missing ams_mapping support)
|
|
158
|
+
const printer = await this.getPrinter(host, serial, token);
|
|
159
|
+
const md5 = options.md5 ?? projectMetadata.md5;
|
|
160
|
+
// Build AMS mapping per OpenBambuAPI spec: 5-element array
|
|
161
|
+
// [-1,-1,-1,-1,0] means slot 0 only; pad unused slots with -1
|
|
162
|
+
let amsMapping;
|
|
163
|
+
if (options.amsMapping && options.amsMapping.length > 0) {
|
|
164
|
+
amsMapping = Array.from({ length: 5 }, (_, i) => i < options.amsMapping.length ? options.amsMapping[i] : -1);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
amsMapping = [-1, -1, -1, -1, 0];
|
|
168
|
+
}
|
|
169
|
+
const projectFileCmd = {
|
|
170
|
+
print: {
|
|
171
|
+
command: "project_file",
|
|
172
|
+
param: `Metadata/${projectMetadata.plateFileName}`,
|
|
173
|
+
url: `file:///sdcard/${remoteProjectPath}`,
|
|
174
|
+
subtask_name: options.projectName,
|
|
175
|
+
md5,
|
|
176
|
+
flow_cali: options.flowCalibration ?? true,
|
|
177
|
+
layer_inspect: options.layerInspect ?? true,
|
|
178
|
+
vibration_cali: options.vibrationCalibration ?? true,
|
|
179
|
+
bed_leveling: options.bedLeveling ?? true,
|
|
180
|
+
bed_type: "textured_plate",
|
|
181
|
+
timelapse: options.timelapse ?? false,
|
|
182
|
+
use_ams: options.useAMS !== false,
|
|
183
|
+
ams_mapping: amsMapping,
|
|
184
|
+
profile_id: "0",
|
|
185
|
+
project_id: "0",
|
|
186
|
+
sequence_id: "0",
|
|
187
|
+
subtask_id: "0",
|
|
188
|
+
task_id: "0",
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
await printer.publish(projectFileCmd);
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
193
|
+
return {
|
|
194
|
+
status: "success",
|
|
195
|
+
message: `Uploaded and started 3MF print: ${options.projectName}`,
|
|
196
|
+
remoteProjectPath,
|
|
197
|
+
plateFile: projectMetadata.plateFileName,
|
|
198
|
+
platePath: projectMetadata.plateInternalPath,
|
|
199
|
+
md5,
|
|
200
|
+
amsMapping,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async cancelJob(host, serial, token) {
|
|
204
|
+
const printer = await this.getPrinter(host, serial, token);
|
|
205
|
+
try {
|
|
206
|
+
await printer.executeCommand(new UpdateStateCommand({ state: "stop" }));
|
|
207
|
+
return { status: "success", message: "Cancel command sent successfully." };
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
throw new Error(`Failed to cancel print: ${error.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async setTemperature(host, serial, token, component, temperature) {
|
|
214
|
+
const printer = await this.getPrinter(host, serial, token);
|
|
215
|
+
const normalizedComponent = component.toLowerCase();
|
|
216
|
+
const targetTemperature = Math.round(temperature);
|
|
217
|
+
if (targetTemperature < 0 || targetTemperature > 300) {
|
|
218
|
+
throw new Error("Temperature must be between 0 and 300°C.");
|
|
219
|
+
}
|
|
220
|
+
let gcode;
|
|
221
|
+
if (normalizedComponent === "bed") {
|
|
222
|
+
gcode = `M140 S${targetTemperature}`;
|
|
223
|
+
}
|
|
224
|
+
else if (normalizedComponent === "extruder" ||
|
|
225
|
+
normalizedComponent === "nozzle" ||
|
|
226
|
+
normalizedComponent === "tool" ||
|
|
227
|
+
normalizedComponent === "tool0") {
|
|
228
|
+
gcode = `M104 S${targetTemperature}`;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
throw new Error(`Unsupported temperature component: ${component}. Use one of: bed, nozzle, extruder.`);
|
|
232
|
+
}
|
|
233
|
+
await printer.executeCommand(new GCodeLineCommand({ gcodes: [gcode] }));
|
|
234
|
+
return {
|
|
235
|
+
status: "success",
|
|
236
|
+
message: `Temperature command sent for ${normalizedComponent}.`,
|
|
237
|
+
command: gcode,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async getFiles(host, serial, token) {
|
|
241
|
+
const printer = new BambuPrinter(host, serial, token);
|
|
242
|
+
const directories = ["cache", "timelapse", "logs"];
|
|
243
|
+
const filesByDirectory = {};
|
|
244
|
+
await printer.manipulateFiles(async (context) => {
|
|
245
|
+
for (const directory of directories) {
|
|
246
|
+
try {
|
|
247
|
+
filesByDirectory[directory] = await context.readDir(directory);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
filesByDirectory[directory] = [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
const files = Object.entries(filesByDirectory).flatMap(([directory, names]) => names.map((name) => `${directory}/${name}`));
|
|
255
|
+
return {
|
|
256
|
+
files,
|
|
257
|
+
directories: filesByDirectory,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async getFile(host, serial, token, filename) {
|
|
261
|
+
const printer = new BambuPrinter(host, serial, token);
|
|
262
|
+
const normalized = filename.replace(/^\/+/, "");
|
|
263
|
+
const directory = path.posix.dirname(normalized) === "." ? "cache" : path.posix.dirname(normalized);
|
|
264
|
+
const baseName = path.posix.basename(normalized);
|
|
265
|
+
let exists = false;
|
|
266
|
+
await printer.manipulateFiles(async (context) => {
|
|
267
|
+
const entries = await context.readDir(directory);
|
|
268
|
+
exists = entries.includes(baseName);
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
name: `${directory}/${baseName}`,
|
|
272
|
+
exists,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
async uploadFile(host, serial, token, filePath, filename, print) {
|
|
276
|
+
await fs.access(filePath);
|
|
277
|
+
const normalizedFileName = filename.replace(/^\/+/, "");
|
|
278
|
+
const remotePath = normalizedFileName.includes("/")
|
|
279
|
+
? normalizedFileName
|
|
280
|
+
: `cache/${normalizedFileName}`;
|
|
281
|
+
// Use direct FTP upload (bypasses bambu-js double-path bug)
|
|
282
|
+
await this.ftpUpload(host, token, filePath, `/${remotePath}`);
|
|
283
|
+
const response = {
|
|
284
|
+
status: "success",
|
|
285
|
+
uploaded: true,
|
|
286
|
+
remotePath,
|
|
287
|
+
printRequested: print,
|
|
288
|
+
};
|
|
289
|
+
if (print) {
|
|
290
|
+
if (remotePath.toLowerCase().endsWith(".gcode")) {
|
|
291
|
+
response.startResult = await this.startJob(host, serial, token, remotePath);
|
|
292
|
+
}
|
|
293
|
+
else if (remotePath.toLowerCase().endsWith(".3mf")) {
|
|
294
|
+
response.note =
|
|
295
|
+
"3MF upload complete. Use print_3mf to start a project print with plate and metadata options.";
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
throw new Error("Automatic print after upload supports .gcode only. Use print_3mf for .3mf project prints.");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return response;
|
|
302
|
+
}
|
|
303
|
+
async startJob(host, serial, token, filename) {
|
|
304
|
+
if (filename.toLowerCase().endsWith(".3mf")) {
|
|
305
|
+
throw new Error("Use print_3mf for .3mf project files.");
|
|
306
|
+
}
|
|
307
|
+
const printer = await this.getPrinter(host, serial, token);
|
|
308
|
+
const normalizedFileName = filename.replace(/^\/+/, "");
|
|
309
|
+
const remoteFile = normalizedFileName.includes("/")
|
|
310
|
+
? normalizedFileName
|
|
311
|
+
: `cache/${normalizedFileName}`;
|
|
312
|
+
await printer.executeCommand(new GCodeFileCommand({ fileName: remoteFile }));
|
|
313
|
+
return {
|
|
314
|
+
status: "success",
|
|
315
|
+
message: `Start command sent for ${remoteFile}.`,
|
|
316
|
+
file: remoteFile,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Upload a file to the printer via FTP using basic-ftp directly.
|
|
321
|
+
* Bypasses bambu-js's sendFile which has a double-path bug (ensureDir CDs
|
|
322
|
+
* into the target directory, then uploadFrom uses the full relative path
|
|
323
|
+
* again, resulting in e.g. /cache/cache/file.3mf).
|
|
324
|
+
*/
|
|
325
|
+
async ftpUpload(host, token, localPath, remotePath) {
|
|
326
|
+
const client = new FTPClient(15000);
|
|
327
|
+
try {
|
|
328
|
+
await client.access({
|
|
329
|
+
host,
|
|
330
|
+
port: 990,
|
|
331
|
+
user: "bblp",
|
|
332
|
+
password: token,
|
|
333
|
+
secure: "implicit",
|
|
334
|
+
secureOptions: { rejectUnauthorized: false },
|
|
335
|
+
});
|
|
336
|
+
// Use absolute path to avoid CWD side-effects
|
|
337
|
+
const absoluteRemote = remotePath.startsWith("/") ? remotePath : `/${remotePath}`;
|
|
338
|
+
const remoteDir = path.posix.dirname(absoluteRemote);
|
|
339
|
+
await client.ensureDir(remoteDir);
|
|
340
|
+
// uploadFrom with just the basename since we're already in the right dir
|
|
341
|
+
await client.uploadFrom(localPath, path.posix.basename(absoluteRemote));
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
client.close();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async disconnectAll() {
|
|
348
|
+
await this.printerStore.disconnectAll();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
export type ProgressCallback = (progress: number, message?: string) => void;
|
|
4
|
+
export type OperationResult = {
|
|
5
|
+
success: boolean;
|
|
6
|
+
filePath?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
operationId: string;
|
|
9
|
+
};
|
|
10
|
+
export type TransformationType = 'scale' | 'rotate' | 'translate' | 'extendBase' | 'customModify';
|
|
11
|
+
export type TransformationAxis = 'x' | 'y' | 'z' | 'all';
|
|
12
|
+
export type BoundingBox = {
|
|
13
|
+
min: THREE.Vector3;
|
|
14
|
+
max: THREE.Vector3;
|
|
15
|
+
center: THREE.Vector3;
|
|
16
|
+
dimensions: THREE.Vector3;
|
|
17
|
+
};
|
|
18
|
+
export type TransformationParams = {
|
|
19
|
+
type: TransformationType;
|
|
20
|
+
axis?: TransformationAxis;
|
|
21
|
+
value: number | number[];
|
|
22
|
+
relative?: boolean;
|
|
23
|
+
selectionBounds?: THREE.Box3;
|
|
24
|
+
};
|
|
25
|
+
export declare class STLManipulator extends EventEmitter {
|
|
26
|
+
private tempDir;
|
|
27
|
+
private activeOperations;
|
|
28
|
+
constructor(tempDir?: string);
|
|
29
|
+
/**
|
|
30
|
+
* Generate a unique operation ID
|
|
31
|
+
*/
|
|
32
|
+
private generateOperationId;
|
|
33
|
+
/**
|
|
34
|
+
* Load STL file and return geometry and bounding box
|
|
35
|
+
*/
|
|
36
|
+
private loadSTL;
|
|
37
|
+
/**
|
|
38
|
+
* Save a geometry to STL file
|
|
39
|
+
*/
|
|
40
|
+
private saveSTL;
|
|
41
|
+
/**
|
|
42
|
+
* Get comprehensive information about an STL file
|
|
43
|
+
*/
|
|
44
|
+
getSTLInfo(stlFilePath: string): Promise<{
|
|
45
|
+
filePath: string;
|
|
46
|
+
fileName: string;
|
|
47
|
+
fileSize: number;
|
|
48
|
+
boundingBox: BoundingBox;
|
|
49
|
+
vertexCount: number;
|
|
50
|
+
faceCount: number;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Scale an STL model uniformly or along specific axes
|
|
54
|
+
*/
|
|
55
|
+
scaleSTL(stlFilePath: string, scaleFactors: number | [number, number, number], progressCallback?: ProgressCallback): Promise<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Rotate an STL model around specific axes
|
|
58
|
+
*/
|
|
59
|
+
rotateSTL(stlFilePath: string, rotationAngles: [number, number, number], // [x, y, z] in degrees
|
|
60
|
+
progressCallback?: ProgressCallback): Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* Translate (move) an STL model along specific axes
|
|
63
|
+
*/
|
|
64
|
+
translateSTL(stlFilePath: string, translationValues: [number, number, number], // [x, y, z] in mm
|
|
65
|
+
progressCallback?: ProgressCallback): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Cancel an ongoing operation
|
|
68
|
+
*/
|
|
69
|
+
cancelOperation(operationId: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Generate an SVG visualization of an STL file from multiple angles
|
|
72
|
+
* @param stlFilePath Path to the STL file
|
|
73
|
+
* @param width Width of each view in pixels
|
|
74
|
+
* @param height Height of each view in pixels
|
|
75
|
+
* @param progressCallback Optional callback for progress updates
|
|
76
|
+
* @returns Path to the generated SVG file
|
|
77
|
+
*/
|
|
78
|
+
generateVisualization(stlFilePath: string, width?: number, height?: number, progressCallback?: ProgressCallback): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Apply a specific transformation to a selected section of an STL file
|
|
81
|
+
* This allows for targeted modifications of specific parts of a model
|
|
82
|
+
*/
|
|
83
|
+
modifySection(stlFilePath: string, selection: THREE.Box3 | 'top' | 'bottom' | 'center', transformation: TransformationParams, progressCallback?: ProgressCallback): Promise<string>;
|
|
84
|
+
/**
|
|
85
|
+
* Enhanced version of extendBase with progress reporting
|
|
86
|
+
* @param stlFilePath Path to the input STL file
|
|
87
|
+
* @param extensionInches Amount to extend base in inches
|
|
88
|
+
* @param progressCallback Optional callback for progress updates
|
|
89
|
+
* @returns Path to the modified STL file
|
|
90
|
+
*/
|
|
91
|
+
extendBase(stlFilePath: string, extensionInches: number, progressCallback?: ProgressCallback): Promise<string>;
|
|
92
|
+
/**
|
|
93
|
+
* Slice an STL or 3MF file using the specified slicer
|
|
94
|
+
* @param stlFilePath Path to the input STL or 3MF file
|
|
95
|
+
* @param slicerType Type of slicer (prusaslicer, cura, slic3r, orcaslicer, bambustudio)
|
|
96
|
+
* @param slicerPath Path to the slicer executable
|
|
97
|
+
* @param slicerProfile Optional path to the slicer profile/config file
|
|
98
|
+
* @param progressCallback Optional callback for progress updates
|
|
99
|
+
* @returns Path to the generated G-code or sliced 3MF file
|
|
100
|
+
*/
|
|
101
|
+
sliceSTL(stlFilePath: string, slicerType: 'prusaslicer' | 'cura' | 'slic3r' | 'orcaslicer' | 'bambustudio', slicerPath: string, slicerProfile?: string, progressCallback?: ProgressCallback): Promise<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Enhanced version of confirmTemperatures with better error handling
|
|
104
|
+
* @param gcodePath Path to the G-code file
|
|
105
|
+
* @param expected Expected temperature settings
|
|
106
|
+
* @param progressCallback Optional callback for progress updates
|
|
107
|
+
* @returns Object with comparison results
|
|
108
|
+
*/
|
|
109
|
+
confirmTemperatures(gcodePath: string, expected: {
|
|
110
|
+
extruder?: number;
|
|
111
|
+
bed?: number;
|
|
112
|
+
}, progressCallback?: ProgressCallback): Promise<{
|
|
113
|
+
match: boolean;
|
|
114
|
+
actual: {
|
|
115
|
+
extruder?: number;
|
|
116
|
+
bed?: number;
|
|
117
|
+
};
|
|
118
|
+
expected: {
|
|
119
|
+
extruder?: number;
|
|
120
|
+
bed?: number;
|
|
121
|
+
};
|
|
122
|
+
allTemperatures: {
|
|
123
|
+
extruder: number[];
|
|
124
|
+
bed: number[];
|
|
125
|
+
};
|
|
126
|
+
}>;
|
|
127
|
+
/**
|
|
128
|
+
* Merge vertices within a specified tolerance.
|
|
129
|
+
*/
|
|
130
|
+
mergeVertices(stlFilePath: string, tolerance?: number, // Default tolerance in mm
|
|
131
|
+
progressCallback?: ProgressCallback): Promise<string>;
|
|
132
|
+
/**
|
|
133
|
+
* Center the model at the origin (0,0,0).
|
|
134
|
+
*/
|
|
135
|
+
centerModel(stlFilePath: string, progressCallback?: ProgressCallback): Promise<string>;
|
|
136
|
+
/**
|
|
137
|
+
* Rotate the model so its largest flat face lies on the XY plane (Z=0).
|
|
138
|
+
*/
|
|
139
|
+
layFlat(stlFilePath: string, progressCallback?: ProgressCallback): Promise<string>;
|
|
140
|
+
}
|