bambu-printer-mcp 1.0.5 → 1.1.2

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.
@@ -0,0 +1,16 @@
1
+ diff --git a/node_modules/bambu-node/dist/index.js b/node_modules/bambu-node/dist/index.js
2
+ index 916e924..6cc3f33 100644
3
+ --- a/node_modules/bambu-node/dist/index.js
4
+ +++ b/node_modules/bambu-node/dist/index.js
5
+ @@ -1,4 +1,4 @@
6
+ -var B=Object.defineProperty;var he=(e,_,t)=>_ in e?B(e,_,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[_]=t;var r=(e,_)=>B(e,"name",{value:_,configurable:!0});var n=(e,_,t)=>(he(e,typeof _!="symbol"?_+"":_,t),t);var U=0;function H(){return U+=1,U}r(H,"getSequenceId");var v=class v{constructor(_={}){n(this,"extra");this.extra=_}invoke(_){return _.publish({[this.category]:{sequence_id:H(),command:this.command,...this.extra}})}};r(v,"AbstractCommand");var f=v;var A=class A extends f{constructor(t){super({param:t.join(`
7
+ +var B=Object.defineProperty;var he=(e,_,t)=>_ in e?B(e,_,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[_]=t;var r=(e,_)=>B(e,"name",{value:_,configurable:!0});var n=(e,_,t)=>(he(e,typeof _!="symbol"?_+"":_,t),t);var U=0;function H(){return String(U+=1)}r(H,"getSequenceId");var v=class v{constructor(_={}){n(this,"extra");this.extra=_}invoke(_){return _.publish({[this.category]:{sequence_id:H(),command:this.command,...this.extra}})}};r(v,"AbstractCommand");var f=v;var A=class A extends f{constructor(t){super({param:t.join(`
8
+ `)+`
9
+ `});n(this,"category","print")}};r(A,"GCodeCommand");var d=A;function y(e){return!!e?.print&&!!e?.print?.command&&["push_status","resume","gcode_line","gcode_file","project_file"].includes(e.print.command)}r(y,"isPrintMessage");function q(e){return e.command==="gcode_file"}r(q,"isGcodeFileCommand");function j(e){return e.command==="gcode_line"}r(j,"isGCodeLineCommand");function Oe(e){return e.command==="project_file"}r(Oe,"isProjectFileCommand");function J(e){return e.command==="push_status"&&Object.keys(e).length<40}r(J,"isPushStatusCommand");var k={GFB00:{type:"filament",name:"Bambu ABS @base",inherits:"fdm_filament_abs",from:"system",instantiation:"false",activate_air_filtration:["1"],cool_plate_temp:["0"],cool_plate_temp_initial_layer:["0"],eng_plate_temp:["90"],eng_plate_temp_initial_layer:["90"],fan_cooling_layer_time:["30"],fan_max_speed:["80"],fan_min_speed:["10"],filament_cost:["24.99"],filament_density:["1.04"],filament_max_volumetric_speed:["28.6"],filament_type:["ABS"],hot_plate_temp:["90"],hot_plate_temp_initial_layer:["90"],nozzle_temperature:["270"],nozzle_temperature_initial_layer:["260"],nozzle_temperature_range_high:["280"],nozzle_temperature_range_low:["240"],overhang_fan_speed:["80"],overhang_fan_threshold:["25%"],reduce_fan_stop_start_freq:["1"],slow_down_layer_time:["3"],slow_down_min_speed:["20"],textured_plate_temp:["90"],textured_plate_temp_initial_layer:["90"],filament_id:"GFB00",filament_flow_ratio:["0.95"],filament_vendor:["Bambu Lab"]},GFB01:{type:"filament",name:"Bambu ASA @base",inherits:"fdm_filament_asa",from:"system",instantiation:"false",activate_air_filtration:["1"],cool_plate_temp:["0"],cool_plate_temp_initial_layer:["0"],eng_plate_temp:["90"],eng_plate_temp_initial_layer:["90"],fan_cooling_layer_time:["35"],fan_max_speed:["35"],fan_min_speed:["10"],filament_cost:["31.99"],filament_density:["1.05"],filament_max_volumetric_speed:["18"],filament_type:["ASA"],hot_plate_temp:["90"],hot_plate_temp_initial_layer:["90"],nozzle_temperature:["270"],nozzle_temperature_initial_layer:["270"],nozzle_temperature_range_high:["280"],nozzle_temperature_range_low:["240"],overhang_fan_speed:["80"],overhang_fan_threshold:["25%"],reduce_fan_stop_start_freq:["1"],slow_down_layer_time:["12"],slow_down_min_speed:["20"],textured_plate_temp:["90"],textured_plate_temp_initial_layer:["90"],filament_id:"GFB01",filament_flow_ratio:["0.95"],filament_vendor:["Bambu Lab"]},GFN03:{type:"filament",name:"Bambu PA-CF @base",inherits:"fdm_filament_pa",from:"system",instantiation:"false",activate_air_filtration:["1"],cool_plate_temp:["0"],cool_plate_temp_initial_layer:["0"],eng_plate_temp:["100"],eng_plate_temp_initial_layer:["100"],fan_cooling_layer_time:["4"],fan_max_speed:["60"],fan_min_speed:["0"],filament_cost:["84.99"],filament_density:["1.09"],filament_max_volumetric_speed:["8"],filament_type:["PA-CF"],hot_plate_temp:["100"],hot_plate_temp_initial_layer:["100"],nozzle_temperature:["280"],nozzle_temperature_initial_layer:["280"],nozzle_temperature_range_high:["300"],nozzle_temperature_range_low:["260"],overhang_fan_speed:["40"],required_nozzle_HRC:["40"],slow_down_layer_time:["2"],slow_down_min_speed:["20"],temperature_vitrification:["170"],textured_plate_temp:["100"],textured_plate_temp_initial_layer:["100"],filament_id:"GFN03",filament_flow_ratio:["0.96"],filament_vendor:["Bambu Lab"],overhang_fan_threshold:["0%"]},GFN05:{type:"filament",name:"Bambu PA6-CF @base",inherits:"fdm_filament_pa",from:"system",instantiation:"false",activate_air_filtration:["1"],cool_plate_temp:["0"],cool_plate_temp_initial_layer:["0"],eng_plate_temp:["100"],eng_plate_temp_initial_layer:["100"],fan_cooling_layer_time:["5"],fan_max_speed:["30"],fan_min_speed:["10"],filament_cost:["79.99"],filament_density:["1.10"],filament_max_volumetric_speed:["8"],filament_type:["PA6-CF"],hot_plate_temp:["100"],hot_plate_temp_initial_layer:["100"],nozzle_temperature:["275"],nozzle_temperature_initial_layer:["275"],nozzle_temperature_range_high:["300"],nozzle_temperature_range_low:["260"],overhang_fan_speed:["40"],required_nozzle_HRC:["40"],slow_down_layer_time:["2"],slow_down_min_speed:["20"],temperature_vitrification:["170"],textured_plate_temp:["100"],textured_plate_temp_initial_layer:["100"],filament_id:"GFN05",filament_flow_ratio:["0.96"],filament_vendor:["Bambu Lab"],overhang_fan_threshold:["0%"]},GFN08:{type:"filament",name:"Bambu PA6-GF @base",inherits:"fdm_filament_pa",from:"system",instantiation:"false",activate_air_filtration:["1"],cool_plate_temp:["0"],cool_plate_temp_initial_layer:["0"],eng_plate_temp:["100"],eng_plate_temp_initial_layer:["100"],fan_cooling_layer_time:["5"],fan_max_speed:["30"],fan_min_speed:["10"],filament_cost:["59.99"],filament_density:["1.14"],filament_max_volumetric_speed:["8"],filament_type:["PA-GF"],hot_plate_temp:["100"],hot_plate_temp_initial_layer:["100"],nozzle_temperature:["290"],nozzle_temperature_initial_layer:["290"],nozzle_temperature_range_high:["300"],nozzle_temperature_range_low:["260"],overhang_fan_speed:["40"],required_nozzle_HRC:["40"],slow_down_layer_time:["2"],slow_down_min_speed:["20"],temperature_vitrification:["180"],textured_plate_temp:["100"],textured_plate_temp_initial_layer:["100"],filament_id:"GFN08",filament_flow_ratio:["0.96"],filament_vendor:["Bambu Lab"],full_fan_speed_layer:["2"],overhang_fan_threshold:["0%"]},GFN04:{type:"filament",name:"Bambu PAHT-CF @base",inherits:"fdm_filament_pa",from:"system",instantiation:"false",activate_air_filtration:["1"],cool_plate_temp:["0"],cool_plate_temp_initial_layer:["0"],eng_plate_temp:["100"],eng_plate_temp_initial_layer:["100"],fan_cooling_layer_time:["5"],fan_max_speed:["30"],fan_min_speed:["10"],filament_cost:["94.99"],filament_density:["1.06"],filament_max_volumetric_speed:["8"],filament_type:["PA-CF"],hot_plate_temp:["100"],hot_plate_temp_initial_layer:["100"],nozzle_temperature:["290"],nozzle_temperature_initial_layer:["290"],nozzle_temperature_range_high:["300"],nozzle_temperature_range_low:["260"],overhang_fan_speed:["40"],required_nozzle_HRC:["40"],slow_down_layer_time:["2"],slow_down_min_speed:["20"],temperature_vitrification:["180"],textured_plate_temp:["100"],textured_plate_temp_initial_layer:["100"],filament_id:"GFN04",full_fan_speed_layer:["2"],filament_vendor:["Bambu Lab"],filament_flow_ratio:["0.96"],overhang_fan_threshold:["0%"]},GFC00:{type:"filament",name:"Bambu PC @base",inherits:"fdm_filament_pc",from:"system",instantiation:"false",cool_plate_temp:["0"],cool_plate_temp_initial_layer:["0"],eng_plate_temp:["110"],eng_plate_temp_initial_layer:["110"],fan_cooling_layer_time:["30"],fan_max_speed:["60"],fan_min_speed:["10"],filament_cost:["39.99"],filament_density:["1.04"],filament_max_volumetric_speed:["18"],filament_type:["PC"],hot_plate_temp:["110"],hot_plate_temp_initial_layer:["110"],nozzle_temperature:["280"],nozzle_temperature_initial_layer:["270"],nozzle_temperature_range_high:["290"],nozzle_temperature_range_low:["260"],overhang_fan_speed:["60"],overhang_fan_threshold:["25%"],reduce_fan_stop_start_freq:["1"],slow_down_layer_time:["2"],slow_down_min_speed:["20"],temperature_vitrification:["120"],textured_plate_temp:["110"],textured_plate_temp_initial_layer:["110"],filament_id:"GFC00",filament_vendor:["Bambu Lab"],filament_flow_ratio:["0.94"]},GFT01:{type:"filament",name:"Bambu PET-CF @base",inherits:"fdm_filament_pet",from:"system",instantiation:"false",eng_plate_temp:["80"],eng_plate_temp_initial_layer:["80"],fan_cooling_layer_time:["5"],fan_min_speed:["10"],filament_cost:["84.99"],filament_density:["1.29"],filament_max_volumetric_speed:["8"],filament_type:["PET-CF"],hot_plate_temp:["100"],hot_plate_temp_initial_layer:["100"],nozzle_temperature:["270"],nozzle_temperature_initial_layer:["270"],nozzle_temperature_range_high:["290"],nozzle_temperature_range_low:["260"],reduce_fan_stop_start_freq:["1"],temperature_vitrification:["185"],textured_plate_temp:["100"],textured_plate_temp_initial_layer:["100"],filament_start_gcode:[`; filament start gcode
10
+ {if (bed_temperature[current_extruder] >80)||(bed_temperature_initial_layer[current_extruder] >80)}M106 P3 S255
11
+ @@ -330,4 +330,4 @@ M106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]}
12
+
13
+ {if activate_air_filtration[current_extruder] && support_air_filtration}
14
+ M106 P3 S{during_print_exhaust_fan_speed_num[current_extruder]}
15
+ -{endif}`],filament_id:"GFL01",filament_flow_ratio:["0.98"],filament_vendor:["Polymaker"]}};var W;(function(e){e[e.PRINTING=0]="PRINTING",e[e.BED_LEVELING=1]="BED_LEVELING",e[e.HEATBED_PREHEATING=2]="HEATBED_PREHEATING",e[e.XY_MECH_MODE_SWEEP=3]="XY_MECH_MODE_SWEEP",e[e.CHANGE_MATERIAL=4]="CHANGE_MATERIAL",e[e.M400_PAUSE=5]="M400_PAUSE",e[e.FILAMENT_RUNOUT_PAUSE=6]="FILAMENT_RUNOUT_PAUSE",e[e.HOTEND_HEATING=7]="HOTEND_HEATING",e[e.EXTRUDE_COMPENSATION_SCAN=8]="EXTRUDE_COMPENSATION_SCAN",e[e.BED_SCAN=9]="BED_SCAN",e[e.FIRST_LAYER_SCAN=10]="FIRST_LAYER_SCAN",e[e.BE_SURFACE_TYPT_IDENTIFICATION=11]="BE_SURFACE_TYPT_IDENTIFICATION",e[e.SCANNER_EXTRINSIC_PARA_CALI=12]="SCANNER_EXTRINSIC_PARA_CALI",e[e.TOOLHEAD_HOMING=13]="TOOLHEAD_HOMING",e[e.NOZZLE_TIP_CLEANING=14]="NOZZLE_TIP_CLEANING",e[e.EXTRUDER_TEMP_PROTECT_CALI=15]="EXTRUDER_TEMP_PROTECT_CALI",e[e.USER_PAUSE=16]="USER_PAUSE",e[e.TOOLHEAD_SHELL_OFF_PAUSE=17]="TOOLHEAD_SHELL_OFF_PAUSE",e[e.SCANNER_LASER_PARA_CALI=18]="SCANNER_LASER_PARA_CALI",e[e.EXTRUDER_ABSOLUTE_FLOW_CALI=19]="EXTRUDER_ABSOLUTE_FLOW_CALI",e[e.HOTEND_TEMPERATURE_ERROR_PAUSE=20]="HOTEND_TEMPERATURE_ERROR_PAUSE",e[e.HEATED_BED_TEMPERATURE_ERROR_PAUSE=21]="HEATED_BED_TEMPERATURE_ERROR_PAUSE",e[e.FILAMENT_UNLOADING=22]="FILAMENT_UNLOADING",e[e.SKIP_STEP_PAUSE=23]="SKIP_STEP_PAUSE",e[e.FILAMENT_LOADING=24]="FILAMENT_LOADING",e[e.MOTOR_NOISE_CALIBRATION=25]="MOTOR_NOISE_CALIBRATION",e[e.AMS_LOST_PAUSE=26]="AMS_LOST_PAUSE",e[e.HEAT_BREAK_FAN_PAUSE=27]="HEAT_BREAK_FAN_PAUSE",e[e.CHAMBER_TEMPERATURE_CONTROL_ERROR_PAUSE=28]="CHAMBER_TEMPERATURE_CONTROL_ERROR_PAUSE",e[e.CHAMBER_COOLING=29]="CHAMBER_COOLING",e[e.USER_INSERT_GCODE_PAUSE=30]="USER_INSERT_GCODE_PAUSE",e[e.MOTOR_NOISE_SHOWOFF=31]="MOTOR_NOISE_SHOWOFF",e[e.NOZZLE_FILAMENT_COVERED_DETECTED_PAUSE=32]="NOZZLE_FILAMENT_COVERED_DETECTED_PAUSE",e[e.CUTTER_ERROR_PAUSE=33]="CUTTER_ERROR_PAUSE",e[e.FIRST_LAYER_ERROR_PAUSE=34]="FIRST_LAYER_ERROR_PAUSE",e[e.NOZZLE_CLOG_PAUSE=35]="NOZZLE_CLOG_PAUSE"})(W||(W={}));var X;(function(e){e.SILENT="1",e.NORMAL="2",e.SPORT="3",e.LUDICROUS="4"})(X||(X={}));var V;(function(e){e[e.IDLE=0]="IDLE",e[e.READING=1]="READING",e[e.GCODE_TRANS=2]="GCODE_TRANS",e[e.GCODE_RUNNING=3]="GCODE_RUNNING",e[e.ASSISTANT=4]="ASSISTANT",e[e.SWITCH_FILAMENT=5]="SWITCH_FILAMENT",e[e.HAS_FILAMENT=6]="HAS_FILAMENT"})(V||(V={}));var $;(function(e){e.DOWNLOADING="DOWNLOADING",e.UPGRADE_REQUEST="UPGRADE_REQUEST",e.PRE_FLASH_START="PRE_FLASH_START",e.PRE_FLASH_SUCCESS="PRE_FLASH_SUCCESS"})($||($={}));var Z;(function(e){e.UPGRADE_SUCCESS="UPGRADE_SUCCESS",e.DOWNLOAD_FAIL="DOWNLOAD_FAIL",e.FLASH_FAIL="FLASH_FAIL",e.PRE_FLASH_FAIL="PRE_FLASH_FAIL",e.UPGRADE_FAIL="UPGRADE_FAIL"})(Z||(Z={}));var Y;(function(e){e[e.UpgradeNoError=0]="UpgradeNoError",e[e.UpgradeDownloadFailed=1]="UpgradeDownloadFailed",e[e.UpgradeVerifyFailed=2]="UpgradeVerifyFailed",e[e.UpgradeFlashFailed=3]="UpgradeFlashFailed",e[e.UpgradePrinting=4]="UpgradePrinting"})(Y||(Y={}));var K;(function(e){e[e.UNAVAILABLE=0]="UNAVAILABLE",e[e.IDLE=1]="IDLE",e[e.UPGRADING=2]="UPGRADING",e[e.UPGRADE_FINISHED=3]="UPGRADE_FINISHED"})(K||(K={}));var Q;(function(e){e.PLA="PLA",e.ABS="ABS",e.TPU="TPU",e.PC="PC",e.ASA="ASA",e.PA_CF="PA-CF",e.PA6_CF="PA6-CF",e.PET_CF="PET-CF",e.PETG="PETG",e.PETG_CF="PETG-CF",e.PLA_AERO="PLA-AERO",e.PLA_CF="PLA-CF",e.PPA_CF="PPA-CF",e.PPA_GF="PPA-GF",e.PA="PA",e.HIPS="HIPS",e.PPS="PPS",e.PPS_CF="PPS-CF",e.PVA="PVA",e.PLA_S="PLA-S",e.PA_S="PLA-S"})(Q||(Q={}));function Je(e){return k[e]??null}r(Je,"getFilamentConfig");function h(e){return e.command==="push_status"&&Object.keys(e).length>=40}r(h,"isPushAllCommand");function ee(e){return e.command==="gcode_line"&&!!e?.param?.startsWith("M106")}r(ee,"isUpdateFanCommand");function te(e){return e.command==="push_status"&&e.hasOwnProperty("lights_report")}r(te,"isUpdateLightCommand");function _e(e){return e.command==="push_status"&&(e.hasOwnProperty("spd_lvl")||e.hasOwnProperty("spd_mag"))}r(_e,"isUpdateSpeedCommand");function re(e){return e.command===e.command}r(re,"isUpdateStateCommand");function ae(e){return e.command==="gcode_line"&&!!(e?.param?.startsWith("M104")||e?.param?.startsWith("M140"))}r(ae,"isUpdateTempCommand");function g(e){return!!e?.mc_print&&!!e?.mc_print?.command&&["push_info"].includes(e.mc_print.command)}r(g,"isMCPrintMessage");function ht(e){return e.command==="push_info"}r(ht,"isPushInfoCommand");function gt(e){let[,_,t,l]=e.param.match(/^\[(.+?)](?:\[(.+?)])?\s*(.+)$/)??[];return{sequenceId:Number(e.sequence_id),category:_,subcategory:t,content:l,rawParam:e.param}}r(gt,"getCleanPushInfoCommand");function x(e){return e.command==="get_version"}r(x,"isGetVersionCommand");function z(e){return!!e?.info&&!!e?.info?.command&&["get_version"].includes(e.info.command)}r(z,"isInfoMessage");var E=class E extends f{constructor({fileName:t}){super({param:t});n(this,"category","print");n(this,"command","gcode_file");n(this,"ownsResponse",q)}};r(E,"GCodeFileCommand");var ie=E;var S=class S extends d{constructor({gcodes:t}){super(t);n(this,"command","gcode_line");n(this,"ownsResponse",j)}};r(S,"GCodeLineCommand");var ne=S;var G=class G extends f{constructor(){super();n(this,"category","info");n(this,"command","get_version");n(this,"ownsResponse",x)}};r(G,"GetVersionCommand");var b=G;var M=class M extends f{constructor(){super();n(this,"category","pushing");n(this,"command","pushall");n(this,"ownsResponse",h)}};r(M,"PushAllCommand");var c=M;var le;(function(e){e[e.PART_COOLING_FAN=1]="PART_COOLING_FAN",e[e.AUXILIARY_FAN=2]="AUXILIARY_FAN",e[e.CHAMBER_FAN=3]="CHAMBER_FAN"})(le||(le={}));var L=class L extends d{constructor({fan:t,speed:l}){super([`M106 P${t.toString()} S${255*l/100}`]);n(this,"command","gcode_line");n(this,"ownsResponse",ee)}};r(L,"UpdateFanCommand");var me=L;var F=class F extends f{constructor({light:t,mode:l,loopOptions:a={led_on_time:0,led_off_time:0,loop_times:0,interval_time:0}}){super({led_node:t,led_mode:l,...a});n(this,"category","system");n(this,"command","ledctrl");n(this,"ownsResponse",te)}};r(F,"UpdateLightCommand");var oe=F;var N=class N extends f{constructor({speed:t}){super({param:t.toString()});n(this,"category","print");n(this,"command","print_speed");n(this,"ownsResponse",_e)}};r(N,"UpdateSpeedCommand");var pe=N;var C=class C extends f{constructor({state:t}){super();n(this,"category","print");n(this,"command");n(this,"ownsResponse",re);this.command=t}};r(C,"UpdateStateCommand");var se=C;var fe;(function(e){e.BED="M140",e.EXTRUDER="M104"})(fe||(fe={}));var T=class T extends d{constructor({part:t,temperature:l}){super([`${t} S${l}`]);n(this,"command","gcode_line");n(this,"ownsResponse",ae)}};r(T,"UpdateTempCommand");var ue=T;var p;(function(e){e.X1C="X1C",e.X1="X1",e.X1E="X1E",e.P1P="P1P",e.P1S="P1S",e.A1="A1",e.A1M="A1M"})(p||(p={}));import{createId as xe}from"@paralleldrive/cuid2";var ze=new RegExp(/\.[^/.]+$/),R=class R{constructor(_,t,l,a=new Date){n(this,"_jobData");let o=[p.P1P,p.P1S].includes(l);this._jobData={id:xe(),printerId:t,printerModel:l,name:_.subtask_name.replace(ze,""),fileName:_.subtask_name,gcodeName:_.gcode_file,jobOrigin:_.print_type,totalLayerNumber:_.total_layer_num,totalPrintTime:_.mc_remaining_time,status:_.gcode_state,stage:_.stg_cur,percentDone:_.mc_percent,layerNumber:_.layer_num,remainingTime:_.mc_remaining_time,skippedObjects:_.s_obj,statusHistory:[{status:_.gcode_state,changedAt:a}],speedLevelHistory:[{speedLevel:_.spd_lvl,speedMagnitude:_.spd_mag,changedAt:a}],printingStageHistory:[{stage:_.stg_cur,changedAt:a}],temperatureHistory:[{bedTemp:_.bed_temper,bedTargetTemp:_.bed_target_temper,interiorTemp:o?_.frame_temper:_.chamber_temper,nozzleTemp:_.nozzle_temper,nozzleTargetTemp:_.nozzle_target_temper,changedAt:a}],tempLastUpdatedAt:new Date,errorCodes:_.hms.map(m=>({attr:m.attr,code:m.code,thrownAt:a,layerNumber:_.layer_num})),createdAt:a}}update(_){if(!this._jobData)return;let t=this._jobData,l=new Date,a=[p.P1P,p.P1S].includes(this._jobData.printerModel);_.gcode_state&&t.statusHistory.at(-1)?.status!==_.gcode_state&&t.statusHistory.push({status:_.gcode_state,changedAt:l}),_.spd_lvl&&_.spd_mag&&t.speedLevelHistory.at(-1)?.speedLevel!==_.spd_lvl&&t.speedLevelHistory.push({speedLevel:_.spd_lvl,speedMagnitude:_.spd_mag,changedAt:l}),_.stg_cur&&t.printingStageHistory.at(-1)?.stage!==_.stg_cur&&t.printingStageHistory.push({stage:_.stg_cur,changedAt:l});let o=!!(_.bed_temper||_.bed_target_temper||(a?_.frame_temper:_.chamber_temper)||_.nozzle_temper||_.nozzle_target_temper),m=t.tempLastUpdatedAt.getTime()+5e3<l.getTime();if(o&&m){let s=a?_.frame_temper:_.chamber_temper,u=t.temperatureHistory.at(-1);t.temperatureHistory.push({bedTemp:_.bed_temper??u.bedTemp,bedTargetTemp:_.bed_target_temper??u.bedTargetTemp,interiorTemp:s??u.interiorTemp,nozzleTemp:_.nozzle_temper??u.nozzleTemp,nozzleTargetTemp:_.nozzle_target_temper??u.nozzleTargetTemp,changedAt:l}),t.tempLastUpdatedAt=l}let i={status:_.gcode_state??t.status,stage:_.stg_cur??t.stage,percentDone:_.mc_percent??t.percentDone,layerNumber:_.layer_num??t.layerNumber,remainingTime:_.mc_remaining_time??t.remainingTime,skippedObjects:_.s_obj??t.skippedObjects,errorCodes:_.hms?.map((s,u)=>({attr:s.attr??t.errorCodes[u].attr,code:s.code??t.errorCodes[u].code,thrownAt:l??t.errorCodes[u].thrownAt,layerNumber:_.layer_num??t.errorCodes[u].layerNumber}))??t.errorCodes};this._jobData={...this._jobData,...i}}get data(){return this._jobData}};r(R,"Job");var w=R;import*as ce from"mqtt";import*as ye from"eventemitter3";import{createId as be}from"@paralleldrive/cuid2";function I(e){return new Promise(_=>setTimeout(_,e))}r(I,"sleep");function O(e,_){let t={},l=r((a,o,m)=>{for(let i in o){let s=o[i],u=a[i];if(!(i in a&&JSON.stringify(a[i])===JSON.stringify(o[i]))){if(typeof s=="object"&&s!==null&&u!==null&&!Array.isArray(s)){m[i]=m[i]||{},l(a[i],o[i],m[i]);continue}m[i]=o[i]}}},"checkDifferences");return l(e,_,t),t}r(O,"subtractObject");var D=class D extends ye.EventEmitter{constructor(t){super();n(this,"clientOptions");n(this,"id");n(this,"mqttClient");n(this,"config");n(this,"_printerData");n(this,"_printerStatus");n(this,"jobHistory");n(this,"currentJob");this.clientOptions=t,this.id=be(),this.mqttClient=void 0,this._printerData={modules:[],model:void 0},this._printerStatus="OFFLINE",this.jobHistory=[],this.currentJob=null,this.config={host:t.host,port:t.port??8883,accessToken:t.accessToken,serialNumber:t.serialNumber,throwOnOfflineCommands:t.throwOnOfflineCommands??!1,reconnectInterval:t.reconnectInterval??1e3,connectTimeout:t.connectTimeout??2e3,keepAlive:t.keepAlive??20},this.jobHistory=t.jobHistory??[],this.currentJob=t.currentJob??null}emit(t,...l){return super.emit(t,...l)}off(t,l){return super.off(t,l),this}once(t,l){return super.once(t,l),this}on(t,l){return super.on(t,l),this}async connectMQTT(){return new Promise((t,l)=>{if(this.mqttClient)throw new Error("Can't establish a new connection while running another one!");this.mqttClient=ce.connect(`mqtts://${this.config.host}:${this.config.port}`,{username:"bblp",password:this.config.accessToken,reconnectPeriod:this.clientOptions.reconnectInterval,connectTimeout:this.clientOptions.connectTimeout,keepalive:this.clientOptions.keepAlive,resubscribe:!0,rejectUnauthorized:!1}),this.mqttClient.on("connect",async(...a)=>{await this.onConnect(...a),this.emit("client:connect"),t()}),this.mqttClient.on("disconnect",()=>{this.emit("client:disconnect",!1),this.emit("printer:statusUpdate",this._printerStatus,"OFFLINE"),this._printerStatus="OFFLINE",this.currentJob&&this.emit("job:pause",this.currentJob,!0)}),this.mqttClient.on("offline",()=>{this.emit("client:disconnect",!0),this.emit("printer:statusUpdate",this._printerStatus,"OFFLINE"),this._printerStatus="OFFLINE",this.currentJob&&this.emit("job:pause",this.currentJob,!0)}),this.mqttClient.on("message",(a,o)=>this.emit("rawMessage",a,o)),this.mqttClient.on("error",a=>{this.emit("client:error",a),l(a)})})}async connect(){return await Promise.all([this.connectMQTT()])}async disconnect(t=!1,l){return new Promise(a=>this.mqttClient?.end(t,l,a))}subscribe(t){if(this._printerStatus!=="OFFLINE"&&this.config.throwOnOfflineCommands)throw new Error(`Unable to subscribe to topic "${t}" while disconnected from printer!`);return new Promise((l,a)=>{if(!this.mqttClient)return a("Client not connected.");this.mqttClient.subscribe(t,m=>{if(m)return a(`Error subscribing to topic '${t}': ${m.message}`)});let o=r(async(m,i)=>{m===t&&await this.onMessage(i.toString(),t)},"listener");this.mqttClient.on("message",o),l()})}async executeCommand(t){if(this._printerStatus!=="OFFLINE"&&this.config.throwOnOfflineCommands)throw new Error("Unable to send commands while disconnected from printer!");return new Promise((l,a)=>{t.invoke(this).catch(m=>a(m));let o=setTimeout(()=>{a(new Error("Command execution timed out after 5 seconds."))},5e3);this.on("rawMessage",(m,i)=>{let s=JSON.parse(i.toString()),u=Object.keys(s)[0];if(!(z(s)||g(s)||y(s)))return;let P=s[u];P?.command&&t.ownsResponse(P)&&(clearTimeout(o),l(P))})})}async onConnect(t){await this.subscribe(`device/${this.config.serialNumber}/report`),await I(1e3),await this.executeCommand(new b),(this._printerData.model===p.P1P||this._printerData.model===p.P1S||this._printerData.model===p.A1||this._printerData.model===p.A1M)&&(await this.executeCommand(new c),setInterval(async()=>{await this.executeCommand(new c)},5*60*1e3))}async onMessage(t,l){let a=JSON.parse(t),o=Object.keys(a)[0];if(z(a)||g(a)||y(a)){if(y(a)&&(this.emit("message",l,o,a.print),(h(a.print)||J(a.print))&&(this._printerData={...this._printerData,...a.print},this.emit("printer:dataUpdate",this._printerData,a.print),a.print.gcode_state&&a.print.gcode_state!==this._printerStatus))){let m=this._printerStatus,i=a.print.gcode_state;if(this.emit("printer:statusUpdate",m,i),(i==="PREPARE"||i==="SLICING")&&(i="IDLE"),(m==="PREPARE"||m==="SLICING")&&(m="RUNNING"),(m==="IDLE"||m==="FINISH"||m==="FAILED")&&i==="RUNNING")this.currentJob!==null&&this.jobHistory.push(this.currentJob),this.currentJob=new w(this._printerData,this.id,this._printerData.model),this.emit("job:start",this.currentJob);else if(m==="RUNNING"&&(i==="FINISH"||i==="FAILED"||i==="IDLE"))i==="FINISH"?this.currentJob!==null&&this.emit("job:finish",this.currentJob,"SUCCESS"):i==="FAILED"?this.currentJob!==null&&this.emit("job:finish",this.currentJob,"FAILED"):i==="IDLE"&&this.currentJob!==null&&this.emit("job:finish",this.currentJob,"UNEXPECTED"),this.currentJob!==null&&this.jobHistory.push(this.currentJob),this.currentJob=null;else if(m==="RUNNING"&&i==="PAUSE")this.currentJob&&this.emit("job:pause",this.currentJob,!1);else if(m==="PAUSE"&&i==="RUNNING")this.currentJob&&this.emit("job:unpause",this.currentJob);else if(m==="OFFLINE"&&i==="PAUSE")this.emit("job:offlineRecovery",this.currentJob);else if(!(m==="OFFLINE"&&i==="RUNNING")){if(!((m==="IDLE"||m==="OFFLINE")&&(i==="FINISH"||i==="FAILED"||i==="IDLE"))){if(!((m==="FINISH"||i==="FAILED")&&i==="IDLE")){if(m!==i)throw new Error("Edge case detected while updating printer status!")}}}if(this._printerStatus=a.print.gcode_state,this.currentJob){let s=this.currentJob?.data;this.currentJob.update(a.print),this.emit("job:update",this.currentJob,O(s,this.currentJob.data))}}if(g(a)&&this.emit("message",l,o,a.print),z(a)&&(this.emit("message",l,o,a.info),x(a.info))){let m=[...new Set([...this._printerData.modules,...a.info.module])];this._printerData.modules=m,this.emit("printer:dataUpdate",this._printerData,{modules:m});let i=this._printerData.modules.find(s=>s.name==="ota");if(!i)throw new Error("OTA module version data not found, unable to determine printer model!");if(i.sn.startsWith("00M"))this._printerData.model=p.X1C;else if(i.sn.startsWith("00W"))this._printerData.model=p.X1;else if(i.sn.startsWith("03W"))this._printerData.model=p.X1E;else if(i.sn.startsWith("01S"))this._printerData.model=p.P1P;else if(i.sn.startsWith("01P"))this._printerData.model=p.P1S;else if(i.sn.startsWith("030"))this._printerData.model=p.A1;else if(i.sn.startsWith("039"))this._printerData.model=p.A1M;else throw new Error("Printer model not supported!");this.emit("printer:dataUpdate",this._printerData,{model:this._printerData.model})}}}publish(t){return new Promise((l,a)=>{if(!this.mqttClient)return a("Client not connected.");let o=typeof t=="string"?t:JSON.stringify(t),m=`device/${this.config.serialNumber}/request`;this.mqttClient.publish(m,o,i=>{if(i)return a(`Error publishing to topic '${m}': ${i.message}`);l()})})}get data(){return this._printerData}get status(){return this._printerStatus}};r(D,"BambuClient");var de=D;export{f as AbstractCommand,V as AmsRFIDStatus,de as BambuClient,le as Fan,Q as FilamentType,d as GCodeCommand,ie as GCodeFileCommand,ne as GCodeLineCommand,b as GetVersionCommand,w as Job,W as PrintStage,p as PrinterModel,c as PushAllCommand,X as SpeedLevel,me as UpdateFanCommand,oe as UpdateLightCommand,pe as UpdateSpeedCommand,se as UpdateStateCommand,ue as UpdateTempCommand,K as UpgradeDisplayState,Y as UpgradeErrorCode,Z as UpgradeStatusFinishState,$ as UpgradeStatusProgressState,gt as getCleanPushInfoCommand,Je as getFilamentConfig,j as isGCodeLineCommand,q as isGcodeFileCommand,x as isGetVersionCommand,z as isInfoMessage,g as isMCPrintMessage,y as isPrintMessage,Oe as isProjectFileCommand,h as isPushAllCommand,ht as isPushInfoCommand,J as isPushStatusCommand,ee as isUpdateFanCommand,te as isUpdateLightCommand,_e as isUpdateSpeedCommand,re as isUpdateStateCommand,ae as isUpdateTempCommand};
16
+ +{endif}`],filament_id:"GFL01",filament_flow_ratio:["0.98"],filament_vendor:["Polymaker"]}};var W;(function(e){e[e.PRINTING=0]="PRINTING",e[e.BED_LEVELING=1]="BED_LEVELING",e[e.HEATBED_PREHEATING=2]="HEATBED_PREHEATING",e[e.XY_MECH_MODE_SWEEP=3]="XY_MECH_MODE_SWEEP",e[e.CHANGE_MATERIAL=4]="CHANGE_MATERIAL",e[e.M400_PAUSE=5]="M400_PAUSE",e[e.FILAMENT_RUNOUT_PAUSE=6]="FILAMENT_RUNOUT_PAUSE",e[e.HOTEND_HEATING=7]="HOTEND_HEATING",e[e.EXTRUDE_COMPENSATION_SCAN=8]="EXTRUDE_COMPENSATION_SCAN",e[e.BED_SCAN=9]="BED_SCAN",e[e.FIRST_LAYER_SCAN=10]="FIRST_LAYER_SCAN",e[e.BE_SURFACE_TYPT_IDENTIFICATION=11]="BE_SURFACE_TYPT_IDENTIFICATION",e[e.SCANNER_EXTRINSIC_PARA_CALI=12]="SCANNER_EXTRINSIC_PARA_CALI",e[e.TOOLHEAD_HOMING=13]="TOOLHEAD_HOMING",e[e.NOZZLE_TIP_CLEANING=14]="NOZZLE_TIP_CLEANING",e[e.EXTRUDER_TEMP_PROTECT_CALI=15]="EXTRUDER_TEMP_PROTECT_CALI",e[e.USER_PAUSE=16]="USER_PAUSE",e[e.TOOLHEAD_SHELL_OFF_PAUSE=17]="TOOLHEAD_SHELL_OFF_PAUSE",e[e.SCANNER_LASER_PARA_CALI=18]="SCANNER_LASER_PARA_CALI",e[e.EXTRUDER_ABSOLUTE_FLOW_CALI=19]="EXTRUDER_ABSOLUTE_FLOW_CALI",e[e.HOTEND_TEMPERATURE_ERROR_PAUSE=20]="HOTEND_TEMPERATURE_ERROR_PAUSE",e[e.HEATED_BED_TEMPERATURE_ERROR_PAUSE=21]="HEATED_BED_TEMPERATURE_ERROR_PAUSE",e[e.FILAMENT_UNLOADING=22]="FILAMENT_UNLOADING",e[e.SKIP_STEP_PAUSE=23]="SKIP_STEP_PAUSE",e[e.FILAMENT_LOADING=24]="FILAMENT_LOADING",e[e.MOTOR_NOISE_CALIBRATION=25]="MOTOR_NOISE_CALIBRATION",e[e.AMS_LOST_PAUSE=26]="AMS_LOST_PAUSE",e[e.HEAT_BREAK_FAN_PAUSE=27]="HEAT_BREAK_FAN_PAUSE",e[e.CHAMBER_TEMPERATURE_CONTROL_ERROR_PAUSE=28]="CHAMBER_TEMPERATURE_CONTROL_ERROR_PAUSE",e[e.CHAMBER_COOLING=29]="CHAMBER_COOLING",e[e.USER_INSERT_GCODE_PAUSE=30]="USER_INSERT_GCODE_PAUSE",e[e.MOTOR_NOISE_SHOWOFF=31]="MOTOR_NOISE_SHOWOFF",e[e.NOZZLE_FILAMENT_COVERED_DETECTED_PAUSE=32]="NOZZLE_FILAMENT_COVERED_DETECTED_PAUSE",e[e.CUTTER_ERROR_PAUSE=33]="CUTTER_ERROR_PAUSE",e[e.FIRST_LAYER_ERROR_PAUSE=34]="FIRST_LAYER_ERROR_PAUSE",e[e.NOZZLE_CLOG_PAUSE=35]="NOZZLE_CLOG_PAUSE"})(W||(W={}));var X;(function(e){e.SILENT="1",e.NORMAL="2",e.SPORT="3",e.LUDICROUS="4"})(X||(X={}));var V;(function(e){e[e.IDLE=0]="IDLE",e[e.READING=1]="READING",e[e.GCODE_TRANS=2]="GCODE_TRANS",e[e.GCODE_RUNNING=3]="GCODE_RUNNING",e[e.ASSISTANT=4]="ASSISTANT",e[e.SWITCH_FILAMENT=5]="SWITCH_FILAMENT",e[e.HAS_FILAMENT=6]="HAS_FILAMENT"})(V||(V={}));var $;(function(e){e.DOWNLOADING="DOWNLOADING",e.UPGRADE_REQUEST="UPGRADE_REQUEST",e.PRE_FLASH_START="PRE_FLASH_START",e.PRE_FLASH_SUCCESS="PRE_FLASH_SUCCESS"})($||($={}));var Z;(function(e){e.UPGRADE_SUCCESS="UPGRADE_SUCCESS",e.DOWNLOAD_FAIL="DOWNLOAD_FAIL",e.FLASH_FAIL="FLASH_FAIL",e.PRE_FLASH_FAIL="PRE_FLASH_FAIL",e.UPGRADE_FAIL="UPGRADE_FAIL"})(Z||(Z={}));var Y;(function(e){e[e.UpgradeNoError=0]="UpgradeNoError",e[e.UpgradeDownloadFailed=1]="UpgradeDownloadFailed",e[e.UpgradeVerifyFailed=2]="UpgradeVerifyFailed",e[e.UpgradeFlashFailed=3]="UpgradeFlashFailed",e[e.UpgradePrinting=4]="UpgradePrinting"})(Y||(Y={}));var K;(function(e){e[e.UNAVAILABLE=0]="UNAVAILABLE",e[e.IDLE=1]="IDLE",e[e.UPGRADING=2]="UPGRADING",e[e.UPGRADE_FINISHED=3]="UPGRADE_FINISHED"})(K||(K={}));var Q;(function(e){e.PLA="PLA",e.ABS="ABS",e.TPU="TPU",e.PC="PC",e.ASA="ASA",e.PA_CF="PA-CF",e.PA6_CF="PA6-CF",e.PET_CF="PET-CF",e.PETG="PETG",e.PETG_CF="PETG-CF",e.PLA_AERO="PLA-AERO",e.PLA_CF="PLA-CF",e.PPA_CF="PPA-CF",e.PPA_GF="PPA-GF",e.PA="PA",e.HIPS="HIPS",e.PPS="PPS",e.PPS_CF="PPS-CF",e.PVA="PVA",e.PLA_S="PLA-S",e.PA_S="PLA-S"})(Q||(Q={}));function Je(e){return k[e]??null}r(Je,"getFilamentConfig");function h(e){return e.command==="push_status"&&Object.keys(e).length>=40}r(h,"isPushAllCommand");function ee(e){return e.command==="gcode_line"&&!!e?.param?.startsWith("M106")}r(ee,"isUpdateFanCommand");function te(e){return e.command==="push_status"&&e.hasOwnProperty("lights_report")}r(te,"isUpdateLightCommand");function _e(e){return e.command==="push_status"&&(e.hasOwnProperty("spd_lvl")||e.hasOwnProperty("spd_mag"))}r(_e,"isUpdateSpeedCommand");function re(e){return e.command===e.command}r(re,"isUpdateStateCommand");function ae(e){return e.command==="gcode_line"&&!!(e?.param?.startsWith("M104")||e?.param?.startsWith("M140"))}r(ae,"isUpdateTempCommand");function g(e){return!!e?.mc_print&&!!e?.mc_print?.command&&["push_info"].includes(e.mc_print.command)}r(g,"isMCPrintMessage");function ht(e){return e.command==="push_info"}r(ht,"isPushInfoCommand");function gt(e){let[,_,t,l]=e.param.match(/^\[(.+?)](?:\[(.+?)])?\s*(.+)$/)??[];return{sequenceId:Number(e.sequence_id),category:_,subcategory:t,content:l,rawParam:e.param}}r(gt,"getCleanPushInfoCommand");function x(e){return e.command==="get_version"}r(x,"isGetVersionCommand");function z(e){return!!e?.info&&!!e?.info?.command&&["get_version"].includes(e.info.command)}r(z,"isInfoMessage");var E=class E extends f{constructor({fileName:t}){super({param:t});n(this,"category","print");n(this,"command","gcode_file");n(this,"ownsResponse",q)}};r(E,"GCodeFileCommand");var ie=E;var S=class S extends d{constructor({gcodes:t}){super(t);n(this,"command","gcode_line");n(this,"ownsResponse",j)}};r(S,"GCodeLineCommand");var ne=S;var G=class G extends f{constructor(){super();n(this,"category","info");n(this,"command","get_version");n(this,"ownsResponse",x)}};r(G,"GetVersionCommand");var b=G;var M=class M extends f{constructor(){super();n(this,"category","pushing");n(this,"command","pushall");n(this,"ownsResponse",h)}};r(M,"PushAllCommand");var c=M;var le;(function(e){e[e.PART_COOLING_FAN=1]="PART_COOLING_FAN",e[e.AUXILIARY_FAN=2]="AUXILIARY_FAN",e[e.CHAMBER_FAN=3]="CHAMBER_FAN"})(le||(le={}));var L=class L extends d{constructor({fan:t,speed:l}){super([`M106 P${t.toString()} S${255*l/100}`]);n(this,"command","gcode_line");n(this,"ownsResponse",ee)}};r(L,"UpdateFanCommand");var me=L;var F=class F extends f{constructor({light:t,mode:l,loopOptions:a={led_on_time:0,led_off_time:0,loop_times:0,interval_time:0}}){super({led_node:t,led_mode:l,...a});n(this,"category","system");n(this,"command","ledctrl");n(this,"ownsResponse",te)}};r(F,"UpdateLightCommand");var oe=F;var N=class N extends f{constructor({speed:t}){super({param:t.toString()});n(this,"category","print");n(this,"command","print_speed");n(this,"ownsResponse",_e)}};r(N,"UpdateSpeedCommand");var pe=N;var C=class C extends f{constructor({state:t}){super();n(this,"category","print");n(this,"command");n(this,"ownsResponse",re);this.command=t}};r(C,"UpdateStateCommand");var se=C;var fe;(function(e){e.BED="M140",e.EXTRUDER="M104"})(fe||(fe={}));var T=class T extends d{constructor({part:t,temperature:l}){super([`${t} S${l}`]);n(this,"command","gcode_line");n(this,"ownsResponse",ae)}};r(T,"UpdateTempCommand");var ue=T;var p;(function(e){e.X1C="X1C",e.X1="X1",e.X1E="X1E",e.P1P="P1P",e.P1S="P1S",e.P2S="P2S",e.A1="A1",e.A1M="A1M"})(p||(p={}));import{createId as xe}from"@paralleldrive/cuid2";var ze=new RegExp(/\.[^/.]+$/),R=class R{constructor(_,t,l,a=new Date){n(this,"_jobData");let o=[p.P1P,p.P1S].includes(l);this._jobData={id:xe(),printerId:t,printerModel:l,name:_.subtask_name.replace(ze,""),fileName:_.subtask_name,gcodeName:_.gcode_file,jobOrigin:_.print_type,totalLayerNumber:_.total_layer_num,totalPrintTime:_.mc_remaining_time,status:_.gcode_state,stage:_.stg_cur,percentDone:_.mc_percent,layerNumber:_.layer_num,remainingTime:_.mc_remaining_time,skippedObjects:_.s_obj,statusHistory:[{status:_.gcode_state,changedAt:a}],speedLevelHistory:[{speedLevel:_.spd_lvl,speedMagnitude:_.spd_mag,changedAt:a}],printingStageHistory:[{stage:_.stg_cur,changedAt:a}],temperatureHistory:[{bedTemp:_.bed_temper,bedTargetTemp:_.bed_target_temper,interiorTemp:o?_.frame_temper:_.chamber_temper,nozzleTemp:_.nozzle_temper,nozzleTargetTemp:_.nozzle_target_temper,changedAt:a}],tempLastUpdatedAt:new Date,errorCodes:_.hms.map(m=>({attr:m.attr,code:m.code,thrownAt:a,layerNumber:_.layer_num})),createdAt:a}}update(_){if(!this._jobData)return;let t=this._jobData,l=new Date,a=[p.P1P,p.P1S].includes(this._jobData.printerModel);_.gcode_state&&t.statusHistory.at(-1)?.status!==_.gcode_state&&t.statusHistory.push({status:_.gcode_state,changedAt:l}),_.spd_lvl&&_.spd_mag&&t.speedLevelHistory.at(-1)?.speedLevel!==_.spd_lvl&&t.speedLevelHistory.push({speedLevel:_.spd_lvl,speedMagnitude:_.spd_mag,changedAt:l}),_.stg_cur&&t.printingStageHistory.at(-1)?.stage!==_.stg_cur&&t.printingStageHistory.push({stage:_.stg_cur,changedAt:l});let o=!!(_.bed_temper||_.bed_target_temper||(a?_.frame_temper:_.chamber_temper)||_.nozzle_temper||_.nozzle_target_temper),m=t.tempLastUpdatedAt.getTime()+5e3<l.getTime();if(o&&m){let s=a?_.frame_temper:_.chamber_temper,u=t.temperatureHistory.at(-1);t.temperatureHistory.push({bedTemp:_.bed_temper??u.bedTemp,bedTargetTemp:_.bed_target_temper??u.bedTargetTemp,interiorTemp:s??u.interiorTemp,nozzleTemp:_.nozzle_temper??u.nozzleTemp,nozzleTargetTemp:_.nozzle_target_temper??u.nozzleTargetTemp,changedAt:l}),t.tempLastUpdatedAt=l}let i={status:_.gcode_state??t.status,stage:_.stg_cur??t.stage,percentDone:_.mc_percent??t.percentDone,layerNumber:_.layer_num??t.layerNumber,remainingTime:_.mc_remaining_time??t.remainingTime,skippedObjects:_.s_obj??t.skippedObjects,errorCodes:_.hms?.map((s,u)=>({attr:s.attr??t.errorCodes[u].attr,code:s.code??t.errorCodes[u].code,thrownAt:l??t.errorCodes[u].thrownAt,layerNumber:_.layer_num??t.errorCodes[u].layerNumber}))??t.errorCodes};this._jobData={...this._jobData,...i}}get data(){return this._jobData}};r(R,"Job");var w=R;import*as ce from"mqtt";import*as ye from"eventemitter3";import{createId as be}from"@paralleldrive/cuid2";function I(e){return new Promise(_=>setTimeout(_,e))}r(I,"sleep");function O(e,_){let t={},l=r((a,o,m)=>{for(let i in o){let s=o[i],u=a[i];if(!(i in a&&JSON.stringify(a[i])===JSON.stringify(o[i]))){if(typeof s=="object"&&s!==null&&u!==null&&!Array.isArray(s)){m[i]=m[i]||{},l(a[i],o[i],m[i]);continue}m[i]=o[i]}}},"checkDifferences");return l(e,_,t),t}r(O,"subtractObject");var D=class D extends ye.EventEmitter{constructor(t){super();n(this,"clientOptions");n(this,"id");n(this,"mqttClient");n(this,"config");n(this,"_printerData");n(this,"_printerStatus");n(this,"jobHistory");n(this,"currentJob");this.clientOptions=t,this.id=be(),this.mqttClient=void 0,this._printerData={modules:[],model:void 0},this._printerStatus="OFFLINE",this.jobHistory=[],this.currentJob=null,this.config={host:t.host,port:t.port??8883,accessToken:t.accessToken,serialNumber:t.serialNumber,throwOnOfflineCommands:t.throwOnOfflineCommands??!1,reconnectInterval:t.reconnectInterval??1e3,connectTimeout:t.connectTimeout??2e3,keepAlive:t.keepAlive??20},this.jobHistory=t.jobHistory??[],this.currentJob=t.currentJob??null}emit(t,...l){return super.emit(t,...l)}off(t,l){return super.off(t,l),this}once(t,l){return super.once(t,l),this}on(t,l){return super.on(t,l),this}async connectMQTT(){return new Promise((t,l)=>{if(this.mqttClient)throw new Error("Can't establish a new connection while running another one!");this.mqttClient=ce.connect(`mqtts://${this.config.host}:${this.config.port}`,{username:"bblp",password:this.config.accessToken,reconnectPeriod:this.clientOptions.reconnectInterval,connectTimeout:this.clientOptions.connectTimeout,keepalive:this.clientOptions.keepAlive,resubscribe:!0,rejectUnauthorized:!1}),this.mqttClient.on("connect",async(...a)=>{await this.onConnect(...a),this.emit("client:connect"),t()}),this.mqttClient.on("disconnect",()=>{this.emit("client:disconnect",!1),this.emit("printer:statusUpdate",this._printerStatus,"OFFLINE"),this._printerStatus="OFFLINE",this.currentJob&&this.emit("job:pause",this.currentJob,!0)}),this.mqttClient.on("offline",()=>{this.emit("client:disconnect",!0),this.emit("printer:statusUpdate",this._printerStatus,"OFFLINE"),this._printerStatus="OFFLINE",this.currentJob&&this.emit("job:pause",this.currentJob,!0)}),this.mqttClient.on("message",(a,o)=>this.emit("rawMessage",a,o)),this.mqttClient.on("error",a=>{this.emit("client:error",a),l(a)})})}async connect(){return await Promise.all([this.connectMQTT()])}async disconnect(t=!1,l){return new Promise(a=>this.mqttClient?.end(t,l,a))}subscribe(t){if(this._printerStatus!=="OFFLINE"&&this.config.throwOnOfflineCommands)throw new Error(`Unable to subscribe to topic "${t}" while disconnected from printer!`);return new Promise((l,a)=>{if(!this.mqttClient)return a("Client not connected.");this.mqttClient.subscribe(t,m=>{if(m)return a(`Error subscribing to topic '${t}': ${m.message}`)});let o=r(async(m,i)=>{m===t&&await this.onMessage(i.toString(),t)},"listener");this.mqttClient.on("message",o),l()})}async executeCommand(t){if(this._printerStatus!=="OFFLINE"&&this.config.throwOnOfflineCommands)throw new Error("Unable to send commands while disconnected from printer!");return new Promise((l,a)=>{t.invoke(this).catch(m=>a(m));let o=setTimeout(()=>{a(new Error("Command execution timed out after 5 seconds."))},5e3);this.on("rawMessage",(m,i)=>{let s=JSON.parse(i.toString()),u=Object.keys(s)[0];if(!(z(s)||g(s)||y(s)))return;let P=s[u];P?.command&&t.ownsResponse(P)&&(clearTimeout(o),l(P))})})}async onConnect(t){await this.subscribe(`device/${this.config.serialNumber}/report`),await I(1e3),await this.executeCommand(new b),(this._printerData.model===p.P1P||this._printerData.model===p.P1S||this._printerData.model===p.P2S||this._printerData.model===p.A1||this._printerData.model===p.A1M)&&(await this.executeCommand(new c),setInterval(async()=>{await this.executeCommand(new c)},5*60*1e3))}async onMessage(t,l){let a=JSON.parse(t),o=Object.keys(a)[0];if(z(a)||g(a)||y(a)){if(y(a)&&(this.emit("message",l,o,a.print),(h(a.print)||J(a.print))&&(this._printerData={...this._printerData,...a.print},this.emit("printer:dataUpdate",this._printerData,a.print),a.print.gcode_state&&a.print.gcode_state!==this._printerStatus))){let m=this._printerStatus,i=a.print.gcode_state;if(this.emit("printer:statusUpdate",m,i),(i==="PREPARE"||i==="SLICING")&&(i="IDLE"),(m==="PREPARE"||m==="SLICING")&&(m="RUNNING"),(m==="IDLE"||m==="FINISH"||m==="FAILED")&&i==="RUNNING")this.currentJob!==null&&this.jobHistory.push(this.currentJob),this.currentJob=new w(this._printerData,this.id,this._printerData.model),this.emit("job:start",this.currentJob);else if(m==="RUNNING"&&(i==="FINISH"||i==="FAILED"||i==="IDLE"))i==="FINISH"?this.currentJob!==null&&this.emit("job:finish",this.currentJob,"SUCCESS"):i==="FAILED"?this.currentJob!==null&&this.emit("job:finish",this.currentJob,"FAILED"):i==="IDLE"&&this.currentJob!==null&&this.emit("job:finish",this.currentJob,"UNEXPECTED"),this.currentJob!==null&&this.jobHistory.push(this.currentJob),this.currentJob=null;else if(m==="RUNNING"&&i==="PAUSE")this.currentJob&&this.emit("job:pause",this.currentJob,!1);else if(m==="PAUSE"&&i==="RUNNING")this.currentJob&&this.emit("job:unpause",this.currentJob);else if(m==="OFFLINE"&&i==="PAUSE")this.emit("job:offlineRecovery",this.currentJob);else if(!(m==="OFFLINE"&&i==="RUNNING")){if(!((m==="IDLE"||m==="OFFLINE")&&(i==="FINISH"||i==="FAILED"||i==="IDLE"))){if(!((m==="FINISH"||i==="FAILED")&&i==="IDLE")){if(m!==i)throw new Error("Edge case detected while updating printer status!")}}}if(this._printerStatus=a.print.gcode_state,this.currentJob){let s=this.currentJob?.data;this.currentJob.update(a.print),this.emit("job:update",this.currentJob,O(s,this.currentJob.data))}}if(g(a)&&this.emit("message",l,o,a.print),z(a)&&(this.emit("message",l,o,a.info),x(a.info))){let m=[...new Set([...this._printerData.modules,...a.info.module])];this._printerData.modules=m,this.emit("printer:dataUpdate",this._printerData,{modules:m});let i=this._printerData.modules.find(s=>s.name==="ota");if(!i)throw new Error("OTA module version data not found, unable to determine printer model!");if(i.sn.startsWith("00M"))this._printerData.model=p.X1C;else if(i.sn.startsWith("00W"))this._printerData.model=p.X1;else if(i.sn.startsWith("03W"))this._printerData.model=p.X1E;else if(i.sn.startsWith("01S"))this._printerData.model=p.P1P;else if(i.sn.startsWith("01P"))this._printerData.model=p.P1S;else if(i.sn.startsWith("22E"))this._printerData.model=p.P2S;else if(i.sn.startsWith("030"))this._printerData.model=p.A1;else if(i.sn.startsWith("039"))this._printerData.model=p.A1M;else throw new Error("Printer model not supported!");this.emit("printer:dataUpdate",this._printerData,{model:this._printerData.model})}}}publish(t){return new Promise((l,a)=>{if(!this.mqttClient)return a("Client not connected.");let o=typeof t=="string"?t:JSON.stringify(t),m=`device/${this.config.serialNumber}/request`;this.mqttClient.publish(m,o,i=>{if(i)return a(`Error publishing to topic '${m}': ${i.message}`);l()})})}get data(){return this._printerData}get status(){return this._printerStatus}};r(D,"BambuClient");var de=D;export{f as AbstractCommand,V as AmsRFIDStatus,de as BambuClient,le as Fan,Q as FilamentType,d as GCodeCommand,ie as GCodeFileCommand,ne as GCodeLineCommand,b as GetVersionCommand,w as Job,W as PrintStage,p as PrinterModel,c as PushAllCommand,X as SpeedLevel,me as UpdateFanCommand,oe as UpdateLightCommand,pe as UpdateSpeedCommand,se as UpdateStateCommand,ue as UpdateTempCommand,K as UpgradeDisplayState,Y as UpgradeErrorCode,Z as UpgradeStatusFinishState,$ as UpgradeStatusProgressState,gt as getCleanPushInfoCommand,Je as getFilamentConfig,j as isGCodeLineCommand,q as isGcodeFileCommand,x as isGetVersionCommand,z as isInfoMessage,g as isMCPrintMessage,y as isPrintMessage,Oe as isProjectFileCommand,h as isPushAllCommand,ht as isPushInfoCommand,J as isPushStatusCommand,ee as isUpdateFanCommand,te as isUpdateLightCommand,_e as isUpdateSpeedCommand,re as isUpdateStateCommand,ae as isUpdateTempCommand};
package/src/3mf_parser.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import JSZip from 'jszip';
2
2
  import { parseStringPromise } from 'xml2js';
3
- import { ThreeMFData, BambuSlicerConfig, ThreeMFMetadata, ThreeMFObject, ThreeMFBuildItem, AMSFilamentMapping } from './types.js';
3
+ import { ThreeMFData, BambuSlicerConfig, ThreeMFMetadata, ThreeMFObject, ThreeMFBuildItem, AMSFilamentMapping, CollarCharmAnalysis, CollarCharmRole, ThreeMFAmsRequirements, ThreeMFPlateObjects } from './types.js';
4
4
  import * as fs from 'fs/promises';
5
5
 
6
6
  // Parses Bambu Studio's JSON config data
@@ -72,7 +72,12 @@ function parseBambuJSONConfig(jsonData: any): Partial<BambuSlicerConfig> {
72
72
 
73
73
  async function parseBambuConfig(zip: JSZip): Promise<Partial<BambuSlicerConfig>> {
74
74
  // Look for common Bambu config file names
75
- const potentialFiles = ['Metadata/project_settings.config', 'Metadata/model_settings.config', 'Metadata/slice_info.config'];
75
+ const potentialFiles = [
76
+ 'Metadata/project_settings.config',
77
+ 'Metadata/Slic3r_PE.config',
78
+ 'Metadata/model_settings.config',
79
+ 'Metadata/slice_info.config'
80
+ ];
76
81
  let configFile = null;
77
82
  let configContent = '';
78
83
 
@@ -220,4 +225,297 @@ export async function parse3MF(filePath: string): Promise<ThreeMFData> {
220
225
  console.error(`Error parsing 3MF file ${filePath}:`, error);
221
226
  throw new Error(`Failed to parse 3MF file: ${error.message}`);
222
227
  }
223
- }
228
+ }
229
+
230
+ export async function extractBambuTemplateSettings(
231
+ filePath: string,
232
+ outputDir: string
233
+ ): Promise<string> {
234
+ const data = await fs.readFile(filePath);
235
+ const zip = await JSZip.loadAsync(data);
236
+ const potentialFiles = [
237
+ 'Metadata/project_settings.config',
238
+ 'Metadata/Slic3r_PE.config',
239
+ 'Metadata/model_settings.config',
240
+ 'Metadata/slice_info.config'
241
+ ];
242
+
243
+ for (const name of potentialFiles) {
244
+ const file = zip.file(name);
245
+ if (!file) continue;
246
+
247
+ const content = await file.async('string');
248
+ const outputPath = `${outputDir}/${name.replace(/\//g, '_')}`;
249
+ await fs.writeFile(outputPath, content, 'utf8');
250
+ return outputPath;
251
+ }
252
+
253
+ throw new Error(`No slicer settings config found in template 3MF: ${filePath}`);
254
+ }
255
+
256
+ const COLLAR_CHARM_ROLE_COLORS = {
257
+ inner: 'black',
258
+ outer: 'white',
259
+ } as const;
260
+
261
+ const COLLAR_CHARM_ROLE_AMS_SLOTS = {
262
+ // User-facing convention:
263
+ // - inner/smaller object -> AMS 1 slot 1 black
264
+ // - outer/larger object -> AMS 2 slot 1 white
265
+ // Internal absolute tray indices are 0-based:
266
+ // AMS 1 slot 1 -> absolute tray 0
267
+ // AMS 2 slot 1 -> absolute tray 4
268
+ inner: 0,
269
+ outer: 4,
270
+ } as const;
271
+
272
+ export async function analyzeCollarCharm3MF(
273
+ filePath: string,
274
+ plateIndex: number = 0
275
+ ): Promise<CollarCharmAnalysis> {
276
+ const data = await fs.readFile(filePath);
277
+ const zip = await JSZip.loadAsync(data);
278
+ const plateName = `Metadata/plate_${plateIndex + 1}.json`;
279
+ const plateFile = zip.file(plateName);
280
+
281
+ if (!plateFile) {
282
+ throw new Error(`Prepared collar charm 3MF is missing ${plateName}. Slice the project first or export a printable 3MF.`);
283
+ }
284
+
285
+ const plateJson = JSON.parse(await plateFile.async('string'));
286
+ const bboxObjects = Array.isArray(plateJson?.bbox_objects) ? plateJson.bbox_objects : [];
287
+ const usedFilamentPositions = Array.isArray(plateJson?.filament_ids)
288
+ ? plateJson.filament_ids.filter((value: unknown) => Number.isInteger(value)).map((value: number) => value)
289
+ : [];
290
+
291
+ if (bboxObjects.length !== 2) {
292
+ throw new Error(`Collar charm wrapper requires exactly 2 plate objects; found ${bboxObjects.length} in ${plateName}.`);
293
+ }
294
+ if (usedFilamentPositions.length !== 2) {
295
+ throw new Error(`Collar charm wrapper requires exactly 2 used filament positions; found ${usedFilamentPositions.length} in ${plateName}.`);
296
+ }
297
+ if (bboxObjects.length !== usedFilamentPositions.length) {
298
+ throw new Error(`Collar charm wrapper requires object count and used filament count to match; found ${bboxObjects.length} objects and ${usedFilamentPositions.length} filament positions in ${plateName}.`);
299
+ }
300
+
301
+ const normalizedObjects = bboxObjects.map((object: any, objectIndex: number) => {
302
+ const area = Number(object?.area);
303
+ if (!Number.isFinite(area) || area <= 0) {
304
+ throw new Error(`Collar charm object ${objectIndex} in ${plateName} has invalid area metadata.`);
305
+ }
306
+ const name = typeof object?.name === 'string' && object.name.trim().length > 0
307
+ ? object.name.trim()
308
+ : `object_${objectIndex + 1}`;
309
+ return {
310
+ objectIndex,
311
+ name,
312
+ area,
313
+ filamentPosition: usedFilamentPositions[objectIndex],
314
+ };
315
+ });
316
+
317
+ const sortedByArea = [...normalizedObjects].sort((a, b) => a.area - b.area);
318
+ if (sortedByArea[0].area === sortedByArea[1].area) {
319
+ throw new Error(`Collar charm wrapper could not distinguish inner vs outer object because both plate objects have the same reported area in ${plateName}.`);
320
+ }
321
+
322
+ const roles: CollarCharmRole[] = [
323
+ {
324
+ role: 'inner',
325
+ objectIndex: sortedByArea[0].objectIndex,
326
+ name: sortedByArea[0].name,
327
+ area: sortedByArea[0].area,
328
+ filamentPosition: sortedByArea[0].filamentPosition,
329
+ },
330
+ {
331
+ role: 'outer',
332
+ objectIndex: sortedByArea[1].objectIndex,
333
+ name: sortedByArea[1].name,
334
+ area: sortedByArea[1].area,
335
+ filamentPosition: sortedByArea[1].filamentPosition,
336
+ },
337
+ ];
338
+
339
+ const trayByFilamentPosition = new Map<number, number>();
340
+ for (const role of roles) {
341
+ trayByFilamentPosition.set(
342
+ role.filamentPosition,
343
+ COLLAR_CHARM_ROLE_AMS_SLOTS[role.role]
344
+ );
345
+ }
346
+
347
+ const amsSlots = usedFilamentPositions.map((position: number) => {
348
+ const tray = trayByFilamentPosition.get(position);
349
+ if (tray === undefined) {
350
+ throw new Error(`Collar charm wrapper could not assign a tray to project filament position ${position}.`);
351
+ }
352
+ return tray;
353
+ });
354
+
355
+ return {
356
+ plateIndex,
357
+ usedFilamentPositions,
358
+ amsSlots,
359
+ roles,
360
+ };
361
+ }
362
+
363
+ export function getCollarCharmRolePolicy() {
364
+ return {
365
+ colors: COLLAR_CHARM_ROLE_COLORS,
366
+ amsSlots: COLLAR_CHARM_ROLE_AMS_SLOTS,
367
+ };
368
+ }
369
+
370
+ export async function analyze3MFAmsRequirements(
371
+ filePath: string,
372
+ plateIndex: number = 0
373
+ ): Promise<ThreeMFAmsRequirements> {
374
+ const data = await fs.readFile(filePath);
375
+ const zip = await JSZip.loadAsync(data);
376
+ const plateNumber = plateIndex + 1;
377
+ const plateName = `Metadata/plate_${plateNumber}.json`;
378
+ const plateFile = zip.file(plateName);
379
+
380
+ if (!plateFile) {
381
+ throw new Error(`3MF is missing ${plateName}; cannot derive AMS filament order.`);
382
+ }
383
+
384
+ const plateJson = JSON.parse(await plateFile.async('string'));
385
+ const usedFilamentPositions = Array.isArray(plateJson?.filament_ids)
386
+ ? plateJson.filament_ids.filter((value: unknown) => Number.isInteger(value)).map((value: number) => value)
387
+ : [];
388
+
389
+ if (usedFilamentPositions.length === 0) {
390
+ throw new Error(`${plateName} does not declare filament_ids; cannot derive AMS filament order.`);
391
+ }
392
+
393
+ const sliceInfo = zip.file('Metadata/slice_info.config');
394
+ const filamentById = new Map<number, { tray_info_idx: string | null; type: string | null; color: string | null }>();
395
+
396
+ if (sliceInfo) {
397
+ try {
398
+ const parsed = await parseStringPromise(await sliceInfo.async('string'), {
399
+ explicitArray: false,
400
+ mergeAttrs: true,
401
+ });
402
+ const plateNodes = parsed?.config?.plate
403
+ ? Array.isArray(parsed.config.plate) ? parsed.config.plate : [parsed.config.plate]
404
+ : [];
405
+ const selectedPlate =
406
+ plateNodes.find((plate: any) => {
407
+ const metadata = Array.isArray(plate?.metadata)
408
+ ? plate.metadata
409
+ : plate?.metadata
410
+ ? [plate.metadata]
411
+ : [];
412
+ return metadata.some((item: any) => item?.key === 'index' && Number(item?.value) === plateNumber);
413
+ }) || plateNodes[plateIndex] || plateNodes[0];
414
+
415
+ const filamentNodes = selectedPlate?.filament
416
+ ? Array.isArray(selectedPlate.filament) ? selectedPlate.filament : [selectedPlate.filament]
417
+ : [];
418
+ for (const filament of filamentNodes) {
419
+ const id = Number.parseInt(String(filament?.id ?? ''), 10);
420
+ if (!Number.isFinite(id)) continue;
421
+ filamentById.set(id, {
422
+ tray_info_idx: typeof filament?.tray_info_idx === 'string' ? filament.tray_info_idx : null,
423
+ type: typeof filament?.type === 'string' ? filament.type : null,
424
+ color: typeof filament?.color === 'string' ? filament.color : null,
425
+ });
426
+ }
427
+ } catch {
428
+ // plate_N.json already gives the project filament order. Bad
429
+ // slice_info should degrade metadata only, not block AMS planning.
430
+ }
431
+ }
432
+
433
+ return {
434
+ plateIndex,
435
+ usedFilamentPositions,
436
+ filaments: usedFilamentPositions.map((position: number, order: number) => {
437
+ const filamentId = position + 1;
438
+ const byOneBasedId = filamentById.get(filamentId);
439
+ const byOrder = Array.from(filamentById.values())[order];
440
+ const filament = byOneBasedId || byOrder || null;
441
+ return {
442
+ filamentPosition: position,
443
+ filamentId,
444
+ tray_info_idx: filament?.tray_info_idx ?? null,
445
+ type: filament?.type ?? null,
446
+ color: filament?.color ?? null,
447
+ };
448
+ }),
449
+ };
450
+ }
451
+
452
+ export async function analyze3MFPlateObjects(
453
+ filePath: string,
454
+ plateIndex: number = 0
455
+ ): Promise<ThreeMFPlateObjects> {
456
+ const data = await fs.readFile(filePath);
457
+ const zip = await JSZip.loadAsync(data);
458
+ const plateName = `Metadata/plate_${plateIndex + 1}.json`;
459
+ const plateFile = zip.file(plateName);
460
+
461
+ if (!plateFile) {
462
+ throw new Error(`3MF is missing ${plateName}; cannot list plate objects.`);
463
+ }
464
+
465
+ const plateJson = JSON.parse(await plateFile.async('string'));
466
+ const bboxObjects = Array.isArray(plateJson?.bbox_objects) ? plateJson.bbox_objects : [];
467
+ const sliceInfoObjects: Array<{ id: number; name: string | null }> = [];
468
+ const sliceInfo = zip.file('Metadata/slice_info.config');
469
+
470
+ if (sliceInfo) {
471
+ try {
472
+ const parsed = await parseStringPromise(await sliceInfo.async('string'), {
473
+ explicitArray: false,
474
+ mergeAttrs: true,
475
+ });
476
+ const plateNodes = parsed?.config?.plate
477
+ ? Array.isArray(parsed.config.plate) ? parsed.config.plate : [parsed.config.plate]
478
+ : [];
479
+ const plateNumber = plateIndex + 1;
480
+ const selectedPlate =
481
+ plateNodes.find((plate: any) => {
482
+ const metadata = Array.isArray(plate?.metadata)
483
+ ? plate.metadata
484
+ : plate?.metadata
485
+ ? [plate.metadata]
486
+ : [];
487
+ return metadata.some((item: any) => item?.key === 'index' && Number(item?.value) === plateNumber);
488
+ }) || plateNodes[plateIndex] || plateNodes[0];
489
+ const objectNodes = selectedPlate?.object
490
+ ? Array.isArray(selectedPlate.object) ? selectedPlate.object : [selectedPlate.object]
491
+ : [];
492
+ for (const object of objectNodes) {
493
+ const id = Number.parseInt(String(object?.identify_id ?? ''), 10);
494
+ if (!Number.isInteger(id)) continue;
495
+ sliceInfoObjects.push({
496
+ id,
497
+ name: typeof object?.name === 'string' ? object.name : null,
498
+ });
499
+ }
500
+ } catch {
501
+ // Fall back to bbox IDs for older or malformed 3MFs.
502
+ }
503
+ }
504
+
505
+ return {
506
+ plateIndex,
507
+ objects: bboxObjects
508
+ .map((object: any, index: number) => {
509
+ const sliceObject = sliceInfoObjects[index];
510
+ const id = sliceObject?.id ?? Number(object?.id);
511
+ if (!Number.isInteger(id)) return null;
512
+ return {
513
+ id,
514
+ name: sliceObject?.name || (typeof object?.name === 'string' ? object.name : `object_${id}`),
515
+ area: Number.isFinite(Number(object?.area)) ? Number(object.area) : null,
516
+ bbox: object?.bbox ?? null,
517
+ };
518
+ })
519
+ .filter((object: unknown): object is NonNullable<typeof object> => object !== null),
520
+ };
521
+ }
@@ -0,0 +1,47 @@
1
+ export function normalizeBridgeAmsTrayValue(value: unknown, label: string): number {
2
+ let numeric: number;
3
+ if (typeof value === "number") {
4
+ numeric = value;
5
+ } else if (typeof value === "string" && value.trim() !== "") {
6
+ numeric = Number(value);
7
+ } else {
8
+ throw new Error(`${label} must be an integer tray index; got ${JSON.stringify(value)}`);
9
+ }
10
+
11
+ if (
12
+ !Number.isInteger(numeric) ||
13
+ numeric < -1 ||
14
+ (numeric > 15 && numeric < 128) ||
15
+ numeric > 254
16
+ ) {
17
+ throw new Error(
18
+ `${label} must be an integer in [-1, 15] (absolute tray) or 128-254 (HT/external); got ${JSON.stringify(value)}`
19
+ );
20
+ }
21
+
22
+ return numeric;
23
+ }
24
+
25
+ function isNumericMappingKey(key: string): boolean {
26
+ const numeric = Number(key);
27
+ return Number.isInteger(numeric) && numeric >= 0;
28
+ }
29
+
30
+ export function normalizeAmsMappingObject(mapping: Record<string, unknown>, label = "ams_mapping"): number[] {
31
+ return Object.entries(mapping)
32
+ .sort(([leftKey], [rightKey]) => {
33
+ const leftNumeric = isNumericMappingKey(leftKey);
34
+ const rightNumeric = isNumericMappingKey(rightKey);
35
+ if (leftNumeric && rightNumeric) return Number(leftKey) - Number(rightKey);
36
+ if (leftNumeric) return -1;
37
+ if (rightNumeric) return 1;
38
+ return 0;
39
+ })
40
+ .map(([key, value]) => normalizeBridgeAmsTrayValue(value, `${label}[${key}]`));
41
+ }
42
+
43
+ export function hasAmsMappingInput(value: unknown): boolean {
44
+ if (Array.isArray(value)) return value.length > 0;
45
+ if (value && typeof value === "object") return Object.keys(value as Record<string, unknown>).length > 0;
46
+ return value !== undefined && value !== null;
47
+ }