bambu-printer-mcp 1.1.27 → 1.1.28
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/CONTRIBUTORS.md +4 -0
- package/README.md +25 -8
- package/dist/bambu-native.d.ts +55 -0
- package/dist/bambu-native.js +212 -13
- package/dist/index.js +101 -26
- package/dist/printers/bambu.d.ts +29 -2
- package/dist/printers/bambu.js +45 -12
- package/dist/safety/hidden-hms.d.ts +32 -0
- package/dist/safety/hidden-hms.js +405 -0
- package/dist/safety/print-file.js +47 -1
- package/dist/safety/printer-state.d.ts +9 -0
- package/dist/safety/printer-state.js +80 -13
- package/dist/slicer/profile-flatten.js +25 -0
- package/dist/stl/stl-manipulator.d.ts +1 -0
- package/dist/stl/stl-manipulator.js +1 -0
- package/native/bambu-native-print.cpp +100 -26
- package/package.json +7 -5
- package/scripts/build-bambu-native.sh +53 -0
- package/scripts/setup-bambu-native.mjs +372 -0
- package/src/bambu-native.ts +229 -14
- package/src/index.ts +107 -25
- package/src/printers/bambu.ts +51 -12
- package/src/safety/hidden-hms.ts +406 -0
- package/src/safety/print-file.ts +37 -1
- package/src/safety/printer-state.ts +85 -13
- package/src/slicer/profile-flatten.ts +32 -0
- package/src/stl/stl-manipulator.ts +2 -0
- package/scripts/build-bambu-native.zsh +0 -33
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// GENERATED by scripts/generate-hidden-hms.mjs from Bambu Studio v02.08.02.61
|
|
2
|
+
// (commit 926a7192574bcb9b3a732e1ec59a46d79cb45466, resources/hms/hms_en_<serial prefix>.json).
|
|
3
|
+
// Codes Bambu lists for a serial prefix with empty text in every entry. Do not edit by hand.
|
|
4
|
+
export const HIDDEN_HMS_SOURCE = {
|
|
5
|
+
studioTag: "v02.08.02.61",
|
|
6
|
+
studioCommit: "926a7192574bcb9b3a732e1ec59a46d79cb45466",
|
|
7
|
+
/** Per table: Bambu's device_hms version and the SHA-256 of the English file used. */
|
|
8
|
+
tables: {
|
|
9
|
+
"093": { version: 202508191204, sha256: "7801fd6c355854b62ecb62ee32727b4e5dcca908841dbcfd5e5de45b3dfd1c25" },
|
|
10
|
+
"094": { version: 202503231214, sha256: "047c15e2140738ec08258b74536e6cb996cddf0c586c5c39e2318e54125a9e76" },
|
|
11
|
+
"20P": { version: 202511162200, sha256: "e4f067d027b9bcbb9bbf6cb90f41f11130cf4d83dffd3dee101db7826dc808c3" },
|
|
12
|
+
"22E": { version: 202510142200, sha256: "87426021aea2bd4e8d721e46e1c86808fd7c6fc06f52baf6d952ead9e8d14d1e" },
|
|
13
|
+
"239": { version: 202506171403, sha256: "c1d23d5302756d57df5cb9bc795d95fc54ef1db7565dca1f04b69a024886426a" },
|
|
14
|
+
"31B": { version: 202511162200, sha256: "dcf63657bff5451f0715ea3c032efe482279bc8cbc28cb9b8ae51c7354826574" },
|
|
15
|
+
},
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export const HIDDEN_HMS: Readonly<Record<string, readonly string[]>> = {
|
|
19
|
+
"093": [
|
|
20
|
+
"0300180000000000",
|
|
21
|
+
"050001000001000A",
|
|
22
|
+
"0500010000020003",
|
|
23
|
+
"0500010000020008",
|
|
24
|
+
"0500010000020009",
|
|
25
|
+
"0500010000030009",
|
|
26
|
+
"0500030000010053",
|
|
27
|
+
"0500030000020010",
|
|
28
|
+
"0500030000020011",
|
|
29
|
+
"0500030000020012",
|
|
30
|
+
"0500030000020013",
|
|
31
|
+
"0500030000020014",
|
|
32
|
+
"0500030000020015",
|
|
33
|
+
"0500030000020016",
|
|
34
|
+
"0500030000020017",
|
|
35
|
+
"0500030000020018",
|
|
36
|
+
"0500030000030007",
|
|
37
|
+
"0500040000020020",
|
|
38
|
+
"0500050000010008",
|
|
39
|
+
"0500050000010009",
|
|
40
|
+
"050005000001000A",
|
|
41
|
+
"050005000001000B",
|
|
42
|
+
"050005000001000C",
|
|
43
|
+
"0500050000020030",
|
|
44
|
+
"0500060000020005",
|
|
45
|
+
"0500060000020007",
|
|
46
|
+
"0500060000020008",
|
|
47
|
+
"0500060000020011",
|
|
48
|
+
"0500060000020012",
|
|
49
|
+
"0500060000020013",
|
|
50
|
+
"0500060000020014",
|
|
51
|
+
"0500060000020021",
|
|
52
|
+
"0500060000020022",
|
|
53
|
+
"0500060000020023",
|
|
54
|
+
"0500060000020024",
|
|
55
|
+
"0500060000020041",
|
|
56
|
+
"0500060000020043",
|
|
57
|
+
"0500060000020044",
|
|
58
|
+
"0500060000020051",
|
|
59
|
+
"0500060000020053",
|
|
60
|
+
"0500060000020054",
|
|
61
|
+
"0500060000020061",
|
|
62
|
+
"0500060000020062",
|
|
63
|
+
"05000600000200A0",
|
|
64
|
+
"05000600000200A1",
|
|
65
|
+
"0C00010000010018",
|
|
66
|
+
"0C0001000002000E",
|
|
67
|
+
"0C00010000020015",
|
|
68
|
+
"0C00010000020016",
|
|
69
|
+
"0C00010000020019",
|
|
70
|
+
"0C0001000002001A",
|
|
71
|
+
"0C0001000002001B",
|
|
72
|
+
"0C00040000020006",
|
|
73
|
+
],
|
|
74
|
+
"094": [
|
|
75
|
+
"0300180000000000",
|
|
76
|
+
"0300260000000000",
|
|
77
|
+
"050001000001000A",
|
|
78
|
+
"0500010000020003",
|
|
79
|
+
"0500010000020008",
|
|
80
|
+
"0500010000020009",
|
|
81
|
+
"0500010000030009",
|
|
82
|
+
"0500030000020010",
|
|
83
|
+
"0500030000020011",
|
|
84
|
+
"0500030000020012",
|
|
85
|
+
"0500030000020013",
|
|
86
|
+
"0500030000020014",
|
|
87
|
+
"0500030000020015",
|
|
88
|
+
"0500030000020016",
|
|
89
|
+
"0500030000020017",
|
|
90
|
+
"0500030000020018",
|
|
91
|
+
"0500030000030007",
|
|
92
|
+
"0500040000020020",
|
|
93
|
+
"0500050000010008",
|
|
94
|
+
"0500050000010009",
|
|
95
|
+
"050005000001000A",
|
|
96
|
+
"050005000001000B",
|
|
97
|
+
"050005000001000C",
|
|
98
|
+
"0500060000020001",
|
|
99
|
+
"0500060000020002",
|
|
100
|
+
"0500060000020003",
|
|
101
|
+
"0500060000020005",
|
|
102
|
+
"0500060000020006",
|
|
103
|
+
"0500060000020007",
|
|
104
|
+
"0500060000020008",
|
|
105
|
+
"0500060000020011",
|
|
106
|
+
"0500060000020012",
|
|
107
|
+
"0500060000020013",
|
|
108
|
+
"0500060000020014",
|
|
109
|
+
"0500060000020021",
|
|
110
|
+
"0500060000020022",
|
|
111
|
+
"0500060000020023",
|
|
112
|
+
"0500060000020024",
|
|
113
|
+
"0500060000020041",
|
|
114
|
+
"0500060000020042",
|
|
115
|
+
"0500060000020043",
|
|
116
|
+
"0500060000020044",
|
|
117
|
+
"0500060000020051",
|
|
118
|
+
"0500060000020052",
|
|
119
|
+
"0500060000020053",
|
|
120
|
+
"0500060000020054",
|
|
121
|
+
"0500060000020061",
|
|
122
|
+
"0500060000020062",
|
|
123
|
+
"05000600000200A0",
|
|
124
|
+
"05000600000200A1",
|
|
125
|
+
"0C00010000020015",
|
|
126
|
+
"0C00010000020016",
|
|
127
|
+
"0C00010000020019",
|
|
128
|
+
"0C0001000002001A",
|
|
129
|
+
"0C0001000002001B",
|
|
130
|
+
"0C00040000020006",
|
|
131
|
+
],
|
|
132
|
+
"20P": [
|
|
133
|
+
"0300180000000000",
|
|
134
|
+
"0300260000000000",
|
|
135
|
+
"050001000001000A",
|
|
136
|
+
"0500010000020003",
|
|
137
|
+
"0500010000020008",
|
|
138
|
+
"0500010000020009",
|
|
139
|
+
"050001000002000B",
|
|
140
|
+
"050001000002000F",
|
|
141
|
+
"0500010000020010",
|
|
142
|
+
"0500010000020011",
|
|
143
|
+
"0500010000030009",
|
|
144
|
+
"0500020000030009",
|
|
145
|
+
"050002000003000A",
|
|
146
|
+
"050002000003000B",
|
|
147
|
+
"050002000003000C",
|
|
148
|
+
"0500030000010053",
|
|
149
|
+
"0500030000020010",
|
|
150
|
+
"0500030000020011",
|
|
151
|
+
"0500030000020012",
|
|
152
|
+
"0500030000020013",
|
|
153
|
+
"0500030000020014",
|
|
154
|
+
"0500030000020015",
|
|
155
|
+
"0500030000020016",
|
|
156
|
+
"0500030000020017",
|
|
157
|
+
"0500030000020018",
|
|
158
|
+
"0500030000020019",
|
|
159
|
+
"0500030000020050",
|
|
160
|
+
"0500030000020051",
|
|
161
|
+
"0500030000020052",
|
|
162
|
+
"0500030000020053",
|
|
163
|
+
"0500030000020054",
|
|
164
|
+
"0500030000030007",
|
|
165
|
+
"0500040000010056",
|
|
166
|
+
"0500040000010060",
|
|
167
|
+
"0500040000010061",
|
|
168
|
+
"0500040000020020",
|
|
169
|
+
"0500050000010008",
|
|
170
|
+
"0500050000010009",
|
|
171
|
+
"050005000001000A",
|
|
172
|
+
"050005000001000B",
|
|
173
|
+
"050005000001000C",
|
|
174
|
+
"0500050000020023",
|
|
175
|
+
"0500050000020030",
|
|
176
|
+
"0500060000020005",
|
|
177
|
+
"0500060000020006",
|
|
178
|
+
"0500060000020007",
|
|
179
|
+
"0500060000020008",
|
|
180
|
+
"0500060000020009",
|
|
181
|
+
"0500060000020011",
|
|
182
|
+
"0500060000020012",
|
|
183
|
+
"0500060000020013",
|
|
184
|
+
"0500060000020014",
|
|
185
|
+
"0500060000020015",
|
|
186
|
+
"0500060000020021",
|
|
187
|
+
"0500060000020022",
|
|
188
|
+
"0500060000020023",
|
|
189
|
+
"0500060000020024",
|
|
190
|
+
"0500060000020025",
|
|
191
|
+
"0500060000020035",
|
|
192
|
+
"0500060000020041",
|
|
193
|
+
"0500060000020042",
|
|
194
|
+
"0500060000020044",
|
|
195
|
+
"0500060000020045",
|
|
196
|
+
"0500060000020051",
|
|
197
|
+
"0500060000020052",
|
|
198
|
+
"0500060000020054",
|
|
199
|
+
"0500060000020061",
|
|
200
|
+
"0500060000020062",
|
|
201
|
+
"0500060000020070",
|
|
202
|
+
"0500060000020071",
|
|
203
|
+
"05000600000200A0",
|
|
204
|
+
"05000600000200A1",
|
|
205
|
+
"0500070000020001",
|
|
206
|
+
"0500070000020002",
|
|
207
|
+
"0500070000020003",
|
|
208
|
+
"0500070000020101",
|
|
209
|
+
"0500070000020201",
|
|
210
|
+
"0503000000030027",
|
|
211
|
+
"0503000000030028",
|
|
212
|
+
"0503000000030029",
|
|
213
|
+
"0C00010000010018",
|
|
214
|
+
"0C0001000002000E",
|
|
215
|
+
"0C00010000020015",
|
|
216
|
+
"0C00010000020016",
|
|
217
|
+
"0C00010000020019",
|
|
218
|
+
"0C0001000002001A",
|
|
219
|
+
"0C0001000002001B",
|
|
220
|
+
"0C0003000003001A",
|
|
221
|
+
"0C00040000020006",
|
|
222
|
+
],
|
|
223
|
+
"22E": [
|
|
224
|
+
"0300180000000000",
|
|
225
|
+
"050001000001000A",
|
|
226
|
+
"0500010000020003",
|
|
227
|
+
"0500010000020008",
|
|
228
|
+
"0500010000020009",
|
|
229
|
+
"0500010000030009",
|
|
230
|
+
"0500030000010053",
|
|
231
|
+
"0500030000020010",
|
|
232
|
+
"0500030000020011",
|
|
233
|
+
"0500030000020012",
|
|
234
|
+
"0500030000020013",
|
|
235
|
+
"0500030000020014",
|
|
236
|
+
"0500030000020015",
|
|
237
|
+
"0500030000020016",
|
|
238
|
+
"0500030000020017",
|
|
239
|
+
"0500030000020018",
|
|
240
|
+
"0500030000030007",
|
|
241
|
+
"0500040000010056",
|
|
242
|
+
"0500040000020020",
|
|
243
|
+
"0500050000010008",
|
|
244
|
+
"0500050000010009",
|
|
245
|
+
"050005000001000A",
|
|
246
|
+
"050005000001000B",
|
|
247
|
+
"050005000001000C",
|
|
248
|
+
"0500050000020030",
|
|
249
|
+
"0500060000020005",
|
|
250
|
+
"0500060000020007",
|
|
251
|
+
"0500060000020008",
|
|
252
|
+
"0500060000020011",
|
|
253
|
+
"0500060000020012",
|
|
254
|
+
"0500060000020013",
|
|
255
|
+
"0500060000020014",
|
|
256
|
+
"0500060000020021",
|
|
257
|
+
"0500060000020022",
|
|
258
|
+
"0500060000020023",
|
|
259
|
+
"0500060000020024",
|
|
260
|
+
"0500060000020044",
|
|
261
|
+
"0500060000020054",
|
|
262
|
+
"0500060000020061",
|
|
263
|
+
"0500060000020062",
|
|
264
|
+
"05000600000200A0",
|
|
265
|
+
"05000600000200A1",
|
|
266
|
+
"0500070000020001",
|
|
267
|
+
"0500070000020002",
|
|
268
|
+
"0500070000020003",
|
|
269
|
+
"0500070000020101",
|
|
270
|
+
"0500070000020201",
|
|
271
|
+
"0503000000030027",
|
|
272
|
+
"0503000000030028",
|
|
273
|
+
"0503000000030029",
|
|
274
|
+
"0C00010000010018",
|
|
275
|
+
"0C0001000002000E",
|
|
276
|
+
"0C00010000020015",
|
|
277
|
+
"0C00010000020016",
|
|
278
|
+
"0C00010000020019",
|
|
279
|
+
"0C0001000002001A",
|
|
280
|
+
"0C0001000002001B",
|
|
281
|
+
"0C00040000020006",
|
|
282
|
+
],
|
|
283
|
+
"239": [
|
|
284
|
+
"0300180000000000",
|
|
285
|
+
"0300260000000000",
|
|
286
|
+
"0500010000020003",
|
|
287
|
+
"0500010000020008",
|
|
288
|
+
"0500010000020009",
|
|
289
|
+
"0500010000030009",
|
|
290
|
+
"0500030000020010",
|
|
291
|
+
"0500030000020011",
|
|
292
|
+
"0500030000020012",
|
|
293
|
+
"0500030000020013",
|
|
294
|
+
"0500030000020014",
|
|
295
|
+
"0500030000020015",
|
|
296
|
+
"0500030000020016",
|
|
297
|
+
"0500030000020017",
|
|
298
|
+
"0500030000020018",
|
|
299
|
+
"0500030000030007",
|
|
300
|
+
"0500040000020020",
|
|
301
|
+
"0500050000010008",
|
|
302
|
+
"0500050000010009",
|
|
303
|
+
"050005000001000A",
|
|
304
|
+
"050005000001000B",
|
|
305
|
+
"050005000001000C",
|
|
306
|
+
"0500060000020005",
|
|
307
|
+
"0500060000020006",
|
|
308
|
+
"0500060000020007",
|
|
309
|
+
"0500060000020008",
|
|
310
|
+
"0500060000020011",
|
|
311
|
+
"0500060000020012",
|
|
312
|
+
"0500060000020013",
|
|
313
|
+
"0500060000020014",
|
|
314
|
+
"0500060000020021",
|
|
315
|
+
"0500060000020022",
|
|
316
|
+
"0500060000020023",
|
|
317
|
+
"0500060000020024",
|
|
318
|
+
"0500060000020041",
|
|
319
|
+
"0500060000020042",
|
|
320
|
+
"0500060000020043",
|
|
321
|
+
"0500060000020044",
|
|
322
|
+
"0500060000020051",
|
|
323
|
+
"0500060000020052",
|
|
324
|
+
"0500060000020053",
|
|
325
|
+
"0500060000020054",
|
|
326
|
+
"0500060000020061",
|
|
327
|
+
"0500060000020062",
|
|
328
|
+
"05000600000200A0",
|
|
329
|
+
"05000600000200A1",
|
|
330
|
+
"0C00010000020015",
|
|
331
|
+
"0C00010000020016",
|
|
332
|
+
"0C00010000020019",
|
|
333
|
+
"0C0001000002001A",
|
|
334
|
+
"0C0001000002001B",
|
|
335
|
+
"0C00040000020006",
|
|
336
|
+
],
|
|
337
|
+
"31B": [
|
|
338
|
+
"0300180000000000",
|
|
339
|
+
"0300260000000000",
|
|
340
|
+
"050001000001000A",
|
|
341
|
+
"0500010000020003",
|
|
342
|
+
"0500010000020008",
|
|
343
|
+
"0500010000020009",
|
|
344
|
+
"0500010000030009",
|
|
345
|
+
"0500030000010053",
|
|
346
|
+
"0500030000020010",
|
|
347
|
+
"0500030000020011",
|
|
348
|
+
"0500030000020012",
|
|
349
|
+
"0500030000020013",
|
|
350
|
+
"0500030000020014",
|
|
351
|
+
"0500030000020015",
|
|
352
|
+
"0500030000020016",
|
|
353
|
+
"0500030000020017",
|
|
354
|
+
"0500030000020018",
|
|
355
|
+
"0500030000030007",
|
|
356
|
+
"0500040000010056",
|
|
357
|
+
"0500040000020020",
|
|
358
|
+
"0500050000010008",
|
|
359
|
+
"0500050000010009",
|
|
360
|
+
"050005000001000A",
|
|
361
|
+
"050005000001000B",
|
|
362
|
+
"050005000001000C",
|
|
363
|
+
"0500050000020030",
|
|
364
|
+
"0500060000020005",
|
|
365
|
+
"0500060000020006",
|
|
366
|
+
"0500060000020007",
|
|
367
|
+
"0500060000020008",
|
|
368
|
+
"0500060000020011",
|
|
369
|
+
"0500060000020012",
|
|
370
|
+
"0500060000020013",
|
|
371
|
+
"0500060000020014",
|
|
372
|
+
"0500060000020021",
|
|
373
|
+
"0500060000020022",
|
|
374
|
+
"0500060000020023",
|
|
375
|
+
"0500060000020024",
|
|
376
|
+
"0500060000020041",
|
|
377
|
+
"0500060000020042",
|
|
378
|
+
"0500060000020043",
|
|
379
|
+
"0500060000020044",
|
|
380
|
+
"0500060000020051",
|
|
381
|
+
"0500060000020052",
|
|
382
|
+
"0500060000020053",
|
|
383
|
+
"0500060000020054",
|
|
384
|
+
"0500060000020061",
|
|
385
|
+
"0500060000020062",
|
|
386
|
+
"05000600000200A0",
|
|
387
|
+
"05000600000200A1",
|
|
388
|
+
"0500070000020001",
|
|
389
|
+
"0500070000020002",
|
|
390
|
+
"0500070000020003",
|
|
391
|
+
"0500070000020101",
|
|
392
|
+
"0500070000020201",
|
|
393
|
+
"0503000000030027",
|
|
394
|
+
"0503000000030028",
|
|
395
|
+
"0503000000030029",
|
|
396
|
+
"0C00010000010018",
|
|
397
|
+
"0C0001000002000E",
|
|
398
|
+
"0C00010000020015",
|
|
399
|
+
"0C00010000020016",
|
|
400
|
+
"0C00010000020019",
|
|
401
|
+
"0C0001000002001A",
|
|
402
|
+
"0C0001000002001B",
|
|
403
|
+
"0C0003000003001A",
|
|
404
|
+
"0C00040000020006",
|
|
405
|
+
],
|
|
406
|
+
};
|
package/src/safety/print-file.ts
CHANGED
|
@@ -57,6 +57,11 @@ function stripComments(line: string): string {
|
|
|
57
57
|
/** A deliberately narrow parameter lexer for commands affecting heat/material selection.
|
|
58
58
|
* Unknown nonthermal vendor commands do not require a global G-code allowlist.
|
|
59
59
|
*/
|
|
60
|
+
/** True when the parameter is present, finite and within [low, high]. */
|
|
61
|
+
function inRange(args: Map<string,number>, key: string, low: number, high: number): boolean {
|
|
62
|
+
const value=args.get(key);
|
|
63
|
+
return value!==undefined && Number.isFinite(value) && value>=low && value<=high;
|
|
64
|
+
}
|
|
60
65
|
function parameters(text: string, flags = ''): Map<string,number> {
|
|
61
66
|
const result=new Map<string,number>();let rest=text.trim();
|
|
62
67
|
while(rest) {
|
|
@@ -292,6 +297,26 @@ export async function inspectPrintFile(filePath: string, options: {model:string;
|
|
|
292
297
|
if(!command) fail(`unsupported G-code command syntax '${line.slice(0,100)}'`);
|
|
293
298
|
const [base,subcode]=command![2].split('.');
|
|
294
299
|
const code=command![1].toUpperCase()+String(Number(base))+(subcode===undefined?'':'.'+subcode);const argumentsText=line.slice(command![0].length).trim();commandCount++;
|
|
300
|
+
if(code==='M9711') {
|
|
301
|
+
// Official X2D and H2D time-lapse capture (Bambu Studio and OrcaSlicer
|
|
302
|
+
// templates), emitted on every layer: M is the time-lapse mode and E the
|
|
303
|
+
// physical extruder, not a second command; T is a duration, not a heater.
|
|
304
|
+
// It sets no temperature. Positions stay inside the machine volume.
|
|
305
|
+
const invalid=()=>fail('unsupported M9711 time-lapse parameters');
|
|
306
|
+
if(!['x2d','h2d'].includes(model)) invalid();
|
|
307
|
+
let args:Map<string,number>;
|
|
308
|
+
try {args=parameters(argumentsText);} catch {return invalid();}
|
|
309
|
+
const [width,depth,height]=MACHINE_LIMITS[model].volume;
|
|
310
|
+
const position=['X','Y','U','V'].filter(key=>args.has(key)).join('');
|
|
311
|
+
const limit:Record<string,number>={X:width,U:width,Y:depth,V:depth};
|
|
312
|
+
if([...args.keys()].some(key=>!'MEXYUVZSCOT'.includes(key)) ||
|
|
313
|
+
// X2D and H2D have two physical extruders, whatever the file declares.
|
|
314
|
+
![0,1].includes(args.get('M')!) || !Number.isInteger(args.get('E')) || !inRange(args,'E',0,Math.min(nozzleDiameters.length,2)-1) ||
|
|
315
|
+
args.get('S')!==11 || args.get('C')!==10 || args.get('O')!==0 || !inRange(args,'T',0,600000) ||
|
|
316
|
+
!inRange(args,'Z',0,height) ||
|
|
317
|
+
!['','XY','UV'].includes(position) || [...position].some(key=>!inRange(args,key,0,limit[key]))) invalid();
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
295
320
|
if(!['M117','M118','M1002','M1006','M900','M970','M983.1','G383'].includes(code) && /[GM]\s*\d/i.test(argumentsText)) fail('multiple commands on one line are unsupported');
|
|
296
321
|
// Coordinated extrusion closes the startup-only allowance even when the
|
|
297
322
|
// source omits layer comments. Retraction also closes it conservatively.
|
|
@@ -409,7 +434,18 @@ export async function inspectPrintFile(filePath: string, options: {model:string;
|
|
|
409
434
|
const args=parameters(argumentsText);
|
|
410
435
|
if(!args.has('S') || !args.has('L') || !args.has('T') || !Number.isInteger(args.get('T')) || args.get('T')!<0 || args.get('T')!>=nozzleDiameters.length) fail('unsupported M620.17 nozzle/material temperature mapping');
|
|
411
436
|
for(const key of args.keys()) if(!'SLT'.includes(key)) fail(`unsupported M620.17 parameter ${key}`);
|
|
412
|
-
|
|
437
|
+
if(args.get('L')===-1 && model==='x2d') {
|
|
438
|
+
// OrcaSlicer's X2D start G-code preheats a nozzle with no assigned
|
|
439
|
+
// filament (L-1) for toolhead-offset calibration; Bambu Studio writes
|
|
440
|
+
// L0 there. No project filament is selected, so check the target
|
|
441
|
+
// against the hardware limit and every declared material, like other
|
|
442
|
+
// heater commands without a verified filament. That is never looser
|
|
443
|
+
// than an accepted L<position> form. Material left in the nozzle from
|
|
444
|
+
// an earlier job is live state that no file can declare. T names one
|
|
445
|
+
// of the two physical nozzles, whatever the file declares.
|
|
446
|
+
if(args.get('T')!>1) fail('unsupported M620.17 nozzle/material temperature mapping');
|
|
447
|
+
heat('nozzle',args.get('S')!,undefined,true);
|
|
448
|
+
} else heat('nozzle',args.get('S')!,requirePosition(args.get('L')!));
|
|
413
449
|
} else if((/^M620\./.test(code) && !['M620.3','M620.6','M620.11'].includes(code)) || /^M621\./.test(code)) {
|
|
414
450
|
fail(`unsupported thermal-affecting filament command ${code}`);
|
|
415
451
|
} else if(code==='M145') {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HIDDEN_HMS } from "./hidden-hms.js";
|
|
1
2
|
import { BambuClient, GetVersionCommand, PushAllCommand } from "bambu-node";
|
|
2
3
|
import { normalizeMaterial, normalizeModel } from "./limits.js";
|
|
3
4
|
|
|
@@ -6,7 +7,7 @@ const MODEL_IDS: Record<string, string> = {
|
|
|
6
7
|
O1C: "H2C", O1C2: "H2C", O1D: "H2D", O1E: "H2D Pro", O1S: "H2S", N6: "X2D",
|
|
7
8
|
N2S: "A1", A1M: "A1 Mini", C11: "P1P", C12: "P1S", C13: "X1E", "BL-P001": "X1C", "BL-P002": "X1",
|
|
8
9
|
};
|
|
9
|
-
const SERIAL_MODELS: Record<string, string> = {
|
|
10
|
+
export const SERIAL_MODELS: Record<string, string> = {
|
|
10
11
|
"093": "H2S", "094": "H2D", "31B": "H2C", "239": "H2D Pro", "20P": "X2D",
|
|
11
12
|
"00M": "X1C", "00W": "X1", "03W": "X1E", "01S": "P1P", "01P": "P1S", "22E": "P2S", "039": "A1", "030": "A1 Mini",
|
|
12
13
|
};
|
|
@@ -183,24 +184,95 @@ function reportedNozzles(raw: Record<string,any>): ReportedNozzle[] {
|
|
|
183
184
|
return output;
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
|
|
187
|
+
const hiddenHmsSets = new Map(Object.entries(HIDDEN_HMS).map(([prefix, codes]) => [prefix, new Set(codes)]));
|
|
188
|
+
|
|
189
|
+
type HmsEntry = { attr: number; code: number; severity: number; hex: string };
|
|
190
|
+
function hmsCode(entry: any): HmsEntry | undefined {
|
|
191
|
+
const code = finiteNumber(entry?.code);
|
|
192
|
+
const attr = finiteNumber(entry?.attr);
|
|
193
|
+
const valid = (value: number | undefined): value is number => value !== undefined && Number.isInteger(value) && value >= 0 && value <= 0xffffffff;
|
|
194
|
+
if (!valid(code) || !valid(attr)) return undefined;
|
|
195
|
+
// BambuStudio DevHMS.cpp: code >> 16; DevHMS.h: 1=fatal,2=serious,3=common,4=info.
|
|
196
|
+
return { attr, code, severity: Math.floor(code / 65536), hex: (attr.toString(16).padStart(8, "0") + code.toString(16).padStart(8, "0")).toUpperCase() };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const hmsLabel = (hex: string) => `HMS ${hex.slice(0, 4)}-${hex.slice(4, 8)}-${hex.slice(8, 12)}-${hex.slice(12)}`;
|
|
200
|
+
const hmsKey = (entry: HmsEntry) => `hms:${entry.attr}:${entry.code}`;
|
|
201
|
+
|
|
202
|
+
/** The one key format for a reported entry, shared by the gate and clear_hms_errors. */
|
|
203
|
+
export function reportedHmsKey(entry: unknown): string {
|
|
204
|
+
const parsed = hmsCode(entry);
|
|
205
|
+
return parsed ? hmsKey(parsed) : `hms:${(entry as any)?.attr}:${(entry as any)?.code}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Bambu's display form of a cleared-code key such as "hms:83887616:131184"; other keys unchanged. */
|
|
209
|
+
export function describeHmsKey(key: string): string {
|
|
210
|
+
const match = /^hms:(\d+):(\d+)$/.exec(key);
|
|
211
|
+
const entry = match ? hmsCode({ attr: Number(match[1]), code: Number(match[2]) }) : undefined;
|
|
212
|
+
return entry ? hmsLabel(entry.hex) : key;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A serious or common entry that Bambu lists for this serial prefix with empty
|
|
217
|
+
* text: Bambu Studio, Bambu Handy and the printer screen never show it, and
|
|
218
|
+
* the printer keeps printing with it (an X2D raises 0500-0600-0002-0070 at
|
|
219
|
+
* every boot). Bambu Studio selects the table by serial prefix, so this does
|
|
220
|
+
* too, but only when that prefix belongs to the verified model: a sparse report
|
|
221
|
+
* without a serial must not let a misconfigured serial pick another model's
|
|
222
|
+
* table. Fatal, invalid-level and unlisted entries never qualify.
|
|
223
|
+
*/
|
|
224
|
+
function isHiddenNotice(serial: string, model: string, entry: HmsEntry): boolean {
|
|
225
|
+
if (entry.severity !== 2 && entry.severity !== 3) return false;
|
|
226
|
+
const prefix = serial.trim().toUpperCase().slice(0, 3);
|
|
227
|
+
const normalized = normalizeModel(model);
|
|
228
|
+
if (!normalized || normalizeModel(SERIAL_MODELS[prefix] ?? "") !== normalized) return false;
|
|
229
|
+
return hiddenHmsSets.get(prefix)?.has(entry.hex) === true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function hiddenHmsEntries(raw: unknown, serial: string, model: string): HmsEntry[] {
|
|
233
|
+
if (!object(raw)) return [];
|
|
234
|
+
const identity = observedIdentity(raw, serial);
|
|
235
|
+
if (!identity.observedSerial || identity.model !== normalizeModel(model)) return [];
|
|
236
|
+
const entries = Array.isArray(raw.hms) ? raw.hms : [];
|
|
237
|
+
const seen = new Set<string>();
|
|
238
|
+
return entries.map(hmsCode).filter((entry): entry is HmsEntry =>
|
|
239
|
+
!!entry && isHiddenNotice(identity.observedSerial, model, entry) && !seen.has(entry.hex) && !!seen.add(entry.hex));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Labels of reported entries that pass only because Bambu hides them for this printer. */
|
|
243
|
+
export function hiddenHmsNotices(raw: unknown, serial: string, model: string): string[] {
|
|
244
|
+
return hiddenHmsEntries(raw, serial, model).map(entry => hmsLabel(entry.hex));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Cleared-code keys of reported hidden entries; clearing never removes them, so they are not re-acknowledged. */
|
|
248
|
+
export function hiddenHmsKeys(raw: unknown, serial: string, model: string): string[] {
|
|
249
|
+
return hiddenHmsEntries(raw, serial, model).map(hmsKey);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const loggedHiddenNotices = new Set<string>();
|
|
253
|
+
/** Name each hidden entry a check passed once per printer on stderr, so headless runs still record it. */
|
|
254
|
+
function logHiddenNotices(raw: Record<string, any>, serial: string, model: string): void {
|
|
255
|
+
for (const entry of hiddenHmsEntries(raw, serial, model)) {
|
|
256
|
+
const key = `${serial}:${entry.hex}`;
|
|
257
|
+
if (loggedHiddenNotices.has(key)) continue;
|
|
258
|
+
loggedHiddenNotices.add(key);
|
|
259
|
+
console.error(`Printer ${serial} reports ${hmsLabel(entry.hex)}, which Bambu lists for it without text and never shows as a fault; it does not block operations.`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function validateErrors(raw: Record<string,any>, requireIdle: boolean, serial: string, model: string) {
|
|
187
264
|
const state = typeof raw.gcode_state === "string" ? raw.gcode_state.toUpperCase() : "UNKNOWN";
|
|
188
265
|
if (requireIdle && !["IDLE","FINISH"].includes(state)) throw new Error(`Printer state ${state} is not safely idle.`);
|
|
189
266
|
if (!requireIdle && !["IDLE","FINISH","RUNNING","PAUSE"].includes(state)) throw new Error(`Printer state ${state} is unknown or failed.`);
|
|
190
267
|
if (finiteNumber(raw.print_error) !== 0) throw new Error("Printer reports a print error or did not report its error state.");
|
|
191
268
|
if (!Array.isArray(raw.hms)) throw new Error("Printer HMS error state is missing or malformed.");
|
|
192
|
-
for (const
|
|
193
|
-
const
|
|
194
|
-
const attr = finiteNumber(entry?.attr);
|
|
195
|
-
// BambuStudio DevHMS.cpp: code >> 16; DevHMS.h: 1=fatal,2=serious,3=common,4=info.
|
|
269
|
+
for (const reported of raw.hms) {
|
|
270
|
+
const entry = hmsCode(reported);
|
|
196
271
|
// Unknown severities fail closed; attr identifies the module and is not a severity bitmask.
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
const label = code !== undefined && attr !== undefined && Number.isInteger(code) && Number.isInteger(attr) && code >= 0 && attr >= 0
|
|
200
|
-
? ` (HMS ${hex(attr).slice(0, 4)}-${hex(attr).slice(4)}-${hex(code).slice(0, 4)}-${hex(code).slice(4)})` : "";
|
|
201
|
-
throw new Error(`Printer reports actionable or unknown HMS errors${label}; inspect and resolve them before proceeding.`);
|
|
202
|
-
}
|
|
272
|
+
if (entry && (entry.severity === 4 || isHiddenNotice(serial, model, entry))) continue;
|
|
273
|
+
throw new Error(`Printer reports actionable or unknown HMS errors${entry ? ` (${hmsLabel(entry.hex)})` : ""}; inspect and resolve them before proceeding.`);
|
|
203
274
|
}
|
|
275
|
+
logHiddenNotices(raw, serial, model);
|
|
204
276
|
}
|
|
205
277
|
function trayRange(value: unknown, label: string): number | undefined {
|
|
206
278
|
if (value === undefined || value === null || value === "" || value === "0" || value === 0) return undefined;
|
|
@@ -248,7 +320,7 @@ export function validatePrinterState(status: any, requirements: PrinterStateRequ
|
|
|
248
320
|
const identity = observedIdentity(raw, String(status.serial ?? ""));
|
|
249
321
|
const model = normalizeModel(requirements.model);
|
|
250
322
|
if (!model || !identity.model || model !== identity.model) throw new Error(`Printer model identity mismatch: requested ${requirements.model}, observed ${identity.model ?? "unknown"}.`);
|
|
251
|
-
validateErrors(raw, requirements.requireIdle !== false);
|
|
323
|
+
validateErrors(raw, requirements.requireIdle !== false, identity.observedSerial ?? "", model);
|
|
252
324
|
if (requirements.requireLoadedFilament && (raw.ams?.tray_now !== undefined || Array.isArray(raw.ams?.ams))) {
|
|
253
325
|
const current = finiteNumber(raw.ams?.tray_now);
|
|
254
326
|
if (current === 255) throw new Error("The nozzle is explicitly reported unloaded. Load the declared filament before starting or resuming this G-code job.");
|
|
@@ -335,6 +335,37 @@ function enforceMatchingNozzles(arr: unknown[]): void {
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
/**
|
|
339
|
+
* BambuStudio picks each per-variant machine, process, and filament value
|
|
340
|
+
* (e.g. filament_max_volumetric_speed) by "<extruder_type> <nozzle_volume_type>"
|
|
341
|
+
* among the variants each extruder declares in extruder_variant_list. A flow
|
|
342
|
+
* type the preset doesn't declare has no values to pick, so refuse it instead
|
|
343
|
+
* of labelling a job for a nozzle it wasn't sliced for.
|
|
344
|
+
*/
|
|
345
|
+
function assertNozzleVariantDeclared(
|
|
346
|
+
flat: Record<string, unknown>,
|
|
347
|
+
volumeType: string,
|
|
348
|
+
machineLeaf: string
|
|
349
|
+
): void {
|
|
350
|
+
const strings = (key: string): string[] =>
|
|
351
|
+
Array.isArray(flat[key]) ? (flat[key] as unknown[]).map(String) : [];
|
|
352
|
+
const extruderTypes = strings("extruder_type");
|
|
353
|
+
const declaredLists = strings("extruder_variant_list");
|
|
354
|
+
const count = (flat["nozzle_volume_type"] as unknown[]).length;
|
|
355
|
+
for (let i = 0; i < count; i++) {
|
|
356
|
+
const extruderType = extruderTypes[i] ?? extruderTypes[0];
|
|
357
|
+
const declared = (declaredLists[i] ?? declaredLists[0] ?? "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
358
|
+
const matches = (variant: string) =>
|
|
359
|
+
extruderType ? variant === `${extruderType} ${volumeType}` : variant.endsWith(` ${volumeType}`);
|
|
360
|
+
// Presets without variant declarations only have Standard values.
|
|
361
|
+
if (declared.length === 0 ? volumeType === "Standard" : declared.some(matches)) continue;
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Slicing for a ${volumeType} nozzle is refused: "${machineLeaf}" declares no ${volumeType} variant ` +
|
|
364
|
+
`for extruder ${i + 1} (declared: ${declared.join(", ") || "none"}).`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
338
369
|
/** Best-effort extruder count for fallback nozzle_volume_type sizing. */
|
|
339
370
|
/** BambuStudio's built-in filament_colour default. */
|
|
340
371
|
export const DEFAULT_FILAMENT_COLOUR = "#00AE42";
|
|
@@ -687,6 +718,7 @@ export async function flattenForCli(opts: FlattenOptions): Promise<FlattenedProf
|
|
|
687
718
|
|
|
688
719
|
// CLI-specific post-processing on machine profile only.
|
|
689
720
|
deriveNozzleVolumeType(machineFlat, opts.nozzleVolumeType);
|
|
721
|
+
if (opts.nozzleVolumeType) assertNozzleVariantDeclared(machineFlat, opts.nozzleVolumeType, opts.machineLeaf);
|
|
690
722
|
applyMachineModelBedMetadata(machineFlat, machineIndex);
|
|
691
723
|
// A standalone custom machine can accompany process/filament profiles that
|
|
692
724
|
// need BBL resolution; do not impose bundled model config on that machine.
|
|
@@ -166,6 +166,7 @@ export interface BambuSliceOptions {
|
|
|
166
166
|
filamentColours?: string[]; // positional #RRGGBB per loaded filament slot
|
|
167
167
|
bedType?: string; // Bambu bed type, e.g. textured_plate or cool_plate
|
|
168
168
|
nozzleType?: string; // installed hotend material written into the machine preset
|
|
169
|
+
nozzleVolumeType?: 'Standard' | 'High Flow'; // installed nozzle flow, the machine preset's nozzle_volume_type
|
|
169
170
|
enableTimelapse?: boolean; // --enable-timelapse: timelapse-aware slicing
|
|
170
171
|
allowMixTemp?: boolean; // --allow-mix-temp: allow mixed-temp filaments
|
|
171
172
|
scale?: number; // --scale factor: uniform scale
|
|
@@ -563,6 +564,7 @@ export class STLManipulator extends EventEmitter {
|
|
|
563
564
|
userProfileRoots: configuredBambuProfileDirs(),
|
|
564
565
|
tempDir: this.tempDir,
|
|
565
566
|
bedType: this.resolveBambuStudioBedType(bambuOptions?.bedType),
|
|
567
|
+
nozzleVolumeType: bambuOptions?.nozzleVolumeType,
|
|
566
568
|
filamentColours: bambuOptions?.filamentColours ?? bundle.filamentColours,
|
|
567
569
|
projectTowerPosition: bundle.projectTowerPosition,
|
|
568
570
|
sourceProfiles: {
|