bambu-printer-mcp 1.1.26 → 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 +43 -8
- package/dist/bambu-native.d.ts +55 -0
- package/dist/bambu-native.js +212 -13
- package/dist/index.js +154 -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 +54 -4
- package/dist/safety/printer-state.d.ts +9 -0
- package/dist/safety/printer-state.js +80 -13
- package/dist/slicer/profile-files.d.ts +2 -0
- package/dist/slicer/profile-files.js +24 -0
- package/dist/slicer/profile-flatten.js +27 -13
- package/dist/stl/stl-manipulator.d.ts +1 -0
- package/dist/stl/stl-manipulator.js +6 -8
- 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 +152 -25
- package/src/printers/bambu.ts +51 -12
- package/src/safety/hidden-hms.ts +406 -0
- package/src/safety/print-file.ts +43 -4
- package/src/safety/printer-state.ts +85 -13
- package/src/slicer/profile-files.ts +21 -0
- package/src/slicer/profile-flatten.ts +34 -11
- package/src/stl/stl-manipulator.ts +7 -10
- package/scripts/build-bambu-native.zsh +0 -33
|
@@ -0,0 +1,405 @@
|
|
|
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
|
+
};
|
|
17
|
+
export const HIDDEN_HMS = {
|
|
18
|
+
"093": [
|
|
19
|
+
"0300180000000000",
|
|
20
|
+
"050001000001000A",
|
|
21
|
+
"0500010000020003",
|
|
22
|
+
"0500010000020008",
|
|
23
|
+
"0500010000020009",
|
|
24
|
+
"0500010000030009",
|
|
25
|
+
"0500030000010053",
|
|
26
|
+
"0500030000020010",
|
|
27
|
+
"0500030000020011",
|
|
28
|
+
"0500030000020012",
|
|
29
|
+
"0500030000020013",
|
|
30
|
+
"0500030000020014",
|
|
31
|
+
"0500030000020015",
|
|
32
|
+
"0500030000020016",
|
|
33
|
+
"0500030000020017",
|
|
34
|
+
"0500030000020018",
|
|
35
|
+
"0500030000030007",
|
|
36
|
+
"0500040000020020",
|
|
37
|
+
"0500050000010008",
|
|
38
|
+
"0500050000010009",
|
|
39
|
+
"050005000001000A",
|
|
40
|
+
"050005000001000B",
|
|
41
|
+
"050005000001000C",
|
|
42
|
+
"0500050000020030",
|
|
43
|
+
"0500060000020005",
|
|
44
|
+
"0500060000020007",
|
|
45
|
+
"0500060000020008",
|
|
46
|
+
"0500060000020011",
|
|
47
|
+
"0500060000020012",
|
|
48
|
+
"0500060000020013",
|
|
49
|
+
"0500060000020014",
|
|
50
|
+
"0500060000020021",
|
|
51
|
+
"0500060000020022",
|
|
52
|
+
"0500060000020023",
|
|
53
|
+
"0500060000020024",
|
|
54
|
+
"0500060000020041",
|
|
55
|
+
"0500060000020043",
|
|
56
|
+
"0500060000020044",
|
|
57
|
+
"0500060000020051",
|
|
58
|
+
"0500060000020053",
|
|
59
|
+
"0500060000020054",
|
|
60
|
+
"0500060000020061",
|
|
61
|
+
"0500060000020062",
|
|
62
|
+
"05000600000200A0",
|
|
63
|
+
"05000600000200A1",
|
|
64
|
+
"0C00010000010018",
|
|
65
|
+
"0C0001000002000E",
|
|
66
|
+
"0C00010000020015",
|
|
67
|
+
"0C00010000020016",
|
|
68
|
+
"0C00010000020019",
|
|
69
|
+
"0C0001000002001A",
|
|
70
|
+
"0C0001000002001B",
|
|
71
|
+
"0C00040000020006",
|
|
72
|
+
],
|
|
73
|
+
"094": [
|
|
74
|
+
"0300180000000000",
|
|
75
|
+
"0300260000000000",
|
|
76
|
+
"050001000001000A",
|
|
77
|
+
"0500010000020003",
|
|
78
|
+
"0500010000020008",
|
|
79
|
+
"0500010000020009",
|
|
80
|
+
"0500010000030009",
|
|
81
|
+
"0500030000020010",
|
|
82
|
+
"0500030000020011",
|
|
83
|
+
"0500030000020012",
|
|
84
|
+
"0500030000020013",
|
|
85
|
+
"0500030000020014",
|
|
86
|
+
"0500030000020015",
|
|
87
|
+
"0500030000020016",
|
|
88
|
+
"0500030000020017",
|
|
89
|
+
"0500030000020018",
|
|
90
|
+
"0500030000030007",
|
|
91
|
+
"0500040000020020",
|
|
92
|
+
"0500050000010008",
|
|
93
|
+
"0500050000010009",
|
|
94
|
+
"050005000001000A",
|
|
95
|
+
"050005000001000B",
|
|
96
|
+
"050005000001000C",
|
|
97
|
+
"0500060000020001",
|
|
98
|
+
"0500060000020002",
|
|
99
|
+
"0500060000020003",
|
|
100
|
+
"0500060000020005",
|
|
101
|
+
"0500060000020006",
|
|
102
|
+
"0500060000020007",
|
|
103
|
+
"0500060000020008",
|
|
104
|
+
"0500060000020011",
|
|
105
|
+
"0500060000020012",
|
|
106
|
+
"0500060000020013",
|
|
107
|
+
"0500060000020014",
|
|
108
|
+
"0500060000020021",
|
|
109
|
+
"0500060000020022",
|
|
110
|
+
"0500060000020023",
|
|
111
|
+
"0500060000020024",
|
|
112
|
+
"0500060000020041",
|
|
113
|
+
"0500060000020042",
|
|
114
|
+
"0500060000020043",
|
|
115
|
+
"0500060000020044",
|
|
116
|
+
"0500060000020051",
|
|
117
|
+
"0500060000020052",
|
|
118
|
+
"0500060000020053",
|
|
119
|
+
"0500060000020054",
|
|
120
|
+
"0500060000020061",
|
|
121
|
+
"0500060000020062",
|
|
122
|
+
"05000600000200A0",
|
|
123
|
+
"05000600000200A1",
|
|
124
|
+
"0C00010000020015",
|
|
125
|
+
"0C00010000020016",
|
|
126
|
+
"0C00010000020019",
|
|
127
|
+
"0C0001000002001A",
|
|
128
|
+
"0C0001000002001B",
|
|
129
|
+
"0C00040000020006",
|
|
130
|
+
],
|
|
131
|
+
"20P": [
|
|
132
|
+
"0300180000000000",
|
|
133
|
+
"0300260000000000",
|
|
134
|
+
"050001000001000A",
|
|
135
|
+
"0500010000020003",
|
|
136
|
+
"0500010000020008",
|
|
137
|
+
"0500010000020009",
|
|
138
|
+
"050001000002000B",
|
|
139
|
+
"050001000002000F",
|
|
140
|
+
"0500010000020010",
|
|
141
|
+
"0500010000020011",
|
|
142
|
+
"0500010000030009",
|
|
143
|
+
"0500020000030009",
|
|
144
|
+
"050002000003000A",
|
|
145
|
+
"050002000003000B",
|
|
146
|
+
"050002000003000C",
|
|
147
|
+
"0500030000010053",
|
|
148
|
+
"0500030000020010",
|
|
149
|
+
"0500030000020011",
|
|
150
|
+
"0500030000020012",
|
|
151
|
+
"0500030000020013",
|
|
152
|
+
"0500030000020014",
|
|
153
|
+
"0500030000020015",
|
|
154
|
+
"0500030000020016",
|
|
155
|
+
"0500030000020017",
|
|
156
|
+
"0500030000020018",
|
|
157
|
+
"0500030000020019",
|
|
158
|
+
"0500030000020050",
|
|
159
|
+
"0500030000020051",
|
|
160
|
+
"0500030000020052",
|
|
161
|
+
"0500030000020053",
|
|
162
|
+
"0500030000020054",
|
|
163
|
+
"0500030000030007",
|
|
164
|
+
"0500040000010056",
|
|
165
|
+
"0500040000010060",
|
|
166
|
+
"0500040000010061",
|
|
167
|
+
"0500040000020020",
|
|
168
|
+
"0500050000010008",
|
|
169
|
+
"0500050000010009",
|
|
170
|
+
"050005000001000A",
|
|
171
|
+
"050005000001000B",
|
|
172
|
+
"050005000001000C",
|
|
173
|
+
"0500050000020023",
|
|
174
|
+
"0500050000020030",
|
|
175
|
+
"0500060000020005",
|
|
176
|
+
"0500060000020006",
|
|
177
|
+
"0500060000020007",
|
|
178
|
+
"0500060000020008",
|
|
179
|
+
"0500060000020009",
|
|
180
|
+
"0500060000020011",
|
|
181
|
+
"0500060000020012",
|
|
182
|
+
"0500060000020013",
|
|
183
|
+
"0500060000020014",
|
|
184
|
+
"0500060000020015",
|
|
185
|
+
"0500060000020021",
|
|
186
|
+
"0500060000020022",
|
|
187
|
+
"0500060000020023",
|
|
188
|
+
"0500060000020024",
|
|
189
|
+
"0500060000020025",
|
|
190
|
+
"0500060000020035",
|
|
191
|
+
"0500060000020041",
|
|
192
|
+
"0500060000020042",
|
|
193
|
+
"0500060000020044",
|
|
194
|
+
"0500060000020045",
|
|
195
|
+
"0500060000020051",
|
|
196
|
+
"0500060000020052",
|
|
197
|
+
"0500060000020054",
|
|
198
|
+
"0500060000020061",
|
|
199
|
+
"0500060000020062",
|
|
200
|
+
"0500060000020070",
|
|
201
|
+
"0500060000020071",
|
|
202
|
+
"05000600000200A0",
|
|
203
|
+
"05000600000200A1",
|
|
204
|
+
"0500070000020001",
|
|
205
|
+
"0500070000020002",
|
|
206
|
+
"0500070000020003",
|
|
207
|
+
"0500070000020101",
|
|
208
|
+
"0500070000020201",
|
|
209
|
+
"0503000000030027",
|
|
210
|
+
"0503000000030028",
|
|
211
|
+
"0503000000030029",
|
|
212
|
+
"0C00010000010018",
|
|
213
|
+
"0C0001000002000E",
|
|
214
|
+
"0C00010000020015",
|
|
215
|
+
"0C00010000020016",
|
|
216
|
+
"0C00010000020019",
|
|
217
|
+
"0C0001000002001A",
|
|
218
|
+
"0C0001000002001B",
|
|
219
|
+
"0C0003000003001A",
|
|
220
|
+
"0C00040000020006",
|
|
221
|
+
],
|
|
222
|
+
"22E": [
|
|
223
|
+
"0300180000000000",
|
|
224
|
+
"050001000001000A",
|
|
225
|
+
"0500010000020003",
|
|
226
|
+
"0500010000020008",
|
|
227
|
+
"0500010000020009",
|
|
228
|
+
"0500010000030009",
|
|
229
|
+
"0500030000010053",
|
|
230
|
+
"0500030000020010",
|
|
231
|
+
"0500030000020011",
|
|
232
|
+
"0500030000020012",
|
|
233
|
+
"0500030000020013",
|
|
234
|
+
"0500030000020014",
|
|
235
|
+
"0500030000020015",
|
|
236
|
+
"0500030000020016",
|
|
237
|
+
"0500030000020017",
|
|
238
|
+
"0500030000020018",
|
|
239
|
+
"0500030000030007",
|
|
240
|
+
"0500040000010056",
|
|
241
|
+
"0500040000020020",
|
|
242
|
+
"0500050000010008",
|
|
243
|
+
"0500050000010009",
|
|
244
|
+
"050005000001000A",
|
|
245
|
+
"050005000001000B",
|
|
246
|
+
"050005000001000C",
|
|
247
|
+
"0500050000020030",
|
|
248
|
+
"0500060000020005",
|
|
249
|
+
"0500060000020007",
|
|
250
|
+
"0500060000020008",
|
|
251
|
+
"0500060000020011",
|
|
252
|
+
"0500060000020012",
|
|
253
|
+
"0500060000020013",
|
|
254
|
+
"0500060000020014",
|
|
255
|
+
"0500060000020021",
|
|
256
|
+
"0500060000020022",
|
|
257
|
+
"0500060000020023",
|
|
258
|
+
"0500060000020024",
|
|
259
|
+
"0500060000020044",
|
|
260
|
+
"0500060000020054",
|
|
261
|
+
"0500060000020061",
|
|
262
|
+
"0500060000020062",
|
|
263
|
+
"05000600000200A0",
|
|
264
|
+
"05000600000200A1",
|
|
265
|
+
"0500070000020001",
|
|
266
|
+
"0500070000020002",
|
|
267
|
+
"0500070000020003",
|
|
268
|
+
"0500070000020101",
|
|
269
|
+
"0500070000020201",
|
|
270
|
+
"0503000000030027",
|
|
271
|
+
"0503000000030028",
|
|
272
|
+
"0503000000030029",
|
|
273
|
+
"0C00010000010018",
|
|
274
|
+
"0C0001000002000E",
|
|
275
|
+
"0C00010000020015",
|
|
276
|
+
"0C00010000020016",
|
|
277
|
+
"0C00010000020019",
|
|
278
|
+
"0C0001000002001A",
|
|
279
|
+
"0C0001000002001B",
|
|
280
|
+
"0C00040000020006",
|
|
281
|
+
],
|
|
282
|
+
"239": [
|
|
283
|
+
"0300180000000000",
|
|
284
|
+
"0300260000000000",
|
|
285
|
+
"0500010000020003",
|
|
286
|
+
"0500010000020008",
|
|
287
|
+
"0500010000020009",
|
|
288
|
+
"0500010000030009",
|
|
289
|
+
"0500030000020010",
|
|
290
|
+
"0500030000020011",
|
|
291
|
+
"0500030000020012",
|
|
292
|
+
"0500030000020013",
|
|
293
|
+
"0500030000020014",
|
|
294
|
+
"0500030000020015",
|
|
295
|
+
"0500030000020016",
|
|
296
|
+
"0500030000020017",
|
|
297
|
+
"0500030000020018",
|
|
298
|
+
"0500030000030007",
|
|
299
|
+
"0500040000020020",
|
|
300
|
+
"0500050000010008",
|
|
301
|
+
"0500050000010009",
|
|
302
|
+
"050005000001000A",
|
|
303
|
+
"050005000001000B",
|
|
304
|
+
"050005000001000C",
|
|
305
|
+
"0500060000020005",
|
|
306
|
+
"0500060000020006",
|
|
307
|
+
"0500060000020007",
|
|
308
|
+
"0500060000020008",
|
|
309
|
+
"0500060000020011",
|
|
310
|
+
"0500060000020012",
|
|
311
|
+
"0500060000020013",
|
|
312
|
+
"0500060000020014",
|
|
313
|
+
"0500060000020021",
|
|
314
|
+
"0500060000020022",
|
|
315
|
+
"0500060000020023",
|
|
316
|
+
"0500060000020024",
|
|
317
|
+
"0500060000020041",
|
|
318
|
+
"0500060000020042",
|
|
319
|
+
"0500060000020043",
|
|
320
|
+
"0500060000020044",
|
|
321
|
+
"0500060000020051",
|
|
322
|
+
"0500060000020052",
|
|
323
|
+
"0500060000020053",
|
|
324
|
+
"0500060000020054",
|
|
325
|
+
"0500060000020061",
|
|
326
|
+
"0500060000020062",
|
|
327
|
+
"05000600000200A0",
|
|
328
|
+
"05000600000200A1",
|
|
329
|
+
"0C00010000020015",
|
|
330
|
+
"0C00010000020016",
|
|
331
|
+
"0C00010000020019",
|
|
332
|
+
"0C0001000002001A",
|
|
333
|
+
"0C0001000002001B",
|
|
334
|
+
"0C00040000020006",
|
|
335
|
+
],
|
|
336
|
+
"31B": [
|
|
337
|
+
"0300180000000000",
|
|
338
|
+
"0300260000000000",
|
|
339
|
+
"050001000001000A",
|
|
340
|
+
"0500010000020003",
|
|
341
|
+
"0500010000020008",
|
|
342
|
+
"0500010000020009",
|
|
343
|
+
"0500010000030009",
|
|
344
|
+
"0500030000010053",
|
|
345
|
+
"0500030000020010",
|
|
346
|
+
"0500030000020011",
|
|
347
|
+
"0500030000020012",
|
|
348
|
+
"0500030000020013",
|
|
349
|
+
"0500030000020014",
|
|
350
|
+
"0500030000020015",
|
|
351
|
+
"0500030000020016",
|
|
352
|
+
"0500030000020017",
|
|
353
|
+
"0500030000020018",
|
|
354
|
+
"0500030000030007",
|
|
355
|
+
"0500040000010056",
|
|
356
|
+
"0500040000020020",
|
|
357
|
+
"0500050000010008",
|
|
358
|
+
"0500050000010009",
|
|
359
|
+
"050005000001000A",
|
|
360
|
+
"050005000001000B",
|
|
361
|
+
"050005000001000C",
|
|
362
|
+
"0500050000020030",
|
|
363
|
+
"0500060000020005",
|
|
364
|
+
"0500060000020006",
|
|
365
|
+
"0500060000020007",
|
|
366
|
+
"0500060000020008",
|
|
367
|
+
"0500060000020011",
|
|
368
|
+
"0500060000020012",
|
|
369
|
+
"0500060000020013",
|
|
370
|
+
"0500060000020014",
|
|
371
|
+
"0500060000020021",
|
|
372
|
+
"0500060000020022",
|
|
373
|
+
"0500060000020023",
|
|
374
|
+
"0500060000020024",
|
|
375
|
+
"0500060000020041",
|
|
376
|
+
"0500060000020042",
|
|
377
|
+
"0500060000020043",
|
|
378
|
+
"0500060000020044",
|
|
379
|
+
"0500060000020051",
|
|
380
|
+
"0500060000020052",
|
|
381
|
+
"0500060000020053",
|
|
382
|
+
"0500060000020054",
|
|
383
|
+
"0500060000020061",
|
|
384
|
+
"0500060000020062",
|
|
385
|
+
"05000600000200A0",
|
|
386
|
+
"05000600000200A1",
|
|
387
|
+
"0500070000020001",
|
|
388
|
+
"0500070000020002",
|
|
389
|
+
"0500070000020003",
|
|
390
|
+
"0500070000020101",
|
|
391
|
+
"0500070000020201",
|
|
392
|
+
"0503000000030027",
|
|
393
|
+
"0503000000030028",
|
|
394
|
+
"0503000000030029",
|
|
395
|
+
"0C00010000010018",
|
|
396
|
+
"0C0001000002000E",
|
|
397
|
+
"0C00010000020015",
|
|
398
|
+
"0C00010000020016",
|
|
399
|
+
"0C00010000020019",
|
|
400
|
+
"0C0001000002001A",
|
|
401
|
+
"0C0001000002001B",
|
|
402
|
+
"0C0003000003001A",
|
|
403
|
+
"0C00040000020006",
|
|
404
|
+
],
|
|
405
|
+
};
|
|
@@ -62,6 +62,11 @@ function stripComments(line) {
|
|
|
62
62
|
/** A deliberately narrow parameter lexer for commands affecting heat/material selection.
|
|
63
63
|
* Unknown nonthermal vendor commands do not require a global G-code allowlist.
|
|
64
64
|
*/
|
|
65
|
+
/** True when the parameter is present, finite and within [low, high]. */
|
|
66
|
+
function inRange(args, key, low, high) {
|
|
67
|
+
const value = args.get(key);
|
|
68
|
+
return value !== undefined && Number.isFinite(value) && value >= low && value <= high;
|
|
69
|
+
}
|
|
65
70
|
function parameters(text, flags = '') {
|
|
66
71
|
const result = new Map();
|
|
67
72
|
let rest = text.trim();
|
|
@@ -372,10 +377,14 @@ export async function inspectPrintFile(filePath, options) {
|
|
|
372
377
|
if (/[{}\[\]#]/.test(line))
|
|
373
378
|
fail('unresolved dynamic command syntax');
|
|
374
379
|
if (/^SYNC(?=$|\s)/i.test(line)) {
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
// vendor template establishes no independent maximum duration.
|
|
380
|
+
// Vendor SYNC T is a duration, not a heater target. X2D and P2S
|
|
381
|
+
// startup templates additionally emit R0; no other R form is supported.
|
|
378
382
|
const args = parameters(line.slice(4));
|
|
383
|
+
if (args.has('R')) {
|
|
384
|
+
if (!['x2d', 'p2s'].includes(model) || args.get('R') !== 0)
|
|
385
|
+
fail('unsupported SYNC reset parameters');
|
|
386
|
+
args.delete('R');
|
|
387
|
+
}
|
|
379
388
|
if (args.size !== 1 || !args.has('T') || !Number.isFinite(args.get('T')) || args.get('T') < 0)
|
|
380
389
|
fail('unsupported SYNC duration parameters');
|
|
381
390
|
commandCount++;
|
|
@@ -388,6 +397,33 @@ export async function inspectPrintFile(filePath, options) {
|
|
|
388
397
|
const code = command[1].toUpperCase() + String(Number(base)) + (subcode === undefined ? '' : '.' + subcode);
|
|
389
398
|
const argumentsText = line.slice(command[0].length).trim();
|
|
390
399
|
commandCount++;
|
|
400
|
+
if (code === 'M9711') {
|
|
401
|
+
// Official X2D and H2D time-lapse capture (Bambu Studio and OrcaSlicer
|
|
402
|
+
// templates), emitted on every layer: M is the time-lapse mode and E the
|
|
403
|
+
// physical extruder, not a second command; T is a duration, not a heater.
|
|
404
|
+
// It sets no temperature. Positions stay inside the machine volume.
|
|
405
|
+
const invalid = () => fail('unsupported M9711 time-lapse parameters');
|
|
406
|
+
if (!['x2d', 'h2d'].includes(model))
|
|
407
|
+
invalid();
|
|
408
|
+
let args;
|
|
409
|
+
try {
|
|
410
|
+
args = parameters(argumentsText);
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
return invalid();
|
|
414
|
+
}
|
|
415
|
+
const [width, depth, height] = MACHINE_LIMITS[model].volume;
|
|
416
|
+
const position = ['X', 'Y', 'U', 'V'].filter(key => args.has(key)).join('');
|
|
417
|
+
const limit = { X: width, U: width, Y: depth, V: depth };
|
|
418
|
+
if ([...args.keys()].some(key => !'MEXYUVZSCOT'.includes(key)) ||
|
|
419
|
+
// X2D and H2D have two physical extruders, whatever the file declares.
|
|
420
|
+
![0, 1].includes(args.get('M')) || !Number.isInteger(args.get('E')) || !inRange(args, 'E', 0, Math.min(nozzleDiameters.length, 2) - 1) ||
|
|
421
|
+
args.get('S') !== 11 || args.get('C') !== 10 || args.get('O') !== 0 || !inRange(args, 'T', 0, 600000) ||
|
|
422
|
+
!inRange(args, 'Z', 0, height) ||
|
|
423
|
+
!['', 'XY', 'UV'].includes(position) || [...position].some(key => !inRange(args, key, 0, limit[key])))
|
|
424
|
+
invalid();
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
391
427
|
if (!['M117', 'M118', 'M1002', 'M1006', 'M900', 'M970', 'M983.1', 'G383'].includes(code) && /[GM]\s*\d/i.test(argumentsText))
|
|
392
428
|
fail('multiple commands on one line are unsupported');
|
|
393
429
|
// Coordinated extrusion closes the startup-only allowance even when the
|
|
@@ -574,7 +610,21 @@ export async function inspectPrintFile(filePath, options) {
|
|
|
574
610
|
for (const key of args.keys())
|
|
575
611
|
if (!'SLT'.includes(key))
|
|
576
612
|
fail(`unsupported M620.17 parameter ${key}`);
|
|
577
|
-
|
|
613
|
+
if (args.get('L') === -1 && model === 'x2d') {
|
|
614
|
+
// OrcaSlicer's X2D start G-code preheats a nozzle with no assigned
|
|
615
|
+
// filament (L-1) for toolhead-offset calibration; Bambu Studio writes
|
|
616
|
+
// L0 there. No project filament is selected, so check the target
|
|
617
|
+
// against the hardware limit and every declared material, like other
|
|
618
|
+
// heater commands without a verified filament. That is never looser
|
|
619
|
+
// than an accepted L<position> form. Material left in the nozzle from
|
|
620
|
+
// an earlier job is live state that no file can declare. T names one
|
|
621
|
+
// of the two physical nozzles, whatever the file declares.
|
|
622
|
+
if (args.get('T') > 1)
|
|
623
|
+
fail('unsupported M620.17 nozzle/material temperature mapping');
|
|
624
|
+
heat('nozzle', args.get('S'), undefined, true);
|
|
625
|
+
}
|
|
626
|
+
else
|
|
627
|
+
heat('nozzle', args.get('S'), requirePosition(args.get('L')));
|
|
578
628
|
}
|
|
579
629
|
else if ((/^M620\./.test(code) && !['M620.3', 'M620.6', 'M620.11'].includes(code)) || /^M621\./.test(code)) {
|
|
580
630
|
fail(`unsupported thermal-affecting filament command ${code}`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BambuClient } from "bambu-node";
|
|
2
|
+
export declare const SERIAL_MODELS: Record<string, string>;
|
|
2
3
|
/** Only raw reports received after this request are evidence. Never read printer.data here. */
|
|
3
4
|
export declare function readFreshPrinterStatus(printer: BambuClient, serial: string, timeoutMs?: number): Promise<any>;
|
|
4
5
|
export interface PrinterStateRequirements {
|
|
@@ -36,6 +37,14 @@ export interface ValidatedPrinterState {
|
|
|
36
37
|
reportedNozzles: ReportedNozzle[];
|
|
37
38
|
filaments: ReportedFilament[];
|
|
38
39
|
}
|
|
40
|
+
/** The one key format for a reported entry, shared by the gate and clear_hms_errors. */
|
|
41
|
+
export declare function reportedHmsKey(entry: unknown): string;
|
|
42
|
+
/** Bambu's display form of a cleared-code key such as "hms:83887616:131184"; other keys unchanged. */
|
|
43
|
+
export declare function describeHmsKey(key: string): string;
|
|
44
|
+
/** Labels of reported entries that pass only because Bambu hides them for this printer. */
|
|
45
|
+
export declare function hiddenHmsNotices(raw: unknown, serial: string, model: string): string[];
|
|
46
|
+
/** Cleared-code keys of reported hidden entries; clearing never removes them, so they are not re-acknowledged. */
|
|
47
|
+
export declare function hiddenHmsKeys(raw: unknown, serial: string, model: string): string[];
|
|
39
48
|
/** Verifies reported configuration, not a physical nozzle/spool inspection. */
|
|
40
49
|
export declare function validatePrinterState(status: any, requirements: PrinterStateRequirements): ValidatedPrinterState;
|
|
41
50
|
/** Manual M104 cannot remap filament slots. Validate the material already at the nozzle. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HIDDEN_HMS } from "./hidden-hms.js";
|
|
1
2
|
import { GetVersionCommand, PushAllCommand } from "bambu-node";
|
|
2
3
|
import { normalizeMaterial, normalizeModel } from "./limits.js";
|
|
3
4
|
const MAX_REPORT_AGE_MS = 15000;
|
|
@@ -5,7 +6,7 @@ const MODEL_IDS = {
|
|
|
5
6
|
O1C: "H2C", O1C2: "H2C", O1D: "H2D", O1E: "H2D Pro", O1S: "H2S", N6: "X2D",
|
|
6
7
|
N2S: "A1", A1M: "A1 Mini", C11: "P1P", C12: "P1S", C13: "X1E", "BL-P001": "X1C", "BL-P002": "X1",
|
|
7
8
|
};
|
|
8
|
-
const SERIAL_MODELS = {
|
|
9
|
+
export const SERIAL_MODELS = {
|
|
9
10
|
"093": "H2S", "094": "H2D", "31B": "H2C", "239": "H2D Pro", "20P": "X2D",
|
|
10
11
|
"00M": "X1C", "00W": "X1", "03W": "X1E", "01S": "P1P", "01P": "P1S", "22E": "P2S", "039": "A1", "030": "A1 Mini",
|
|
11
12
|
};
|
|
@@ -184,7 +185,77 @@ function reportedNozzles(raw) {
|
|
|
184
185
|
}
|
|
185
186
|
return output;
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
+
const hiddenHmsSets = new Map(Object.entries(HIDDEN_HMS).map(([prefix, codes]) => [prefix, new Set(codes)]));
|
|
189
|
+
function hmsCode(entry) {
|
|
190
|
+
const code = finiteNumber(entry?.code);
|
|
191
|
+
const attr = finiteNumber(entry?.attr);
|
|
192
|
+
const valid = (value) => value !== undefined && Number.isInteger(value) && value >= 0 && value <= 0xffffffff;
|
|
193
|
+
if (!valid(code) || !valid(attr))
|
|
194
|
+
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
|
+
const hmsLabel = (hex) => `HMS ${hex.slice(0, 4)}-${hex.slice(4, 8)}-${hex.slice(8, 12)}-${hex.slice(12)}`;
|
|
199
|
+
const hmsKey = (entry) => `hms:${entry.attr}:${entry.code}`;
|
|
200
|
+
/** The one key format for a reported entry, shared by the gate and clear_hms_errors. */
|
|
201
|
+
export function reportedHmsKey(entry) {
|
|
202
|
+
const parsed = hmsCode(entry);
|
|
203
|
+
return parsed ? hmsKey(parsed) : `hms:${entry?.attr}:${entry?.code}`;
|
|
204
|
+
}
|
|
205
|
+
/** Bambu's display form of a cleared-code key such as "hms:83887616:131184"; other keys unchanged. */
|
|
206
|
+
export function describeHmsKey(key) {
|
|
207
|
+
const match = /^hms:(\d+):(\d+)$/.exec(key);
|
|
208
|
+
const entry = match ? hmsCode({ attr: Number(match[1]), code: Number(match[2]) }) : undefined;
|
|
209
|
+
return entry ? hmsLabel(entry.hex) : key;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* A serious or common entry that Bambu lists for this serial prefix with empty
|
|
213
|
+
* text: Bambu Studio, Bambu Handy and the printer screen never show it, and
|
|
214
|
+
* the printer keeps printing with it (an X2D raises 0500-0600-0002-0070 at
|
|
215
|
+
* every boot). Bambu Studio selects the table by serial prefix, so this does
|
|
216
|
+
* too, but only when that prefix belongs to the verified model: a sparse report
|
|
217
|
+
* without a serial must not let a misconfigured serial pick another model's
|
|
218
|
+
* table. Fatal, invalid-level and unlisted entries never qualify.
|
|
219
|
+
*/
|
|
220
|
+
function isHiddenNotice(serial, model, entry) {
|
|
221
|
+
if (entry.severity !== 2 && entry.severity !== 3)
|
|
222
|
+
return false;
|
|
223
|
+
const prefix = serial.trim().toUpperCase().slice(0, 3);
|
|
224
|
+
const normalized = normalizeModel(model);
|
|
225
|
+
if (!normalized || normalizeModel(SERIAL_MODELS[prefix] ?? "") !== normalized)
|
|
226
|
+
return false;
|
|
227
|
+
return hiddenHmsSets.get(prefix)?.has(entry.hex) === true;
|
|
228
|
+
}
|
|
229
|
+
function hiddenHmsEntries(raw, serial, model) {
|
|
230
|
+
if (!object(raw))
|
|
231
|
+
return [];
|
|
232
|
+
const identity = observedIdentity(raw, serial);
|
|
233
|
+
if (!identity.observedSerial || identity.model !== normalizeModel(model))
|
|
234
|
+
return [];
|
|
235
|
+
const entries = Array.isArray(raw.hms) ? raw.hms : [];
|
|
236
|
+
const seen = new Set();
|
|
237
|
+
return entries.map(hmsCode).filter((entry) => !!entry && isHiddenNotice(identity.observedSerial, model, entry) && !seen.has(entry.hex) && !!seen.add(entry.hex));
|
|
238
|
+
}
|
|
239
|
+
/** Labels of reported entries that pass only because Bambu hides them for this printer. */
|
|
240
|
+
export function hiddenHmsNotices(raw, serial, model) {
|
|
241
|
+
return hiddenHmsEntries(raw, serial, model).map(entry => hmsLabel(entry.hex));
|
|
242
|
+
}
|
|
243
|
+
/** Cleared-code keys of reported hidden entries; clearing never removes them, so they are not re-acknowledged. */
|
|
244
|
+
export function hiddenHmsKeys(raw, serial, model) {
|
|
245
|
+
return hiddenHmsEntries(raw, serial, model).map(hmsKey);
|
|
246
|
+
}
|
|
247
|
+
const loggedHiddenNotices = new Set();
|
|
248
|
+
/** Name each hidden entry a check passed once per printer on stderr, so headless runs still record it. */
|
|
249
|
+
function logHiddenNotices(raw, serial, model) {
|
|
250
|
+
for (const entry of hiddenHmsEntries(raw, serial, model)) {
|
|
251
|
+
const key = `${serial}:${entry.hex}`;
|
|
252
|
+
if (loggedHiddenNotices.has(key))
|
|
253
|
+
continue;
|
|
254
|
+
loggedHiddenNotices.add(key);
|
|
255
|
+
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.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function validateErrors(raw, requireIdle, serial, model) {
|
|
188
259
|
const state = typeof raw.gcode_state === "string" ? raw.gcode_state.toUpperCase() : "UNKNOWN";
|
|
189
260
|
if (requireIdle && !["IDLE", "FINISH"].includes(state))
|
|
190
261
|
throw new Error(`Printer state ${state} is not safely idle.`);
|
|
@@ -194,18 +265,14 @@ function validateErrors(raw, requireIdle) {
|
|
|
194
265
|
throw new Error("Printer reports a print error or did not report its error state.");
|
|
195
266
|
if (!Array.isArray(raw.hms))
|
|
196
267
|
throw new Error("Printer HMS error state is missing or malformed.");
|
|
197
|
-
for (const
|
|
198
|
-
const
|
|
199
|
-
const attr = finiteNumber(entry?.attr);
|
|
200
|
-
// BambuStudio DevHMS.cpp: code >> 16; DevHMS.h: 1=fatal,2=serious,3=common,4=info.
|
|
268
|
+
for (const reported of raw.hms) {
|
|
269
|
+
const entry = hmsCode(reported);
|
|
201
270
|
// Unknown severities fail closed; attr identifies the module and is not a severity bitmask.
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
? ` (HMS ${hex(attr).slice(0, 4)}-${hex(attr).slice(4)}-${hex(code).slice(0, 4)}-${hex(code).slice(4)})` : "";
|
|
206
|
-
throw new Error(`Printer reports actionable or unknown HMS errors${label}; inspect and resolve them before proceeding.`);
|
|
207
|
-
}
|
|
271
|
+
if (entry && (entry.severity === 4 || isHiddenNotice(serial, model, entry)))
|
|
272
|
+
continue;
|
|
273
|
+
throw new Error(`Printer reports actionable or unknown HMS errors${entry ? ` (${hmsLabel(entry.hex)})` : ""}; inspect and resolve them before proceeding.`);
|
|
208
274
|
}
|
|
275
|
+
logHiddenNotices(raw, serial, model);
|
|
209
276
|
}
|
|
210
277
|
function trayRange(value, label) {
|
|
211
278
|
if (value === undefined || value === null || value === "" || value === "0" || value === 0)
|
|
@@ -265,7 +332,7 @@ export function validatePrinterState(status, requirements) {
|
|
|
265
332
|
const model = normalizeModel(requirements.model);
|
|
266
333
|
if (!model || !identity.model || model !== identity.model)
|
|
267
334
|
throw new Error(`Printer model identity mismatch: requested ${requirements.model}, observed ${identity.model ?? "unknown"}.`);
|
|
268
|
-
validateErrors(raw, requirements.requireIdle !== false);
|
|
335
|
+
validateErrors(raw, requirements.requireIdle !== false, identity.observedSerial ?? "", model);
|
|
269
336
|
if (requirements.requireLoadedFilament && (raw.ams?.tray_now !== undefined || Array.isArray(raw.ams?.ams))) {
|
|
270
337
|
const current = finiteNumber(raw.ams?.tray_now);
|
|
271
338
|
if (current === 255)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { lstatSync, readdirSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Discover vendor subfolders without following links into another profile tree. */
|
|
4
|
+
export function profileJsonFiles(directory) {
|
|
5
|
+
let entries;
|
|
6
|
+
try {
|
|
7
|
+
if (lstatSync(directory).isSymbolicLink())
|
|
8
|
+
return [];
|
|
9
|
+
entries = readdirSync(directory, { withFileTypes: true });
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error.code === 'ENOENT')
|
|
13
|
+
return [];
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
17
|
+
const files = entries.filter(entry => entry.isFile() && entry.name.endsWith('.json'))
|
|
18
|
+
.map(entry => path.join(directory, entry.name));
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.isDirectory())
|
|
21
|
+
files.push(...profileJsonFiles(path.join(directory, entry.name)));
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|