bambu-printer-mcp 1.1.33 → 1.1.35
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 +3 -1
- package/README.md +5 -3
- package/dist/bambu-network-bridge.d.ts +78 -4
- package/dist/bambu-network-bridge.js +292 -47
- package/dist/bambu-network-certs.d.ts +13 -0
- package/dist/bambu-network-certs.js +152 -0
- package/dist/index.js +28 -11
- package/dist/printers/bambu.d.ts +12 -6
- package/dist/printers/bambu.js +74 -57
- package/dist/safety/printer-state.d.ts +2 -0
- package/dist/safety/printer-state.js +42 -1
- package/dist/stl/stl-manipulator.d.ts +8 -0
- package/dist/stl/stl-manipulator.js +119 -12
- package/package.json +1 -2
- package/src/bambu-network-bridge.ts +324 -55
- package/src/bambu-network-certs.ts +154 -0
- package/src/index.ts +43 -25
- package/src/printers/bambu.ts +74 -79
- package/src/safety/printer-state.ts +42 -1
- package/src/stl/stl-manipulator.ts +126 -7
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bambu's public certificates from Bambu Studio v02.08.02.61 resources/cert,
|
|
3
|
+
* the folder Bambu Studio and FULU's GUI pass to the networking plug-in's
|
|
4
|
+
* set_cert_file. printer.cer holds the BBL CA roots that sign each printer's
|
|
5
|
+
* LAN MQTT certificate; slicer_base64.cer is the cloud certificate. FULU's
|
|
6
|
+
* standalone Linux host runtime ships only slicer_base64.cer, so its plug-in
|
|
7
|
+
* cannot verify a LAN printer without these. Exact bytes, pinned by SHA-256
|
|
8
|
+
* (the same pins as scripts/setup-bambu-native.mjs).
|
|
9
|
+
*/
|
|
10
|
+
export const BAMBU_NETWORK_CERTIFICATES = Object.freeze({
|
|
11
|
+
"printer.cer": {
|
|
12
|
+
sha256: "36f2bcee347ec7adce719b5fd350099591a4d3d0ec4e039c7019890d78e152a0",
|
|
13
|
+
base64: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZmekNDQTJlZ0F3SUJBZ0lVWHR6UjZ0UmlML1JIQlJYT295RlUwK1hybGlv" +
|
|
14
|
+
"d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1JqRUxNQWtHQTFVRUJoTUNRMDR4SVRBZkJnTlZCQW9NR0VKQ1RDQlVaV05vYm05c2IyZHBa" +
|
|
15
|
+
"WE1nUTI4dQpJRXgwWkRFVU1CSUdBMVVFQXd3TFFrSk1JRU5CTWlCU1UwRXdJQmNOTWpVd05qRTNNREV6T0RBNFdoZ1BNakExCk1E" +
|
|
16
|
+
"QTJNVGN3TVRNNE1EaGFNRVl4Q3pBSkJnTlZCQVlUQWtOT01TRXdId1lEVlFRS0RCaENRa3dnVkdWamFHNXYKYkc5bmFXVnpJRU52" +
|
|
17
|
+
"TGlCTWRHUXhGREFTQmdOVkJBTU1DMEpDVENCRFFUSWdVbE5CTUlJQ0lqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBZzhBTUlJQ0Nn" +
|
|
18
|
+
"S0NBZ0VBbzQ1NTBHNGM0MmdUS3pRcWl4d0tUMDg5Uml6SWRacHlPY0dBCjY3OXJQYU9kV3NNcVZ3bllQUDJGcE1xWEtrakZiZWRF" +
|
|
19
|
+
"K1NwR2xvaTJOS0N1aVBOVlJicTlQSE9PWndUczdZTG8KYk93ZjUzRkp1TzZ2UkZwekZmWDF0bGM5emxGcUp2Wm5ZTzlOZ0hwTXlz" +
|
|
20
|
+
"aWRvY1djZ3JETi9TSUR5d2dQQjVDVgpiWWczVnZ6dWE5ZndaeDllNUtUOXhkNUlwVHFkVHJXUzQ3alFPVktMaGRRQ2JKRklsTXJi" +
|
|
21
|
+
"bE93TEJBeCtmSG9rCndxaDZ0a0k2S3R1eXlqdzhEeXNlYmkxbmRXakt0WjJtVzQ3cjh4Wi9KK3ozRVpxY3lKTVk2TVJ0eC96YjFq" +
|
|
22
|
+
"QkYKdUh0a2pyYjVLdjFETXpTS2xrYU5KSWJ2QytNaytoSTk3VytTakxTUnVJZEM3K29KVXpXYVN6Z3U5Y2pYQ1ZmbQpxOHQ0SUwv" +
|
|
23
|
+
"MzVoUDY5UEs5NUxnTGVjdElyUDk2Q1lBVC9hVk1HMTlGckZXMFFXRXlmVCtrekc0amt1bWZQYkhxClkybk5rRU4wK3RqajNoNFdk" +
|
|
24
|
+
"enJXZ1FFb2pLL2xoZmNSRlZrdHM3NCthWm9NcFFQK3ZtTDE3Q0ttU3pYazVvL2UKSzIxeGd4SmR6TWJkenRmVHBpYmlYazBhYmZP" +
|
|
25
|
+
"cE4rMVZSKzNOWWErYlJPQUtOeUdhUmVFR3N5VzJiamNqTng1MQo1VnF6ajNTVnhoTVNwNXZmRjlFNEExakU5OU0vbDlqUURNNlJ6" +
|
|
26
|
+
"a1QwbE1jY0dBZDV0VVNkTnZEbHJxdFFhUWlLCnYvWnNYUGdYTFRXZk9wdmFMTkVnd2RNZ1pNdWhqcGt3dkFaeW9ZZmVGOWt5eWRq" +
|
|
27
|
+
"RGg3YnZyWC8vY3ovVm9wQVUKbHhVdFF0TUNBd0VBQWFOak1HRXdIUVlEVlIwT0JCWUVGTlZKZ1FhZDFzTlROMGp4Vmt3YkovWE0x" +
|
|
28
|
+
"YW4xTUI4RwpBMVVkSXdRWU1CYUFGTlZKZ1FhZDFzTlROMGp4Vmt3YkovWE0xYW4xTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3CkRn" +
|
|
29
|
+
"WURWUjBQQVFIL0JBUURBZ0VHTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElDQVFCRlpES01KZnAvTjRnQmVGSGgKTWlGZWhhVXlNUzZl" +
|
|
30
|
+
"OW16clRmTUxKTEpvajZKb3BhOVY5aklmY0NFQkdadVJUaHFGY0FUVitVZEZIU0lOcFVjSAp1cGNDWW5helRSQzRkbjFobnhuUTFv" +
|
|
31
|
+
"alFjSHhkR3A5eEd3L1ljbEFLRDk3ZDhiUFNoZkJNVDF0bzl6Yk1LN1Q1Ckw4emdxZzAxWUlPS2pRazBIY2QwKzBpVXI2bTh6UTVQ" +
|
|
32
|
+
"OFJsM1FYcUF5ZVdncW1ZUXJyalRXd1BzZ2RmSE5YS1gKdkRyeDcvY3FyeTVsS1U4MDJoVXBsS01CeGVsdjRXODQwN1l0ajFsZkpP" +
|
|
33
|
+
"d3Z4cXh4c0ZVNWpTd2NVRzN6bzJ2awpRdGpSczhtNUJLdXA1SzFPUFlra1B1N0xkODlYMFhwVTA3My9kTkRHMTF1eGIxZURLcnRO" +
|
|
34
|
+
"UDZ2WnVaak5FMlBxCjhIQ29JMUV0UCtJdHlxdFVNdkhpNloyenNtbEEyNWJyb1Zpb2VVS3hqbEllY3BROUpSL0ZoRHU5Q1dORi9u" +
|
|
35
|
+
"RFcKTFNPUk5hTU16Z3NNU3pJK0hDaVVocU4rcU1JdlZQNnJ6R1RKendxei9sYzVMZitaUENuR0E5V0pUVDR1UEloZgp1ZmJabW5V" +
|
|
36
|
+
"SjM1V3VXS0h4b3ZEc3FCaDg4elE5c1orZWk0SGk0dlZ6T2hVZ2ZHM2FMb1NRRVlxUm9xYWJvQU5oCndDd3p5dVcyUnY1NHU1UVNC" +
|
|
37
|
+
"YmQ2R3gxT3B2c1dtTFBXZDIvaUwya0lTbDV3Zm1MR1Z5ZHZTSmErcmJPZnVBeTcKeWNWUWFjVkRRQ0FuYmhvVnJReTcrNDU0UXNL" +
|
|
38
|
+
"U1czWlY2QmN5UnJvcmV3Q3lDWWdkN255eGZseEhaVEJFeWtYWApoYUdOZS9LRk52SkJNT0l1SVV6a25SUm1pUT09Ci0tLS0tRU5E" +
|
|
39
|
+
"IENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUI4ekNDQVptZ0F3SUJBZ0lVZTYxakdRNFJ6" +
|
|
40
|
+
"SUM4aytzTnVxYkkvQ2FOcVBJd0NnWUlLb1pJemowRUF3SXcKUmpFTE1Ba0dBMVVFQmhNQ1EwNHhJVEFmQmdOVkJBb01HRUpDVENC" +
|
|
41
|
+
"VVpXTm9ibTlzYjJkcFpYTWdRMjh1SUV4MApaREVVTUJJR0ExVUVBd3dMUWtKTUlFTkJNaUJGUTBNd0lCY05NalV3TmpFM01ERXpP" +
|
|
42
|
+
"RE0xV2hnUE1qQTFNREEyCk1UY3dNVE00TXpWYU1FWXhDekFKQmdOVkJBWVRBa05PTVNFd0h3WURWUVFLREJoQ1Frd2dWR1ZqYUc1" +
|
|
43
|
+
"dmJHOW4KYVdWeklFTnZMaUJNZEdReEZEQVNCZ05WQkFNTUMwSkNUQ0JEUVRJZ1JVTkRNRmt3RXdZSEtvWkl6ajBDQVFZSQpLb1pJ" +
|
|
44
|
+
"emowREFRY0RRZ0FFcEtURjd3UlN0eTREWHBHSnpnQ1B3Umg4Z2hMbHhVQzNxSmJ5RWdMcVR2SmdiaXdZCkFQUEhLN2tWYlZtZXJr" +
|
|
45
|
+
"cWhIT1Q0UWVXUmxURzNkT1FHTEEyVnBhTmpNR0V3SFFZRFZSME9CQllFRkt1UnBzalkKUkVPeUlLSDdId09FNmpoR0JkNk5NQjhH" +
|
|
46
|
+
"QTFVZEl3UVlNQmFBRkt1UnBzallSRU95SUtIN0h3T0U2amhHQmQ2TgpNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEZ1lEVlIwUEFR" +
|
|
47
|
+
"SC9CQVFEQWdFR01Bb0dDQ3FHU000OUJBTUNBMGdBCk1FVUNJRXJCaVVtM1ZkdFAzcno0a2I4YUxwSTVwK0J6TDdNOXZFbEJHV1dK" +
|
|
48
|
+
"eHBITUFpRUEzcjV0SldWR3d1eGkKWUNyQjFjNDBLWUZSRnlhaEdyaE9KWkFqL1loUmRuVT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUt" +
|
|
49
|
+
"LS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJRWVUQ0NBMkdnQXdJQkFnSVVPcStsTklhQzJ4c3N3a0ZxajVKUHlW" +
|
|
50
|
+
"Qmw0NWN3RFFZSktvWklodmNOQVFFTApCUUF3UWpFTE1Ba0dBMVVFQmhNQ1EwNHhJakFnQmdOVkJBb01HVUpDVENCVVpXTm9ibTlz" +
|
|
51
|
+
"YjJkcFpYTWdRMjh1CkxDQk1kR1F4RHpBTkJnTlZCQU1NQmtKQ1RDQkRRVEFlRncweU5UQTJNVGN3TWpBeE1qZGFGdzB6TlRBMk1U" +
|
|
52
|
+
"VXcKTWpBeE1qZGFNRVl4Q3pBSkJnTlZCQVlUQWtOT01TRXdId1lEVlFRS0RCaENRa3dnVkdWamFHNXZiRzluYVdWegpJRU52TGlC" +
|
|
53
|
+
"TWRHUXhGREFTQmdOVkJBTU1DMEpDVENCRFFUSWdVbE5CTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGCkFBT0NBZzhBTUlJQ0NnS0NB" +
|
|
54
|
+
"Z0VBbzQ1NTBHNGM0MmdUS3pRcWl4d0tUMDg5Uml6SWRacHlPY0dBNjc5clBhT2QKV3NNcVZ3bllQUDJGcE1xWEtrakZiZWRFK1Nw" +
|
|
55
|
+
"R2xvaTJOS0N1aVBOVlJicTlQSE9PWndUczdZTG9iT3dmNTNGSgp1TzZ2UkZwekZmWDF0bGM5emxGcUp2Wm5ZTzlOZ0hwTXlzaWRv" +
|
|
56
|
+
"Y1djZ3JETi9TSUR5d2dQQjVDVmJZZzNWdnp1CmE5ZndaeDllNUtUOXhkNUlwVHFkVHJXUzQ3alFPVktMaGRRQ2JKRklsTXJibE93" +
|
|
57
|
+
"TEJBeCtmSG9rd3FoNnRrSTYKS3R1eXlqdzhEeXNlYmkxbmRXakt0WjJtVzQ3cjh4Wi9KK3ozRVpxY3lKTVk2TVJ0eC96YjFqQkZ1" +
|
|
58
|
+
"SHRranJiNQpLdjFETXpTS2xrYU5KSWJ2QytNaytoSTk3VytTakxTUnVJZEM3K29KVXpXYVN6Z3U5Y2pYQ1ZmbXE4dDRJTC8zCjVo" +
|
|
59
|
+
"UDY5UEs5NUxnTGVjdElyUDk2Q1lBVC9hVk1HMTlGckZXMFFXRXlmVCtrekc0amt1bWZQYkhxWTJuTmtFTjAKK3RqajNoNFdkenJX" +
|
|
60
|
+
"Z1FFb2pLL2xoZmNSRlZrdHM3NCthWm9NcFFQK3ZtTDE3Q0ttU3pYazVvL2VLMjF4Z3hKZAp6TWJkenRmVHBpYmlYazBhYmZPcE4r" +
|
|
61
|
+
"MVZSKzNOWWErYlJPQUtOeUdhUmVFR3N5VzJiamNqTng1MTVWcXpqM1NWCnhoTVNwNXZmRjlFNEExakU5OU0vbDlqUURNNlJ6a1Qw" +
|
|
62
|
+
"bE1jY0dBZDV0VVNkTnZEbHJxdFFhUWlLdi9ac1hQZ1gKTFRXZk9wdmFMTkVnd2RNZ1pNdWhqcGt3dkFaeW9ZZmVGOWt5eWRqRGg3" +
|
|
63
|
+
"YnZyWC8vY3ovVm9wQVVseFV0UXRNQwpBd0VBQWFOak1HRXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1D" +
|
|
64
|
+
"QVFZd0hRWURWUjBPCkJCWUVGTlZKZ1FhZDFzTlROMGp4Vmt3YkovWE0xYW4xTUI4R0ExVWRJd1FZTUJhQUZJODBRbWpjWjA2UHhD" +
|
|
65
|
+
"S2UKeFh4SjVhdmRSTDRlTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBdlM4dHlmYWdhR3NGZjlZbmNBMmtvL05hNQo5QlZGKzhU" +
|
|
66
|
+
"bFVvKzMyb3pud0lWcFMxQWhTZ0xQNnJOVmVrWE5GS2J1UDVodHVkTFExN1pSQkpJL1VNVnlZRURxCklON3h2N1pqK3pKd0Y2VzZo" +
|
|
67
|
+
"YVlyamIyVms4aWd3MVh2TlVMWmZ2Vk5OS0lrdkpVaVZxRXNsV3JDK2s3NGNyay8KV3Y4Q2hWZit6cXZmSU42TFYzZXNhR1JMMDJK" +
|
|
68
|
+
"M0FwclFHYjdERGhSMUVlZlFNU2NEa05wR0pNVW12Q21ma25ybAppSzhxZ3ZRTjFTV083SlJmNmZOS0hzTjFaUXZ5UDBwZ0xXeHBU" +
|
|
69
|
+
"M1YwLzAvV3R0cVgzY01HdUpGK2pWVXptL05oCnhZaEZld0c4dmMzS3pUam53UUFwTUE2Q1c1NTRGT0pXRnlPRDJqbjV5SkxUM1Z1" +
|
|
70
|
+
"ZSthWURRUnA0YktNeAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlDcmpD" +
|
|
71
|
+
"Q0FaYWdBd0lCQWdJVU9xK2xOSWFDMnhzc3drRnFqNUpQeVZCbDQ1Z3dEUVlKS29aSWh2Y05BUUVMCkJRQXdRakVMTUFrR0ExVUVC" +
|
|
72
|
+
"aE1DUTA0eElqQWdCZ05WQkFvTUdVSkNUQ0JVWldOb2JtOXNiMmRwWlhNZ1EyOHUKTENCTWRHUXhEekFOQmdOVkJBTU1Ca0pDVENC" +
|
|
73
|
+
"RFFUQWVGdzB5TlRBMk1UY3dNakF4TkRkYUZ3MHpOVEEyTVRVdwpNakF4TkRkYU1FWXhDekFKQmdOVkJBWVRBa05PTVNFd0h3WURW" +
|
|
74
|
+
"UVFLREJoQ1Frd2dWR1ZqYUc1dmJHOW5hV1Z6CklFTnZMaUJNZEdReEZEQVNCZ05WQkFNTUMwSkNUQ0JEUVRJZ1JVTkRNRmt3RXdZ" +
|
|
75
|
+
"SEtvWkl6ajBDQVFZSUtvWkkKemowREFRY0RRZ0FFcEtURjd3UlN0eTREWHBHSnpnQ1B3Umg4Z2hMbHhVQzNxSmJ5RWdMcVR2Smdi" +
|
|
76
|
+
"aXdZQVBQSApLN2tWYlZtZXJrcWhIT1Q0UWVXUmxURzNkT1FHTEEyVnBhTmpNR0V3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFPCkJn" +
|
|
77
|
+
"TlZIUThCQWY4RUJBTUNBUVl3SFFZRFZSME9CQllFRkt1UnBzallSRU95SUtIN0h3T0U2amhHQmQ2Tk1COEcKQTFVZEl3UVlNQmFB" +
|
|
78
|
+
"Rkk4MFFtamNaMDZQeENLZXhYeEo1YXZkUkw0ZU1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQgpBUUNnNlBqVVNTWlYrNGJ2ZWpjVk12" +
|
|
79
|
+
"Z1htS3pmRDk1b3NXbjBjdG5vTUJ4UERhK20rR2crQmNMVDJJbEZBZTNFCktZTXZ1NFQyOTVXUWM5MnJqS1lxVzZjaXJGcHBuZzl1" +
|
|
80
|
+
"RUZXMm1aTGlteGFTbXV0c1RmdEUzc2JNVk1KL1NMWU4KUFY3VEZ2Nm1jQlNJRldYd21CT0lwYmg0QlVjVmZPTlR2ZFNmSXFmeUFW" +
|
|
81
|
+
"eHNxNHh6YzJuYzZoUEJwQW0yMUF5agpUb0MxZXYvVGJESjhWbGxGWmlFVm1XV2xJUDNhTnpBbThTMm1PcHhQQjJXbmFuYVpIU3J2" +
|
|
82
|
+
"WExGaHN0eXp3cmpECnlPMS9pc09aN3d0cjdyY3VUSmRFdnZ2Q2ltT1psa2ZSaGFEb1Rldzl0UTBFMkZWcHp6U2ludzAycW1RMXhJ" +
|
|
83
|
+
"RTkKNS9INVp6SlNQa3BlQUhXRVBuS2t4ZzB2Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FU" +
|
|
84
|
+
"RS0tLS0tCk1JSURaVENDQWsyZ0F3SUJBZ0lVVjFGY2t3WEVseWVrMW9uRm5ROWtMN0JrNE44d0RRWUpLb1pJaHZjTkFRRUwKQlFB" +
|
|
85
|
+
"d1FqRUxNQWtHQTFVRUJoTUNRMDR4SWpBZ0JnTlZCQW9NR1VKQ1RDQlVaV05vYm05c2IyZHBaWE1nUTI4dQpMQ0JNZEdReER6QU5C" +
|
|
86
|
+
"Z05WQkFNTUJrSkNUQ0JEUVRBZUZ3MHlNakEwTURRd016UXlNVEZhRncwek1qQTBNREV3Ck16UXlNVEZhTUVJeEN6QUpCZ05WQkFZ" +
|
|
87
|
+
"VEFrTk9NU0l3SUFZRFZRUUtEQmxDUWt3Z1ZHVmphRzV2Ykc5bmFXVnoKSUVOdkxpd2dUSFJrTVE4d0RRWURWUVFEREFaQ1Frd2dR" +
|
|
88
|
+
"MEV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQgpEd0F3Z2dFS0FvSUJBUURMM3BuRGR4R09rNVo2dnVnaVQ0ZHBNMGp1KzNY" +
|
|
89
|
+
"YXR4ejA5VVk3bWJqNHRrSWRieTRICm9lRWRpWVNaamM1TEpuZ0p1Q0h3dEViQkp0MUJyaVJkU1ZyRjZNOUQyVWFCRHlhbUVvMGR4" +
|
|
90
|
+
"d1NhVnhaaURWV0MKZWVDUGRFTHBGWmRFaFNOVGFUNE83emd2Y25Gc2ZITWEvMHZNQWt2RTdpMHFwM21qRXpZTGZ6NjBheGNEb0pM" +
|
|
91
|
+
"awpwN242eEtYSStjSmJBNElsVG9GanBTbGRQbUMreW5PbzdZQU9zWHQ3QVlLWTZHbHowQndVVnpTSnhVKy8rVkZ5Ci9Rcm1ZR053" +
|
|
92
|
+
"bHJRdGRSRUhlUmkwU05LMzJ4MStiT25kZkpQMHNvanVJckRqS3NkQ0x5ZTVDU1pJdnFuYm93d1cKMWpSd1pnVEJSMjlacDJuekNv" +
|
|
93
|
+
"eEpZY1U5VFNRcC80S1p1V05WQWdNQkFBR2pVekJSTUIwR0ExVWREZ1FXQkJTUApORUpvM0dkT2o4UWluc1Y4U2VXcjNVUytIakFm" +
|
|
94
|
+
"QmdOVkhTTUVHREFXZ0JTUE5FSm8zR2RPajhRaW5zVjhTZVdyCjNVUytIakFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJ" +
|
|
95
|
+
"YjNEUUVCQ3dVQUE0SUJBUUFCbEJJVDVaZUcKZmdjSzFMT2gxQ045c1R6eE1DTGJ0VFBGRjFOR0dBMTNtQXB1NmoxaDVZRUxiU0tj" +
|
|
96
|
+
"VXFmWHpNblZlQWIwNkh0dQozQ29Db2Urd2o3TE9OVEZPKyt2Qm0yL2lmNkp0L0RVdzFDQUVjTnlxZWg2RVMwTlg4TEpSVlNlMHFk" +
|
|
97
|
+
"VHhQSnVBCkJkT29vOTZpWDg5clJQb3hlZWQxY3BxNWhad2Jla2EzK0NKR1Y3Nml0V3AzNVVwNXJtbVVxcmx5UU9yL1dheDYKaXRv" +
|
|
98
|
+
"c0l6RzBNZmhnVXpVNTFBMlAvaFNuRDNORE1Ydit3VVkvQXZxZ0lMN3U3ZmJES25rdTFHekVLSWtmSDhobQpSczZkOFNDVTg5eHly" +
|
|
99
|
+
"d3pRMFBSODUzaXJIYXMzV3JIVnFhYjNQK3FOd1IwWWlyTDBRazdYdC9xM08xZ3JpTmcyCkJsYmpnM29icEhvOQotLS0tLUVORCBD" +
|
|
100
|
+
"RVJUSUZJQ0FURS0tLS0t",
|
|
101
|
+
},
|
|
102
|
+
"slicer_base64.cer": {
|
|
103
|
+
sha256: "f504dc1c37bc9741b7a95198914e67f3f6f1d917b1e45eaa14b9e84eb7b51ec8",
|
|
104
|
+
base64: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY3ekNDQk5lZ0F3SUJBZ0lRQm5XSDBPTmY1dE1XNlZyNXhtWXhEREFOQmdr" +
|
|
105
|
+
"cWhraUc5dzBCQVFzRkFEQloKTVFzd0NRWURWUVFHRXdKVlV6RVZNQk1HQTFVRUNoTU1SR2xuYVVObGNuUWdTVzVqTVRNd01RWURW" +
|
|
106
|
+
"UVFERXlwUwpZWEJwWkZOVFRDQlVURk1nUkZZZ1VsTkJJRTFwZUdWa0lGTklRVEkxTmlBeU1ESXdJRU5CTFRFd0hoY05NakV3Ck56" +
|
|
107
|
+
"RXpNREF3TURBd1doY05Nakl3TnpFek1qTTFPVFU1V2pBWk1SY3dGUVlEVlFRRERBNHFMbUpoYldKMWJHRmkKTG1OdmJUQ0NBU0l3" +
|
|
108
|
+
"RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUt4c3pMZGpBS21TMkpscQpaNTI4WkVsM0VxWkd2MWp1UnZ2TTR4" +
|
|
109
|
+
"cCtvcGVNWENzMWpLc2IxV2ZpZUZBL0tqWEJ6NG51ZVpzRkxhYnB5aXBBCjlvUFBkOG9HajhzWjRoOENJYk1iejJHeG1VVktUb1Ra" +
|
|
110
|
+
"OWYzd1lwMHNXRFVQVHhoWEd3L0p6cXk2bWN3cWhvQ08KUWJWck5hUUNxUXVOcElLTWx2UFg1YUsxQTBRL0FzZE4ybXBWdFNHMENw" +
|
|
111
|
+
"cGNSZjVaRTQyelZGUGcxaWVReGdpSAp3NkxXTGlaM0NHcHRueVAxc0JsSUx6VTQ4cXJoMUQwN1ptdUVITDA1cEo3ZFFBc2hzK0Jr" +
|
|
112
|
+
"bFJ6eXpUZ2lVKzFlCitRZVBGYXExRG9zekZlMndMS3Z1UjhPL1JaOGZGS0pzTnlodE80b21jMklTR3ZSb3lrN3FLU3JGUU9jNG1l" +
|
|
113
|
+
"a1kKMDJzSUZHVUNBd0VBQWFPQ0F2RXdnZ0x0TUI4R0ExVWRJd1FZTUJhQUZLU041YjU4ZWVSd0kyMHVLVFN0STFqYwo5VEYvTUIw" +
|
|
114
|
+
"R0ExVWREZ1FXQkJUUDIrTDR0YjJhOGZsMXBPWSt6RmxBNEdOamtqQW5CZ05WSFJFRUlEQWVnZzRxCkxtSmhiV0oxYkdGaUxtTnZi" +
|
|
115
|
+
"WUlNWW1GdFluVnNZV0l1WTI5dE1BNEdBMVVkRHdFQi93UUVBd0lGb0RBZEJnTlYKSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZ" +
|
|
116
|
+
"QkJRVUhBd0l3UGdZRFZSMGdCRGN3TlRBekJnWm5nUXdCQWdFdwpLVEFuQmdnckJnRUZCUWNDQVJZYmFIUjBjRG92TDNkM2R5NWth" +
|
|
117
|
+
"V2RwWTJWeWRDNWpiMjB2UTFCVE1JR0ZCZ2dyCkJnRUZCUWNCQVFSNU1IY3dKQVlJS3dZQkJRVUhNQUdHR0doMGRIQTZMeTl2WTNO" +
|
|
118
|
+
"d0xtUnBaMmxqWlhKMExtTnYKYlRCUEJnZ3JCZ0VGQlFjd0FvWkRhSFIwY0RvdkwyTmhZMlZ5ZEhNdVpHbG5hV05sY25RdVkyOXRM" +
|
|
119
|
+
"MUpoY0dsawpVMU5NVkV4VFJGWlNVMEZOYVhobFpGTklRVEkxTmpJd01qQkRRUzB4TG1OeWREQUpCZ05WSFJNRUFqQUFNSUlCCmZn" +
|
|
120
|
+
"WUtLd1lCQkFIV2VRSUVBZ1NDQVc0RWdnRnFBV2dBZGdBcGViN3duams1SWZCV2M1OWpwWGZsdmxkOW5HQUsKK1BsTlhTWmNKVjNI" +
|
|
121
|
+
"aEFBQUFYcWVwZEJsQUFBRUF3QkhNRVVDSUcrQVVPdnNqV1VIMDdBK0FaTkNyVVJackxYLwo2MldSVXcrM2FTWlhxSG8vQWlFQW9r" +
|
|
122
|
+
"c1dpMm13bTZrNEZDZW45VThNeTNJbmVEU1lxbnY3K2JoODhLOExrdHdBCmRnQlJvN0QxL1FGNW5GWnR1RGQ0and5a2Vzd2JKOHYz" +
|
|
123
|
+
"bm9oQ21nMysxSXNGNVFBQUFYcWVwZERWQUFBRUF3QkgKTUVVQ0lRRDZLYk5YRWZaT1pnN1MvdURIRlRWY00xczByemw0ZWtQNFhW" +
|
|
124
|
+
"cHlRSHQ5MkFJZ0pVbndQMzlDY1NjTwppZ1dZSm1yeGpCQTMxR25XajE4THVLeURWMWpYSmNRQWRnQkJ5TXF4M3lKR1NoREdvVG9K" +
|
|
125
|
+
"UW9kZVRqR0xHd1ByCjYwdkhhUENRWXBZRzlnQUFBWHFlcGRCRUFBQUVBd0JITUVVQ0lRQ3Y3dWZlY21CVmhwbVF1aUpFV1R6dFkw" +
|
|
126
|
+
"ck4KbnBnWnZnQlVNL3ppVlo0d1VBSWdRQXVHbUM5KzZzbkQwUklud3R0Ly9WdmRZcERYT2RpVVN3ZG4zdXc0NXNjdwpEUVlKS29a" +
|
|
127
|
+
"SWh2Y05BUUVMQlFBRGdnRUJBTEJSMFhuSFNBRm0zODN3TDM0VGcvZnFjdWtwWUFFTGZ4MG9zczVpCnZGUWhERUZndVovUXhjaDcw" +
|
|
128
|
+
"UmZBTnYyK0hETFVKNUJIWmZFRnpPQVZFc2xlalBEL2xPZFZKZVozWGE3azhXZXAKZDJudi9hVmR1UmlUOUc4Q0JFa3MrVWxXKzlW" +
|
|
129
|
+
"NmwvWVB5YThJMWh4bWRLWm5YWlhrSm1kOUJia1A4SndieHNzcApxUEYzRzlkc1Y1RTBab0RTWkZuOXgzSkpIM1QzNUZkSXJ4b0sv" +
|
|
130
|
+
"K3MvK3ZvbzdPbkRPWkdqekZrRjUrWW00ajZ1Cks2dW9vMVRXSmFDOW1NQ2FiNElneXJ5alNiVlNvNDlROUdsdXRvaGR2aEZrNEhD" +
|
|
131
|
+
"SWRmejJ3M0JYM29XdFJ3LzIKdUtLTHlEamRYeWYzVFlqWWFTYmJhblNmRDEycGQwWUsyejAvK0daUHZoMUtiN1E9Ci0tLS0tRU5E" +
|
|
132
|
+
"IENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURyekNDQXBlZ0F3SUJBZ0lRQ0R2Z1ZwQkNS" +
|
|
133
|
+
"ckdoZFdySldaSEhTakFOQmdrcWhraUc5dzBCQVFVRkFEQmgKTVFzd0NRWURWUVFHRXdKVlV6RVZNQk1HQTFVRUNoTU1SR2xuYVVO" +
|
|
134
|
+
"bGNuUWdTVzVqTVJrd0Z3WURWUVFMRXhCMwpkM2N1WkdsbmFXTmxjblF1WTI5dE1TQXdIZ1lEVlFRREV4ZEVhV2RwUTJWeWRDQkhi" +
|
|
135
|
+
"RzlpWVd3Z1VtOXZkQ0JEClFUQWVGdzB3TmpFeE1UQXdNREF3TURCYUZ3MHpNVEV4TVRBd01EQXdNREJhTUdFeEN6QUpCZ05WQkFZ" +
|
|
136
|
+
"VEFsVlQKTVJVd0V3WURWUVFLRXd4RWFXZHBRMlZ5ZENCSmJtTXhHVEFYQmdOVkJBc1RFSGQzZHk1a2FXZHBZMlZ5ZEM1agpiMjB4" +
|
|
137
|
+
"SURBZUJnTlZCQU1URjBScFoybERaWEowSUVkc2IySmhiQ0JTYjI5MElFTkJNSUlCSWpBTkJna3Foa2lHCjl3MEJBUUVGQUFPQ0FR" +
|
|
138
|
+
"OEFNSUlCQ2dLQ0FRRUE0anZoRVhMZXFLVFRvMWVxVUtLUEMzZVF5YUtsN2hMT2xsc0IKQ1NETUFaT25UakMzVS9kRHhHa0FWNTNp" +
|
|
139
|
+
"alNMZGh3WkFBSUVKenM0Ymc3L2Z6VHR4UnVMV1pzY0ZzM1luRm85NwpuaDZWZmU2M1NLTUkydGF2ZWd3NUJtVi9TbDBmdkJmNHE3" +
|
|
140
|
+
"N3VLTmQwZjNwNG1WbUZhRzVjSXpKTHYwN0E2RnB0CjQzQy9keEMvL0FIMmhkbW9SQkJZTXFsMUdOWFJvcjVINGlkcTlKb3orRWtJ" +
|
|
141
|
+
"WUl2VVg3UTZoTCtocWtwTWZUN1AKVDE5c2RsNmdTemVSbnR3aTVtM09GQnFPYXN2K3piTVVaQmZIV3ltZU1yL3k3dnJUQzBMVXE3" +
|
|
142
|
+
"ZEJNdG9NMU8vNApnZFc3alZnL3RSdm9TU2lpY05veEJOMzNzaGJ5VEFwT0I2anRTajFldFgramtNT3ZKd0lEQVFBQm8yTXdZVEFP" +
|
|
143
|
+
"CkJnTlZIUThCQWY4RUJBTUNBWVl3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVQTk1UU5WYlIKVEx0bThLUGlH" +
|
|
144
|
+
"eHZEbDdJOTBWVXdId1lEVlIwakJCZ3dGb0FVQTk1UU5WYlJUTHRtOEtQaUd4dkRsN0k5MFZVdwpEUVlKS29aSWh2Y05BUUVGQlFB" +
|
|
145
|
+
"RGdnRUJBTXVjTjZwSUV4SUsrdDFFbkU5U3NQVGZyZ1QxZVhrSW95UVkvRXNyCmhNQXR1ZFhIL3ZUQkgxakx1RzJjZW5Ubm1DbXJF" +
|
|
146
|
+
"YlhqY0tDaHpVeUltWk9Na1hEaXF3OGN2cE9wLzJQVjVBZGcKMDZPL25Wc0o4ZFdPNDFQMGptUDZQNmZidEdiZlltYlcwVzVCamZJ" +
|
|
147
|
+
"dHRlcDNTcCtkV09JcldjQkFJKzB0S0lKRgpQbmxVa2lhWTRJQklxRGZ2OE5aNVlCYmVyT2dPelc2c1JCYzRMMG5hNFVVK0tyazJV" +
|
|
148
|
+
"ODg2VUFiM0x1akVWMGxzCllTRVkxUVN0ZUR3c09vQnJwK3V2RlJUcDJJbkJ1VGhzNHBGc2l2OWt1WGNsVnpEQUd5U2o0ZHpwMzBk" +
|
|
149
|
+
"OHRiUWsKQ0FVdzdDMjlDNzlGdjFDNXFmUHJtQUVTcmNpSXhwZzBYNDBLUE1icDFaV1ZiZDQ9Ci0tLS0tRU5EIENFUlRJRklDQVRF" +
|
|
150
|
+
"LS0tLS0K",
|
|
151
|
+
},
|
|
152
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import path from "path";
|
|
|
10
10
|
import { createServer as createHttpServer } from "node:http";
|
|
11
11
|
import { randomUUID } from "node:crypto";
|
|
12
12
|
import { STLManipulator, SLICER_TYPES, normalizeSlicerType, } from "./stl/stl-manipulator.js";
|
|
13
|
-
import { BambuNetworkBridge } from "./bambu-network-bridge.js";
|
|
13
|
+
import { BambuNetworkBridge, describeBambuNetworkPrintError } from "./bambu-network-bridge.js";
|
|
14
14
|
import { assertBambuNativeAvailable, bambuNativeControlsAvailable, bambuNativeUnavailableReason, interruptNativeOperations, validateBambuNativeMetadata, buildBambuNativeFanCommand, buildBambuNativeTemperatureCommand, printWithBambuNative, sendCommandWithBambuNative, uploadWithBambuNative, } from "./bambu-native.js";
|
|
15
15
|
import { importFileViaBambuConnect } from "./bambu-connect.js";
|
|
16
16
|
import { BlenderMcpBridge } from "./blender-mcp-bridge.js";
|
|
@@ -1229,12 +1229,17 @@ class BambuPrinterMCPServer {
|
|
|
1229
1229
|
this.bambu.assertBedClearance(confirmedStatus, bedClearance);
|
|
1230
1230
|
const amsMapping = stringifyBridgeJson(mapping?.map(value => bridgeAmsMappingValue(value, isDualNozzleModel(printModel))));
|
|
1231
1231
|
const remoteFileName = uniquePrintName(threeMFPath);
|
|
1232
|
+
const checkedName = remoteFileName.replace(/(?:\.gcode)?\.3mf$/i, "");
|
|
1232
1233
|
const params = {
|
|
1233
1234
|
dev_id: devId,
|
|
1234
1235
|
// Firmware can report the task name instead of the uploaded filename.
|
|
1235
1236
|
// Bind either report to this inspected artifact for a later safe resume.
|
|
1236
|
-
task_name:
|
|
1237
|
-
|
|
1237
|
+
task_name: checkedName,
|
|
1238
|
+
// The plug-in names the uploaded file and the printer's job from
|
|
1239
|
+
// project_name, so it carries the same unique checked name, as the
|
|
1240
|
+
// native route already passes; a reused name would overwrite a stored
|
|
1241
|
+
// job and break the resume binding.
|
|
1242
|
+
project_name: checkedName,
|
|
1238
1243
|
preset_name: presetName,
|
|
1239
1244
|
filename: snapshot,
|
|
1240
1245
|
config_filename: snapshot,
|
|
@@ -1309,7 +1314,7 @@ class BambuPrinterMCPServer {
|
|
|
1309
1314
|
this.bambu.recordCheckedJob(devIp, devId, remoteFileName, requirements);
|
|
1310
1315
|
return { ...nativeResult, autoSliced, projectName, plateIndex, useAMS, amsMapping: mapping };
|
|
1311
1316
|
}
|
|
1312
|
-
const
|
|
1317
|
+
const bridgeOptions = {
|
|
1313
1318
|
...this.bridgeOptionsFromArgs(args, "print_3mf_bambu_network"),
|
|
1314
1319
|
beforeDispatch: async (method) => {
|
|
1315
1320
|
if (method !== bridgeMethod)
|
|
@@ -1323,7 +1328,15 @@ class BambuPrinterMCPServer {
|
|
|
1323
1328
|
assertActive(); },
|
|
1324
1329
|
onDispatched: method => { if (method === bridgeMethod)
|
|
1325
1330
|
retainForDispatchedUpload(); },
|
|
1326
|
-
}
|
|
1331
|
+
};
|
|
1332
|
+
let jobUpdates = [];
|
|
1333
|
+
let bridgeResult;
|
|
1334
|
+
if (isLocalBridgePrint) {
|
|
1335
|
+
({ result: bridgeResult, jobUpdates } = await this.bambuNetwork.startLocalPrint({ devId, devIp, username: String(params.username), password, useSsl: params.use_ssl_for_mqtt }, bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions, assertActive));
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
bridgeResult = await this.bambuNetwork.callWithAgent(bridgeMethod, { client_job_id: clientJobId, params }, bridgeOptions);
|
|
1339
|
+
}
|
|
1327
1340
|
if (typeof bridgeResult === "object" && bridgeResult !== null && bridgeResult.ok === false) {
|
|
1328
1341
|
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} failed: ${String(bridgeResult.error || "unknown bridge error")}`);
|
|
1329
1342
|
}
|
|
@@ -1332,7 +1345,10 @@ class BambuPrinterMCPServer {
|
|
|
1332
1345
|
typeof bridgeResult.value === "number" &&
|
|
1333
1346
|
bridgeResult.value !== 0) {
|
|
1334
1347
|
const value = bridgeResult.value;
|
|
1335
|
-
|
|
1348
|
+
const reported = jobUpdates.filter(update => update.code !== 0 || update.msg).slice(-3)
|
|
1349
|
+
.map(update => `status ${update.status}, code ${update.code}${update.msg ? `: ${update.msg}` : ""}`).join("; ");
|
|
1350
|
+
throw new Error(`FULU BambuNetwork bridge method ${bridgeMethod} returned non-zero result ${value}${describeBambuNetworkPrintError(value)}.` +
|
|
1351
|
+
(reported ? ` Plug-in reported: ${reported}.` : ""));
|
|
1336
1352
|
}
|
|
1337
1353
|
assertActive();
|
|
1338
1354
|
this.bambu.recordCheckedJob(devIp, devId, remoteFileName, requirements);
|
|
@@ -1346,6 +1362,7 @@ class BambuPrinterMCPServer {
|
|
|
1346
1362
|
projectName,
|
|
1347
1363
|
plateIndex,
|
|
1348
1364
|
bridgePlateIndex: plateIndex + 1,
|
|
1365
|
+
printerJobName: checkedName,
|
|
1349
1366
|
useAMS,
|
|
1350
1367
|
amsMapping: mapping,
|
|
1351
1368
|
params: redactPrintParams(params),
|
|
@@ -1972,11 +1989,11 @@ class BambuPrinterMCPServer {
|
|
|
1972
1989
|
},
|
|
1973
1990
|
use_printer_filaments: { type: "boolean", description: "When true, and no explicit slicer profile or load_filaments override is provided, use the printer's current or first loaded AMS filament as the slicer filament profile. Template 3MF process settings can still be used at the same time." },
|
|
1974
1991
|
uptodate: { type: "boolean", description: "Refresh 3MF preset configs to match the latest BambuStudio version. Use when slicing downloaded or older 3MF files to prevent stale-config failures." },
|
|
1975
|
-
repetitions: { type: "number", description: "Print N identical copies of the model. Each copy gets its own plate placement. Example: 3 prints three copies." },
|
|
1992
|
+
repetitions: { type: "number", description: "Print N identical copies of the model. Each copy gets its own plate placement. Example: 3 prints three copies. OrcaSlicer copies an STL with --clone-objects; do not combine with clone_objects." },
|
|
1976
1993
|
orient: { type: "boolean", description: "Auto-orient the model for optimal printability (minimize supports, maximize bed adhesion). Recommended for raw STL imports that lack a pre-set orientation." },
|
|
1977
|
-
arrange: { type: "boolean", description: "Auto-arrange all objects on the build plate with optimal spacing
|
|
1994
|
+
arrange: { type: "boolean", description: "Auto-arrange all objects on the build plate with optimal spacing; STL input is arranged by default. Set false only to keep a 3MF's existing layout: an STL that is not already on the plate then fails to slice." },
|
|
1978
1995
|
ensure_on_bed: { type: "boolean", description: "Detect models floating above the bed and lower them onto the build surface. Safety net for imported models with incorrect Z origins." },
|
|
1979
|
-
clone_objects: { type: "string", description: "
|
|
1996
|
+
clone_objects: { type: "string", description: "Clone the loaded model: total copies as a single integer, e.g. '3'. The Bambu-style CLIs count copies per input file and this tool loads one file, so a comma-separated list is rejected by the slicer." },
|
|
1980
1997
|
skip_objects: { type: "string", description: "Skip specific objects during slicing by index. Comma-separated, e.g. '3,5,10'. Useful for multi-object 3MFs where you only want to print some parts." },
|
|
1981
1998
|
load_filaments: { type: "string", description: "Override filament profiles. Semicolon-separated paths to filament JSON configs, e.g. 'pla_basic.json;petg_cf.json'." },
|
|
1982
1999
|
filament_profile: { type: "string", description: "Compatibility alias for load_filaments. Semicolon-separated Orca/Bambu filament profile JSON paths." },
|
|
@@ -2078,7 +2095,7 @@ class BambuPrinterMCPServer {
|
|
|
2078
2095
|
nozzle_type: { type: "string", enum: [...VALID_NOZZLE_TYPES], description: "Installed nozzle material, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_TYPE, else the preset's stock nozzle)." },
|
|
2079
2096
|
nozzle_volume_type: { type: "string", enum: [...VALID_NOZZLE_VOLUME_TYPES], description: "Installed nozzle flow type, used when the 3MF must be auto-sliced (default: BAMBU_NOZZLE_VOLUME_TYPE, else standard)." },
|
|
2080
2097
|
plate_index: { type: "number", description: "Zero-based plate index to print from the sliced 3MF; converted to FULU's one-based PrintParams plate_index." },
|
|
2081
|
-
project_name: { type: "string", description: "Optional project name
|
|
2098
|
+
project_name: { type: "string", description: "Optional project name returned in the result; defaults to the 3MF filename without extension. The printer job and stored file use the server's unique checked name so resume_print can bind to the inspected file." },
|
|
2082
2099
|
preset_name: { type: "string", description: "Optional preset name sent in FULU PrintParams; defaults to project plus one-based plate index." },
|
|
2083
2100
|
task_name: { type: "string", description: "Optional project label when project_name is omitted. The submitted task name uses a unique inspected-job identity for safe resume." },
|
|
2084
2101
|
config_filename: { type: "string", description: "Optional config 3MF path for cloud print; defaults to the same 3MF path." },
|
|
@@ -2122,7 +2139,7 @@ class BambuPrinterMCPServer {
|
|
|
2122
2139
|
type: "object",
|
|
2123
2140
|
properties: {
|
|
2124
2141
|
save_path: { type: "string", description: "Optional absolute path to write the JPEG to disk. If omitted, only the base64 payload is returned." },
|
|
2125
|
-
timeout_ms: { type: "number", description: "Max ms to wait for a
|
|
2142
|
+
timeout_ms: { type: "number", description: "Max ms to wait for a live frame (default 8000). A1/P1 cameras first replay a buffered frame, which is skipped, so a live frame takes about 3 s, longer on cold start." },
|
|
2126
2143
|
bambu_model: { type: "string", description: "Printer model. Used to route to the correct protocol or fail fast on unsupported models. Defaults to BAMBU_MODEL." },
|
|
2127
2144
|
experimental: { type: "boolean", description: "Deprecated and ignored. Earlier this flag let callers probe H2 series via the A1/P1 TCP-on-6000 path; live testing on an H2S confirmed H2 uses RTSP instead, so the flag has no effect now." },
|
|
2128
2145
|
ffmpeg_path: { type: "string", description: "Override path to the ffmpeg binary used by the RTSP path. Defaults to FFMPEG_PATH or ffmpeg via $PATH. Per-call overrides require MCP_ALLOW_EXECUTABLE_ARG=1. Required only for the RTSP transport (X1, P2S, H2 series, X2D)." },
|
package/dist/printers/bambu.d.ts
CHANGED
|
@@ -33,6 +33,11 @@ export type PrintPreflight = {
|
|
|
33
33
|
model: string;
|
|
34
34
|
notices: string[];
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Split the A1/P1 camera stream on port 6000 into JPEG frames. Each frame is
|
|
38
|
+
* a 16-byte header (payload size LE, itrack, flags, reserved) and the JPEG.
|
|
39
|
+
*/
|
|
40
|
+
export declare function cameraFrameParser(onFrame: (jpeg: Buffer) => void, onError: (error: Error) => void): (chunk: Buffer) => void;
|
|
36
41
|
export declare class BambuImplementation {
|
|
37
42
|
private readonly confirm?;
|
|
38
43
|
private printerStore;
|
|
@@ -108,10 +113,6 @@ export declare class BambuImplementation {
|
|
|
108
113
|
files: string[];
|
|
109
114
|
directories: Record<string, string[]>;
|
|
110
115
|
}>;
|
|
111
|
-
getFile(host: string, serial: string, token: string, filename: string): Promise<{
|
|
112
|
-
name: string;
|
|
113
|
-
exists: boolean;
|
|
114
|
-
}>;
|
|
115
116
|
uploadFile(host: string, serial: string, token: string, filePath: string, filename: string, print: boolean, bambuModel?: string, upload?: (snapshot: string, destination: string, assertActive: () => void) => Promise<void>): Promise<{
|
|
116
117
|
status: string;
|
|
117
118
|
uploaded: boolean;
|
|
@@ -218,8 +219,13 @@ export declare class BambuImplementation {
|
|
|
218
219
|
*/
|
|
219
220
|
private fetchRtspCameraFrame;
|
|
220
221
|
/**
|
|
221
|
-
* Open the TLS-on-6000 socket, send the 80-byte auth packet, and
|
|
222
|
-
* a
|
|
222
|
+
* Open the TLS-on-6000 socket, send the 80-byte auth packet, and return
|
|
223
|
+
* a live JPEG frame. The printer first replays the last frame it buffered,
|
|
224
|
+
* which after an idle period can show the chamber as it was long ago (a
|
|
225
|
+
* finished part still on the bed), so that frame and any byte-identical
|
|
226
|
+
* repeat of it are discarded and the next frame returned; live frames always
|
|
227
|
+
* differ by sensor noise. If no live frame arrives in time, this fails rather
|
|
228
|
+
* than return a possibly stale image.
|
|
223
229
|
*/
|
|
224
230
|
private fetchTcpCameraFrame;
|
|
225
231
|
/**
|
package/dist/printers/bambu.js
CHANGED
|
@@ -9,10 +9,9 @@ import { readSafe3mfArchive } from "../safety/archive.js";
|
|
|
9
9
|
import { inspectPrintFile } from "../safety/print-file.js";
|
|
10
10
|
import { isDualNozzleModel, normalizeModel, validateTemperature } from "../safety/limits.js";
|
|
11
11
|
import { bridgeAmsMapping2Entry, bridgeAmsMappingValue, externalTrayForNozzle, describeAmsTrayRange } from "../ams-mapping.js";
|
|
12
|
-
import { describeHmsKey, hiddenHmsKeys, hiddenHmsNotices, reportedHmsKey, validatePrinterState, manualHeatingRequirements } from "../safety/printer-state.js";
|
|
12
|
+
import { describeHmsKey, hiddenHmsKeys, hiddenHmsNotices, reportedHmsKey, unchippedNozzles, validatePrinterState, manualHeatingRequirements } from "../safety/printer-state.js";
|
|
13
13
|
import { withPrinterOperation, withPrintSnapshot, uniquePrintName, normalizedRemotePath, cancelPendingPrinterOperations } from "../safety/artifact.js";
|
|
14
14
|
import { Client as FTPClient } from "basic-ftp";
|
|
15
|
-
import { BambuPrinter } from "bambu-js";
|
|
16
15
|
import * as mqtt from "mqtt";
|
|
17
16
|
import { BambuClient, GCodeFileCommand, GCodeLineCommand, PushAllCommand, UpdateFanCommand, UpdateLightCommand, UpdateStateCommand, } from "bambu-node";
|
|
18
17
|
/**
|
|
@@ -277,6 +276,7 @@ class BambuClientStore {
|
|
|
277
276
|
}
|
|
278
277
|
async waitForInitialReport(host, serial, token, timeoutMs = 4000) {
|
|
279
278
|
const key = getPrinterKey(host, serial, token);
|
|
279
|
+
const deadline = Date.now() + timeoutMs + 500;
|
|
280
280
|
const existing = this.reportSnapshots.get(key);
|
|
281
281
|
if (!existing || Object.keys(existing).length === 0) {
|
|
282
282
|
// No MQTT data yet — wait for the first push.
|
|
@@ -298,8 +298,12 @@ class BambuClientStore {
|
|
|
298
298
|
// (gcode_state, ams, hms, temperatures, fans, etc.) arrives afterward
|
|
299
299
|
// and gets merged into reportSnapshots via incremental update.
|
|
300
300
|
// This settle runs regardless of whether data arrived before or during
|
|
301
|
-
// the promise race above, so the merge has time to complete.
|
|
301
|
+
// the promise race above, so the merge has time to complete. A busy host
|
|
302
|
+
// can take longer, which returned the sparse report as status UNKNOWN,
|
|
303
|
+
// so keep waiting for the full status within the same bound.
|
|
302
304
|
await sleep(500);
|
|
305
|
+
while (typeof this.reportSnapshots.get(key)?.gcode_state !== "string" && Date.now() < deadline)
|
|
306
|
+
await sleep(50);
|
|
303
307
|
return this.reportSnapshots.get(key) || null;
|
|
304
308
|
}
|
|
305
309
|
async getPrinter(host, serial, token) {
|
|
@@ -382,6 +386,37 @@ class BambuClientStore {
|
|
|
382
386
|
this.initialReportResolvers.clear();
|
|
383
387
|
}
|
|
384
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Split the A1/P1 camera stream on port 6000 into JPEG frames. Each frame is
|
|
391
|
+
* a 16-byte header (payload size LE, itrack, flags, reserved) and the JPEG.
|
|
392
|
+
*/
|
|
393
|
+
export function cameraFrameParser(onFrame, onError) {
|
|
394
|
+
let buffered = Buffer.alloc(0);
|
|
395
|
+
let failed = false;
|
|
396
|
+
return chunk => {
|
|
397
|
+
if (failed)
|
|
398
|
+
return;
|
|
399
|
+
buffered = Buffer.concat([buffered, chunk]);
|
|
400
|
+
while (buffered.length >= 16) {
|
|
401
|
+
const payloadSize = buffered.readUInt32LE(0);
|
|
402
|
+
if (payloadSize <= 0 || payloadSize > 5000000) {
|
|
403
|
+
failed = true;
|
|
404
|
+
onError(new Error(`camera_snapshot: unreasonable payload size ${payloadSize} from header; auth likely failed.`));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (buffered.length < 16 + payloadSize)
|
|
408
|
+
return;
|
|
409
|
+
const jpeg = buffered.subarray(16, 16 + payloadSize);
|
|
410
|
+
buffered = buffered.subarray(16 + payloadSize);
|
|
411
|
+
if (jpeg[0] !== 0xff || jpeg[1] !== 0xd8) {
|
|
412
|
+
failed = true;
|
|
413
|
+
onError(new Error(`camera_snapshot: payload does not start with JPEG SOI (FF D8); got ${jpeg[0].toString(16)} ${jpeg[1].toString(16)}.`));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
onFrame(Buffer.from(jpeg));
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
385
420
|
export class BambuImplementation {
|
|
386
421
|
constructor(confirm) {
|
|
387
422
|
this.confirm = confirm;
|
|
@@ -392,8 +427,14 @@ export class BambuImplementation {
|
|
|
392
427
|
async confirmHardwareAction(message, physicalCheck = false) {
|
|
393
428
|
if (!physicalCheck && process.env.BAMBU_REQUIRE_CONFIRMATION === "0")
|
|
394
429
|
return;
|
|
395
|
-
if (!this.confirm
|
|
396
|
-
throw new Error("Hardware safety confirmation
|
|
430
|
+
if (!this.confirm) {
|
|
431
|
+
throw new Error("Hardware safety confirmation is unavailable. Use an MCP client with elicitation support. No command was sent.");
|
|
432
|
+
}
|
|
433
|
+
if (!(await this.confirm(message))) {
|
|
434
|
+
throw new Error("Hardware safety confirmation was declined or cancelled in the MCP client. No command was sent. " + (physicalCheck
|
|
435
|
+
? "This confirmation covers a physical check at the printer and always needs a person to accept it."
|
|
436
|
+
: "Some clients decline prompts automatically, for example in a non-interactive permission mode. " +
|
|
437
|
+
"A deliberately headless setup that approves jobs another way can set BAMBU_REQUIRE_CONFIRMATION=0 to skip ordinary print and heating prompts."));
|
|
397
438
|
}
|
|
398
439
|
}
|
|
399
440
|
finishedJobIdentity(status) {
|
|
@@ -408,13 +449,16 @@ export class BambuImplementation {
|
|
|
408
449
|
const finishedJob = this.finishedJobIdentity(status);
|
|
409
450
|
const cleared = this.clearedErrors.get(serial);
|
|
410
451
|
const notices = hiddenHmsNotices(status?.raw, serial, inspection.model);
|
|
452
|
+
const unchipped = unchippedNozzles(status?.raw);
|
|
411
453
|
await this.confirmHardwareAction(`Start a checked print on ${inspection.model.toUpperCase()} (${serial})? Nozzles: ${inspection.nozzleDiameters.join(", ")} mm. ` +
|
|
412
454
|
`Materials: ${inspection.materials.join(", ")}. Peak targets: nozzle ${inspection.maxNozzleTemperature}°C, bed ${inspection.maxBedTemperature}°C, chamber ${inspection.maxChamberTemperature}°C. ` +
|
|
413
455
|
`File SHA-256: ${inspection.sha256}. Confirm the physical spool labels and that the build plate is clear.` +
|
|
414
456
|
(finishedJob !== undefined ? " The printer reports FINISH: remove the previous part and debris before confirming." : "") +
|
|
415
457
|
(cleared ? ` Previously cleared hardware codes: ${cleared.map(describeHmsKey).join(", ")}. Confirm their physical causes have been resolved.` : "") +
|
|
416
458
|
(notices.length ? ` The printer also reports ${notices.join(", ")}, which Bambu lists for this model without text and never shows as a fault; ` +
|
|
417
|
-
`${notices.length === 1 ? "it does" : "they do"} not block printing.` : "")
|
|
459
|
+
`${notices.length === 1 ? "it does" : "they do"} not block printing.` : "") +
|
|
460
|
+
(unchipped.length ? ` Printer ${unchipped.length === 1 ? `nozzle ${unchipped[0]} reports` : `nozzles ${unchipped.join(" and ")} report`} no identity chip, ` +
|
|
461
|
+
`so ${unchipped.length === 1 ? "its" : "their"} diameter and type are the printer's configured settings; confirm they match the installed ${unchipped.length === 1 ? "hotend" : "hotends"}.` : ""), finishedJob !== undefined || !!cleared);
|
|
418
462
|
return { finishedJob, serial, model: inspection.model, notices };
|
|
419
463
|
}
|
|
420
464
|
/** Stop a print whose confirmed situation changed: a newly finished job, or a hidden HMS notice the preflight did not name. */
|
|
@@ -1211,21 +1255,6 @@ export class BambuImplementation {
|
|
|
1211
1255
|
directories: filesByDirectory,
|
|
1212
1256
|
};
|
|
1213
1257
|
}
|
|
1214
|
-
async getFile(host, serial, token, filename) {
|
|
1215
|
-
const printer = new BambuPrinter(host, serial, token);
|
|
1216
|
-
const normalized = filename.replace(/^\/+/, "");
|
|
1217
|
-
const directory = path.posix.dirname(normalized) === "." ? "cache" : path.posix.dirname(normalized);
|
|
1218
|
-
const baseName = path.posix.basename(normalized);
|
|
1219
|
-
let exists = false;
|
|
1220
|
-
await printer.manipulateFiles(async (context) => {
|
|
1221
|
-
const entries = await context.readDir(directory);
|
|
1222
|
-
exists = entries.includes(baseName);
|
|
1223
|
-
});
|
|
1224
|
-
return {
|
|
1225
|
-
name: `${directory}/${baseName}`,
|
|
1226
|
-
exists,
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
1258
|
async uploadFile(host, serial, token, filePath, filename, print, bambuModel, upload) {
|
|
1230
1259
|
const remotePath = normalizedRemotePath(filename);
|
|
1231
1260
|
if (!print) {
|
|
@@ -1609,10 +1638,15 @@ export class BambuImplementation {
|
|
|
1609
1638
|
});
|
|
1610
1639
|
}
|
|
1611
1640
|
/**
|
|
1612
|
-
* Open the TLS-on-6000 socket, send the 80-byte auth packet, and
|
|
1613
|
-
* a
|
|
1641
|
+
* Open the TLS-on-6000 socket, send the 80-byte auth packet, and return
|
|
1642
|
+
* a live JPEG frame. The printer first replays the last frame it buffered,
|
|
1643
|
+
* which after an idle period can show the chamber as it was long ago (a
|
|
1644
|
+
* finished part still on the bed), so that frame and any byte-identical
|
|
1645
|
+
* repeat of it are discarded and the next frame returned; live frames always
|
|
1646
|
+
* differ by sensor noise. If no live frame arrives in time, this fails rather
|
|
1647
|
+
* than return a possibly stale image.
|
|
1614
1648
|
*/
|
|
1615
|
-
async fetchTcpCameraFrame(host, token, timeoutMs) {
|
|
1649
|
+
async fetchTcpCameraFrame(host, token, timeoutMs, port = 6000) {
|
|
1616
1650
|
const tls = await import("node:tls");
|
|
1617
1651
|
const auth = Buffer.alloc(80, 0);
|
|
1618
1652
|
auth.writeUInt32LE(0x40, 0); // payload size
|
|
@@ -1621,11 +1655,9 @@ export class BambuImplementation {
|
|
|
1621
1655
|
auth.write("bblp", 16, 4, "ascii");
|
|
1622
1656
|
auth.write(token, 48, Math.min(32, Buffer.byteLength(token, "ascii")), "ascii");
|
|
1623
1657
|
return new Promise((resolve, reject) => {
|
|
1624
|
-
const chunks = [];
|
|
1625
|
-
let totalLen = 0;
|
|
1626
|
-
const FRAME_HEADER_BYTES = 16;
|
|
1627
|
-
let payloadSize = null;
|
|
1628
1658
|
let settled = false;
|
|
1659
|
+
let frames = 0;
|
|
1660
|
+
let buffered;
|
|
1629
1661
|
const finish = (err, jpeg) => {
|
|
1630
1662
|
if (settled)
|
|
1631
1663
|
return;
|
|
@@ -1639,44 +1671,29 @@ export class BambuImplementation {
|
|
|
1639
1671
|
else
|
|
1640
1672
|
reject(new Error("camera_snapshot: ended without jpeg payload"));
|
|
1641
1673
|
};
|
|
1642
|
-
const
|
|
1674
|
+
const unfinished = (reason) => new Error(frames
|
|
1675
|
+
? `camera_snapshot: ${reason} after only the printer's buffered frame, which may be stale; no live frame arrived. Retry, or raise timeout_ms.`
|
|
1676
|
+
: `camera_snapshot: ${reason} before a full JPEG frame arrived`);
|
|
1677
|
+
const timer = setTimeout(() => finish(unfinished(`timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
1678
|
+
const parse = cameraFrameParser(jpeg => {
|
|
1679
|
+
frames++;
|
|
1680
|
+
if (!buffered)
|
|
1681
|
+
buffered = jpeg;
|
|
1682
|
+
else if (!jpeg.equals(buffered))
|
|
1683
|
+
finish(null, jpeg);
|
|
1684
|
+
}, error => finish(error));
|
|
1643
1685
|
const socket = tls.connect({
|
|
1644
1686
|
host,
|
|
1645
|
-
port
|
|
1687
|
+
port,
|
|
1646
1688
|
rejectUnauthorized: false,
|
|
1647
1689
|
// The printer uses TLS for confidentiality but presents a self-signed cert.
|
|
1648
1690
|
// Same trust posture as the FTPS path (basic-ftp with rejectUnauthorized: false).
|
|
1649
1691
|
}, () => {
|
|
1650
1692
|
socket.write(auth);
|
|
1651
1693
|
});
|
|
1652
|
-
socket.on("data",
|
|
1653
|
-
chunks.push(data);
|
|
1654
|
-
totalLen += data.length;
|
|
1655
|
-
if (payloadSize === null && totalLen >= FRAME_HEADER_BYTES) {
|
|
1656
|
-
const merged = Buffer.concat(chunks, totalLen);
|
|
1657
|
-
payloadSize = merged.readUInt32LE(0);
|
|
1658
|
-
if (payloadSize <= 0 || payloadSize > 5000000) {
|
|
1659
|
-
finish(new Error(`camera_snapshot: unreasonable payload size ${payloadSize} from header; auth likely failed.`));
|
|
1660
|
-
return;
|
|
1661
|
-
}
|
|
1662
|
-
// Reset chunk list to remaining bytes after the header.
|
|
1663
|
-
const remainder = merged.subarray(FRAME_HEADER_BYTES);
|
|
1664
|
-
chunks.length = 0;
|
|
1665
|
-
chunks.push(remainder);
|
|
1666
|
-
totalLen = remainder.length;
|
|
1667
|
-
}
|
|
1668
|
-
if (payloadSize !== null && totalLen >= payloadSize) {
|
|
1669
|
-
const merged = Buffer.concat(chunks, totalLen);
|
|
1670
|
-
const jpeg = merged.subarray(0, payloadSize);
|
|
1671
|
-
if (jpeg[0] !== 0xff || jpeg[1] !== 0xd8) {
|
|
1672
|
-
finish(new Error(`camera_snapshot: payload does not start with JPEG SOI (FF D8); got ${jpeg[0].toString(16)} ${jpeg[1].toString(16)}.`));
|
|
1673
|
-
return;
|
|
1674
|
-
}
|
|
1675
|
-
finish(null, jpeg);
|
|
1676
|
-
}
|
|
1677
|
-
});
|
|
1694
|
+
socket.on("data", parse);
|
|
1678
1695
|
socket.on("error", (err) => finish(err));
|
|
1679
|
-
socket.on("end", () => finish(
|
|
1696
|
+
socket.on("end", () => finish(unfinished("connection ended")));
|
|
1680
1697
|
});
|
|
1681
1698
|
}
|
|
1682
1699
|
/**
|
|
@@ -39,6 +39,8 @@ export interface ValidatedPrinterState {
|
|
|
39
39
|
reportedNozzles: ReportedNozzle[];
|
|
40
40
|
filaments: ReportedFilament[];
|
|
41
41
|
}
|
|
42
|
+
/** Nozzles whose reported diameter and type are configured settings rather than chip readings. */
|
|
43
|
+
export declare function unchippedNozzles(raw: unknown): number[];
|
|
42
44
|
/** The one key format for a reported entry, shared by the gate and clear_hms_errors. */
|
|
43
45
|
export declare function reportedHmsKey(entry: unknown): string;
|
|
44
46
|
/** Bambu's display form of a cleared-code key such as "hms:83887616:131184"; other keys unchanged. */
|
|
@@ -128,6 +128,41 @@ function nozzleTypeAndFlow(value) {
|
|
|
128
128
|
const type = token(value);
|
|
129
129
|
return { type: ["stainlesssteel", "hardenedsteel", "tungstencarbide", "brass"].includes(type) ? type : undefined };
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* A nozzle without an identity chip reports serial "N/A" and keeps the
|
|
133
|
+
* unreliable bit set permanently (stock H2D HS01 nozzles), so its diameter
|
|
134
|
+
* and type are configured settings, as on single-nozzle printers.
|
|
135
|
+
*/
|
|
136
|
+
function hasNoIdentityChip(entry) {
|
|
137
|
+
return typeof entry?.sn === "string" && entry.sn.trim().toUpperCase() === "N/A";
|
|
138
|
+
}
|
|
139
|
+
/** Bit 0 (information unreliable) is accepted only from a nozzle without an identity chip. */
|
|
140
|
+
function unreliableWithoutChip(entry, stat) {
|
|
141
|
+
return (stat & 1) !== 0 && hasNoIdentityChip(entry);
|
|
142
|
+
}
|
|
143
|
+
/** Nozzles whose reported diameter and type are configured settings rather than chip readings. */
|
|
144
|
+
export function unchippedNozzles(raw) {
|
|
145
|
+
const info = raw?.device?.nozzle?.info;
|
|
146
|
+
if (!Array.isArray(info))
|
|
147
|
+
return [];
|
|
148
|
+
return info.flatMap(entry => {
|
|
149
|
+
const index = finiteNumber(entry?.id);
|
|
150
|
+
const stat = finiteNumber(entry?.stat);
|
|
151
|
+
return index !== undefined && Number.isInteger(index) && index >= 0 && index < 16 &&
|
|
152
|
+
stat !== undefined && Number.isInteger(stat) && unreliableWithoutChip(entry, stat) ? [index] : [];
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const loggedUnchippedNozzles = new Set();
|
|
156
|
+
/** Name each unchipped nozzle a check accepted once per printer on stderr, so headless runs still record it. */
|
|
157
|
+
function logUnchippedNozzles(raw, serial) {
|
|
158
|
+
for (const index of unchippedNozzles(raw)) {
|
|
159
|
+
const key = `${serial}:${index}`;
|
|
160
|
+
if (loggedUnchippedNozzles.has(key))
|
|
161
|
+
continue;
|
|
162
|
+
loggedUnchippedNozzles.add(key);
|
|
163
|
+
console.error(`Printer ${serial || "(unknown serial)"} nozzle ${index} reports no identity chip; its diameter and type are the printer's configured settings.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
131
166
|
function reportedNozzles(raw) {
|
|
132
167
|
const info = raw.device?.nozzle?.info;
|
|
133
168
|
const output = [];
|
|
@@ -141,9 +176,13 @@ function reportedNozzles(raw) {
|
|
|
141
176
|
if (index === undefined || !Number.isInteger(index) || index < 0 || diameter === undefined || diameter <= 0)
|
|
142
177
|
throw new Error("Printer reported malformed nozzle identity or diameter.");
|
|
143
178
|
if (entry.stat !== undefined) {
|
|
179
|
+
// BambuStudio DevNozzle: bit 0 set means the information is unreliable;
|
|
180
|
+
// bits 1-2 are 0 normal, 1 abnormal, 2 unknown.
|
|
144
181
|
const stat = finiteNumber(entry.stat);
|
|
145
|
-
if (stat === undefined || !Number.isInteger(stat) || stat < 0 || (stat &
|
|
182
|
+
if (stat === undefined || !Number.isInteger(stat) || stat < 0 || (stat & 6) !== 0)
|
|
146
183
|
throw new Error(`Printer nozzle ${index} information is unreliable, abnormal or unknown.`);
|
|
184
|
+
if ((stat & 1) !== 0 && !unreliableWithoutChip(entry, stat))
|
|
185
|
+
throw new Error(`Printer nozzle ${index} information is unreliable; refresh the nozzle information on the printer.`);
|
|
147
186
|
}
|
|
148
187
|
if (output.some(nozzle => nozzle.index === index))
|
|
149
188
|
throw new Error("Printer reported ambiguous duplicate nozzle indices.");
|
|
@@ -420,6 +459,8 @@ export function validatePrinterState(status, requirements) {
|
|
|
420
459
|
filaments.push({ position, material, trayIndex, nozzleMin, nozzleMax });
|
|
421
460
|
}
|
|
422
461
|
}
|
|
462
|
+
if (nozzles.length)
|
|
463
|
+
logUnchippedNozzles(raw, identity.observedSerial ?? String(status.serial ?? ""));
|
|
423
464
|
return { model, reportedNozzles: nozzles, filaments };
|
|
424
465
|
}
|
|
425
466
|
/** Manual M104 cannot remap filament slots. Validate the material already at the nozzle. */
|
|
@@ -114,6 +114,14 @@ export declare class STLManipulator extends EventEmitter {
|
|
|
114
114
|
* Scale an STL model uniformly or along specific axes
|
|
115
115
|
*/
|
|
116
116
|
scaleSTL(stlFilePath: string, scaleFactors: number | [number, number, number], progressCallback?: ProgressCallback): Promise<string>;
|
|
117
|
+
/**
|
|
118
|
+
* Apply slice_stl's scale and rotations to a copy of the mesh as OrcaSlicer's
|
|
119
|
+
* CLI would, about the model's centre. Orca adds each --rotate* to the
|
|
120
|
+
* object's Euler angles (ModelVolume::rotate), so the result rotates about X,
|
|
121
|
+
* then Y, then Z whatever the argument order, after scaling. The slicer then
|
|
122
|
+
* places the result on the plate.
|
|
123
|
+
*/
|
|
124
|
+
private transformMeshForCli;
|
|
117
125
|
/**
|
|
118
126
|
* Rotate an STL model around specific axes
|
|
119
127
|
*/
|