├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── templates.json └── templates │ ├── bed.ejs │ ├── config │ ├── accelerometers.ejs │ ├── accessories.ejs │ ├── axes.ejs │ ├── compensation │ │ ├── index.ejs │ │ ├── mesh.ejs │ │ └── orthogonal.ejs │ ├── drivers │ │ ├── closedLoop.ejs │ │ ├── currentReduction.ejs │ │ ├── external.ejs │ │ ├── index.ejs │ │ └── smart.ejs │ ├── endstops.ejs │ ├── extruders.ejs │ ├── fans.ejs │ ├── general.ejs │ ├── heaters.ejs │ ├── index.ejs │ ├── kinematics.ejs │ ├── lasers.ejs │ ├── ledStrips.ejs │ ├── miscellaneous.ejs │ ├── network.ejs │ ├── probes.ejs │ ├── sensors.ejs │ ├── spindles.ejs │ └── tools.ejs │ ├── deployprobe.ejs │ ├── homeall │ ├── cartesian.ejs │ ├── corexy.ejs │ ├── corexz.ejs │ ├── hangprinter.ejs │ ├── index.ejs │ ├── polar.ejs │ └── scara.ejs │ ├── homeaxis.ejs │ ├── homedelta.ejs │ ├── pause.ejs │ ├── resume.ejs │ ├── retractprobe.ejs │ ├── runonce.ejs │ ├── tfree.ejs │ ├── tpost.ejs │ └── tpre.ejs ├── scss └── style.scss ├── src ├── App.vue ├── assets │ └── logo.svg ├── components │ ├── Card.vue │ ├── ConfigSection.vue │ ├── ProgressIcon.vue │ ├── Sidebar.vue │ ├── calculators │ │ ├── BaseCalculator.vue │ │ ├── StealthChopCalculator.vue │ │ ├── StepsPerMmCalculator.vue │ │ └── ThermistorCalculator.vue │ ├── dialogs │ │ ├── BaseDialog.vue │ │ ├── CoreKinematicsDialog.vue │ │ ├── DeltaKinematicsDialog.vue │ │ ├── HangprinterKinematicsDialog.vue │ │ ├── HeaterModelDialog.vue │ │ ├── HeaterMonitorsDialog.vue │ │ ├── PolarKinematicsDialog.vue │ │ ├── ScaraKinematicsDialog.vue │ │ └── ToolDialog.vue │ ├── inputs │ │ ├── CheckInput.vue │ │ ├── DriverList.vue │ │ ├── DriverSelection.vue │ │ ├── ExtruderList.vue │ │ ├── FanList.vue │ │ ├── HeaterList.vue │ │ ├── HomingSpeedsInput.vue │ │ ├── IpInput.vue │ │ ├── NumberInput.vue │ │ ├── OptionalNumberInput.vue │ │ ├── PortInput.vue │ │ ├── ProbeTypeInput.vue │ │ ├── RatioInput.vue │ │ ├── SelectInput.vue │ │ ├── SensorList.vue │ │ ├── TextInput.vue │ │ └── TwoNumberInput.vue │ ├── monaco │ │ ├── GCodeInput.vue │ │ ├── GCodeOutput.vue │ │ ├── monaco-gcode.ts │ │ └── monaco-worker.ts │ └── sections │ │ ├── Accelerometers.vue │ │ ├── Accessories.vue │ │ ├── Axes.vue │ │ ├── Compensation.vue │ │ ├── Drivers.vue │ │ ├── Endstops.vue │ │ ├── Expansion.vue │ │ ├── Extruders.vue │ │ ├── Fans.vue │ │ ├── General.vue │ │ ├── Heaters.vue │ │ ├── Kinematics.vue │ │ ├── Lasers.vue │ │ ├── LedStrips.vue │ │ ├── Miscellaneous.vue │ │ ├── Network.vue │ │ ├── Probes.vue │ │ ├── Sensors.vue │ │ ├── Spindles.vue │ │ └── Tools.vue ├── directives │ ├── VPreset.ts │ └── VTitle.ts ├── main.ts ├── router │ └── index.ts ├── shims-vue.d.ts ├── store │ ├── BaseBoard.ts │ ├── Boards.ts │ ├── ExpansionBoards.ts │ ├── compatibility │ │ ├── LegacyBoards.ts │ │ ├── LegacyExpansionBoards.ts │ │ ├── LegacyPreset.ts │ │ └── index.ts │ ├── defaults.ts │ ├── index.ts │ ├── model │ │ ├── ConfigDriver.ts │ │ ├── ConfigPort.ts │ │ ├── ConfigTempSensor.ts │ │ ├── ConfigToolModel.ts │ │ └── index.ts │ ├── render.ts │ └── sections.ts ├── utils.ts └── views │ ├── ConfigurationView.vue │ ├── PresetsView.vue │ ├── StartView.vue │ └── SummaryView.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConfigTool 2 | 3 | This is the new version of the RepRapFirmware Configuration Tool. It is written in TypeScript using the Vue 3 and Bootstrap-Vue libraries. 4 | 5 | ## Recommended IDE Setup 6 | 7 | Vue recommends [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. 12 | 13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 14 | 15 | 1. Disable the built-in TypeScript Extension 16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 19 | 20 | ## Customize configuration 21 | 22 | See [Vite Configuration Reference](https://vitejs.dev/config/). 23 | 24 | ## Project Setup 25 | 26 | ```sh 27 | npm install 28 | ``` 29 | 30 | ### Compile and Hot-Reload for Development 31 | 32 | ```sh 33 | npm run dev 34 | ``` 35 | 36 | ### Type-Check, Compile and Minify for Production 37 | 38 | ```sh 39 | npm run build 40 | ``` 41 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RRF Config Tool 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "configtool", 3 | "version": "3.5.12", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "run-p type-check \"build-only {@}\" --", 7 | "preview": "vite preview --port 5050", 8 | "build-only": "vite build", 9 | "type-check": "vue-tsc --build --force" 10 | }, 11 | "type": "module", 12 | "dependencies": { 13 | "@duet3d/monacotokens": "~3.5.3", 14 | "@duet3d/objectmodel": "~3.5.5", 15 | "@popperjs/core": "^2.11.5", 16 | "bootstrap": "^5.3.3", 17 | "bootstrap-icons": "^1.11.3", 18 | "ejs": "^3.1.9", 19 | "file-saver": "^2.0.5", 20 | "jszip": "^3.10.1", 21 | "monaco-editor": "^0.52.0", 22 | "pinia": "^3.0.2", 23 | "vue": "^3.2.36", 24 | "vue-router": "^4.3.0" 25 | }, 26 | "devDependencies": { 27 | "@tsconfig/node20": "^20.1.4", 28 | "@types/bootstrap": "^5.2.10", 29 | "@types/ejs": "^3.1.5", 30 | "@types/file-saver": "^2.0.7", 31 | "@types/node": "^22.7.5", 32 | "@vitejs/plugin-vue": "^5.0.4", 33 | "@vue/tsconfig": "^0.7.0", 34 | "npm-run-all2": "^7.0.1", 35 | "sass": "~1.71.1", 36 | "typescript": "^5.7.2", 37 | "vite": "^6.0.7", 38 | "vue-tsc": "^2.0.12" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Duet3D/ConfigTool/a449a56d1312a0237a4cfb9c4886f6971d83c0ca/public/favicon.ico -------------------------------------------------------------------------------- /public/templates.json: -------------------------------------------------------------------------------- 1 | { 2 | "Creality": { 3 | "Ender 3 Pro": "ender3pro.json" 4 | }, 5 | "Others": { 6 | "Foobar 3000": "foobar3000.json" 7 | } 8 | } -------------------------------------------------------------------------------- /public/templates/bed.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; <%- filename %> 3 | ; called to <%- model.isDelta ? 'perform automatic delta calibration' : 'level the bed' %> 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } 8 | 9 | if (model.isDelta) { 10 | if (model.configTool.delta.homeFirst) { -%> 11 | G28 ; home the towers first 12 | <% } -%> 13 | ; Probe the bed at <%- model.configTool.delta.peripheralPoints %> peripheral and <%- model.configTool.delta.halfwayPoints %> halfway points, and perform <%- model.configTool.delta.factors %>-factor auto compensation 14 | ; Before running this, you should have set up your Z-probe trigger height to suit your build, in the G31 command in config.g. 15 | <% for (let i = 0; i < model.configTool.delta.probePoints.length; i++) { 16 | const point = model.configTool.delta.probePoints[i]; -%> 17 | G30 <%- params({ 18 | P: i, 19 | X: point.x, 20 | Y: point.y, 21 | H: point.heightCorrection, 22 | Z: -99999, 23 | S: (i === model.configTool.delta.probePoints.length - 1) ? model.configTool.delta.factors : undefined 24 | }) %> 25 | <% } -%> 26 | ; Use S-1 for measurements only, without calculations. Use S4 for endstop heights and Z-height only. Use S6 for full 6 factors 27 | ; If your Z probe has significantly different trigger heights depending on XY position, adjust the H parameters in the G30 commands accordingly. The value of each H parameter should be (trigger height at that XY position) - (trigger height at centre of bed) 28 | <% } else { -%> 29 | G29 ; call mesh bed compensation 30 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/accelerometers.ejs: -------------------------------------------------------------------------------- 1 | <% for (const board of model.boards) { 2 | if (board.accelerometer !== null) { 3 | const spiCsPort = getPort(ConfigPortFunction.accelerometerSpiCs, board.canAddress), intPort = getPort(ConfigPortFunction.accelerometerInt, board.canAddress); -%> 4 | M955 <%- params({ 5 | P: [board.canAddress ? `${board.canAddress}.0` : 0], 6 | C: (spiCsPort && intPort) ? `${spiCsPort.toString()}+${intPort.toString()}` : undefined, 7 | I: board.accelerometer.orientation 8 | }) %> ; configure accelerometer on board #<%- board.canAddress ?? 0 %> 9 | <% } 10 | } %> -------------------------------------------------------------------------------- /public/templates/config/accessories.ejs: -------------------------------------------------------------------------------- 1 | <% if (model.boards.length > 0 && model.boards[0].supportsDirectDisplay) { 2 | const directDisplay = model.boards[0].directDisplay; 3 | if (directDisplay !== null) { 4 | let typeCode = 0; 5 | switch (directDisplay.screen.controller) { 6 | case DirectDisplayController.ST7290: 7 | typeCode = 1; 8 | break; 9 | case DirectDisplayController.ST7567: 10 | typeCode = 2; 11 | break; 12 | case DirectDisplayController.ILI9488: 13 | typeCode = 3; 14 | break; 15 | } -%> 16 | M918 <%- params({ 17 | P: typeCode, 18 | E: directDisplay.encoder?.pulsesPerClick, 19 | F: directDisplay.screen.spiFreq, 20 | C: directDisplay.screen.contrast, 21 | R: directDisplay.screen.resistorRatio 22 | }) %> ; configure direct-connect display 23 | <% } 24 | } 25 | 26 | // Configure PanelDue if applicable 27 | if (model.panelDueChannel >= 0) { -%> 28 | M575 <%- params({ 29 | P: model.panelDueChannel, 30 | S: model.configTool.panelDueChecksum ? 1 : 0, 31 | B: model.configTool.panelDueBaudRate 32 | }) %> ; configure PanelDue support 33 | <% } -%> 34 | -------------------------------------------------------------------------------- /public/templates/config/axes.ejs: -------------------------------------------------------------------------------- 1 | <% const mapping = {}, minsMaxs = {}; 2 | const microstepping = {}, microsteppingInterpolated = {}, currents = {}, stepsPerMm = {}; 3 | const jerk = {}, maxSpeed = {}, acceleration = {}; 4 | for (const axis of model.move.axes) { 5 | mapping[axis.letter] = axis.drivers; 6 | if (!model.isDelta || !['X', 'Y', 'Z'].includes(axis.letter)) { 7 | // Don't set XYZ min/max in Delta configs 8 | minsMaxs[axis.letter] = [axis.min, axis.max]; 9 | } 10 | if (axis.microstepping.interpolated) { 11 | microsteppingInterpolated[axis.letter] = axis.microstepping.value; 12 | } else { 13 | microstepping[axis.letter] = axis.microstepping.value; 14 | } 15 | stepsPerMm[axis.letter] = axis.stepsPerMm; 16 | jerk[axis.letter] = axis.jerk * 60; 17 | maxSpeed[axis.letter] = axis.speed * 60; 18 | acceleration[axis.letter] = axis.acceleration; 19 | for (const driver of axis.drivers) { 20 | const board = model.boards.find(board => board.canAddress === driver.board); 21 | if (board) { 22 | const boardDefinition = (board.canAddress > 0) ? getExpansionBoardDefinition(board) : getBoardDefinition(model); 23 | if (boardDefinition?.hasSmartDrivers) { 24 | currents[axis.letter] = axis.current; 25 | break; 26 | } 27 | } 28 | } 29 | } -%> 30 | M584 <%- params(mapping) %> ; set axis mapping 31 | <% if (Object.keys(microstepping).length > 0) { -%> 32 | M350 <%- params({ ...microstepping, I: 0 }) %> ; configure microstepping without interpolation 33 | <% } 34 | if (Object.keys(microsteppingInterpolated).length > 0) { -%> 35 | M350 <%- params({ ...microsteppingInterpolated, I: 1}) %> ; configure microstepping with interpolation 36 | <% } 37 | if (Object.keys(currents).length > 0) { 38 | if (preview) { -%> 39 | ; NOTE: See Smart Drivers section for motor currents 40 | <% } else { -%> 41 | M906 <%- params(currents) %> ; set axis driver currents 42 | <% } 43 | } -%> 44 | M92 <%- params(stepsPerMm) %> ; configure steps per mm 45 | <% if (Object.keys(minsMaxs).length > 0) { -%> 46 | M208 <%- params(minsMaxs) %> ; set minimum and maximum axis limits 47 | <% } -%> 48 | M566 <%- params(jerk) %> ; set maximum instantaneous speed changes (mm/min) 49 | M203 <%- params(maxSpeed) %> ; set maximum speeds (mm/min) 50 | M201 <%- params(acceleration) %> ; set accelerations (mm/s^2) 51 | -------------------------------------------------------------------------------- /public/templates/config/compensation/index.ejs: -------------------------------------------------------------------------------- 1 | <%- sections({ 2 | "Mesh Bed Compensation": await render("config/compensation/mesh"), 3 | "Orthogonal Axis Compensation": await render("config/compensation/orthogonal") 4 | }) -%> -------------------------------------------------------------------------------- /public/templates/config/compensation/mesh.ejs: -------------------------------------------------------------------------------- 1 | <% let meshEnabled = false; 2 | if (model.isDelta) { 3 | if (model.move.compensation.probeGrid.radius > 0 && model.move.compensation.probeGrid.spacings[0] > 0) { -%> 4 | M557 <%- params({ 5 | R: model.move.compensation.probeGrid.radius, 6 | S: model.move.compensation.probeGrid.spacings 7 | }) %> ; define grid for mesh bed compensation 8 | <% } 9 | } else if (model.move.compensation.probeGrid.maxs[0] > model.move.compensation.probeGrid.mins[0] && 10 | model.move.compensation.probeGrid.maxs[1] > model.move.compensation.probeGrid.mins[1] && 11 | model.move.compensation.probeGrid.spacings[0] > 0 && 12 | model.move.compensation.probeGrid.spacings[1] > 0) { -%> 13 | M557 <%- params({ 14 | [model.move.compensation.probeGrid.axes[0]]: [model.move.compensation.probeGrid.mins[0], model.move.compensation.probeGrid.maxs[0]], 15 | [model.move.compensation.probeGrid.axes[1]]: [model.move.compensation.probeGrid.mins[1], model.move.compensation.probeGrid.maxs[1]], 16 | S: model.move.compensation.probeGrid.spacings 17 | }) %> ; define grid for mesh bed compensation 18 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/compensation/orthogonal.ejs: -------------------------------------------------------------------------------- 1 | <% if (model.move.compensation.skew.tanXY !== 0 || 2 | model.move.compensation.skew.tanXZ !== 0 || 3 | model.move.compensation.skew.tanYZ !== 0) { 4 | const xParam = model.configTool.orthogonalDistance * model.move.compensation.skew.tanXY; 5 | const yParam = model.configTool.orthogonalDistance * model.move.compensation.skew.tanYZ; 6 | const zParam = model.configTool.orthogonalDistance * model.move.compensation.skew.tanXZ; -%> 7 | M556 <%- params({ 8 | S: model.configTool.orthogonalDistance, 9 | X: precise(xParam, 5), 10 | Y: precise(yParam, 5), 11 | Z: precise(zParam, 5), 12 | P: model.move.compensation.skew.compensateXY ? undefined : 1 13 | }) %> ; configure orthogonal axis compensation parameters 14 | <% } %> -------------------------------------------------------------------------------- /public/templates/config/drivers/closedLoop.ejs: -------------------------------------------------------------------------------- 1 | <% // Get the list of closed loop drivers first 2 | const closedLoopDrivers = []; 3 | for (const driver of model.configTool.drivers) { 4 | if (driver.closedLoop.encoderType !== ConfigDriverClosedLoopEncoderType.none) { 5 | const boardDefinition = model.getBoardDefinition(driver.id.board); 6 | if (boardDefinition?.hasClosedLoopDrivers) { 7 | closedLoopDrivers.push(driver); 8 | } 9 | } 10 | } 11 | 12 | // Configure them 13 | for (const driver of closedLoopDrivers) { 14 | let type = "unknown"; 15 | switch (driver.closedLoop.encoderType) { 16 | case ConfigDriverClosedLoopEncoderType.quadratureOnAxis: 17 | case ConfigDriverClosedLoopEncoderType.quadratureOnMotor: 18 | type = "quadrature"; 19 | break; 20 | case ConfigDriverClosedLoopEncoderType.magnetic: 21 | type = "magnetic"; 22 | break; 23 | } -%> 24 | M569.1 <%- params({ 25 | P: driver.id, 26 | T: driver.closedLoop.encoderType, 27 | C: (driver.closedLoop.mode !== ConfigDriverClosedLoopEncoderType.magnetic && driver.closedLoop.countsPerFullStep !== null) ? driver.closedLoop.countsPerFullStep : undefined 28 | }) %> ; driver <%- driver.id %> has a <%- type %> encoder<%- (driver.closedLoop.mode !== ConfigDriverClosedLoopEncoderType.magnetic && driver.closedLoop.countsPerFullStep !== null) ? ` with ${driver.closedLoop.countsPerFullStep} CPS` : "" %> 29 | <% } 30 | 31 | if (preview && closedLoopDrivers.length === 0) { -%> 32 | ; no drivers with encoders configured 33 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/drivers/currentReduction.ejs: -------------------------------------------------------------------------------- 1 | <% if (model.move.idle.timeout > 0) { -%> 2 | M906 I<%- precise(model.move.idle.factor * 100) %> ; set motor current idle factor 3 | M84 S<%- model.move.idle.timeout %> ; set motor current idle timeout 4 | <% } else if (preview) { -%> 5 | ; NOTE: This section is not used in your configuration 6 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/drivers/external.ejs: -------------------------------------------------------------------------------- 1 | <% // Get the list of external drivers first 2 | const externalDrivers = []; 3 | for (const driver of model.configTool.drivers) { 4 | if (!driver.id.board && model.boardDefinition) { 5 | if ((driver.id.driver < model.boardDefinition.numDrivers && !model.boardDefinition.hasSmartDrivers) || 6 | (driver.id.driver >= model.boardDefinition.numDrivers && model.configTool.expansionBoard !== null && !ExpansionBoards[model.configTool.expansionBoard].hasSmartDrivers)) 7 | { 8 | externalDrivers.push(driver); 9 | } 10 | } else { 11 | const board = model.boards.find(board => board.canAddress === driver.id.board); 12 | if (board) { 13 | const boardDefinition = getExpansionBoardDefinition(board); 14 | if (boardDefinition && !boardDefinition.hasSmartDrivers) { 15 | externalDrivers.push(driver); 16 | } 17 | } 18 | } 19 | } 20 | 21 | // Configure each driver 22 | let hasExternalDrivers = false; 23 | for (const driver of externalDrivers) { 24 | const axis = model.move.axes.find(axis => axis.drivers.some(axisDriver => axisDriver.equals(driver.id))); 25 | const extruderIndex = model.move.extruders.findIndex(extruder => (extruder.driver !== null) && extruder.driver.equals(driver.id)); 26 | if (axis || extruderIndex >= 0) { -%> 27 | M569 <%- params({ 28 | P: driver.id, 29 | S: driver.forwards ? 1 : 0, 30 | R: driver.external.enablePolarity ? 1 : 0, 31 | T: [driver.external.minStepPulse, driver.external.minStepInterval, driver.external.dirSetupTime, driver.external.holdTime] 32 | }) %> ; driver <%- driver.id.toString() %> goes <%- driver.forwards ? "forwards" : "backwards" %> and requires an <%- driver.enablePolarity ? "active-high" : "active-low" %> enable signal (<%- axis ? `${axis.letter} axis` : `extruder ${extruderIndex}` %>) 33 | <% hasExternalDrivers = true; 34 | } 35 | } 36 | 37 | if (preview && !hasExternalDrivers) { -%> 38 | ; no external drivers mapped to axes or extruders 39 | <% } -%> 40 | -------------------------------------------------------------------------------- /public/templates/config/drivers/index.ejs: -------------------------------------------------------------------------------- 1 | <%- sections({ 2 | "Smart Drivers": await render("config/drivers/smart"), 3 | "Motor Idle Current Reduction": await render("config/drivers/currentReduction"), 4 | "External Drivers": await render("config/drivers/external"), 5 | "Closed-Loop Drivers": await render("config/drivers/closedLoop") 6 | }) -%> -------------------------------------------------------------------------------- /public/templates/config/drivers/smart.ejs: -------------------------------------------------------------------------------- 1 | <% // Get the list of smart drivers first 2 | const smartDrivers = []; 3 | for (const driver of model.configTool.drivers) { 4 | if (!driver.id.board && model.boardDefinition) { 5 | if ((driver.id.driver < model.boardDefinition.numDrivers && model.boardDefinition.hasSmartDrivers) || 6 | (driver.id.driver >= model.boardDefinition.numDrivers && model.configTool.expansionBoard !== null && ExpansionBoards[model.configTool.expansionBoard].hasSmartDrivers)) 7 | { 8 | smartDrivers.push(driver); 9 | } 10 | } else { 11 | const board = model.boards.find(board => board.canAddress === driver.id.board); 12 | if (board) { 13 | const boardDefinition = getExpansionBoardDefinition(board); 14 | if (boardDefinition?.hasSmartDrivers) { 15 | smartDrivers.push(driver); 16 | } 17 | } 18 | } 19 | } 20 | 21 | // Configure each driver and possibly SG 22 | let hasSmartDrivers = false, configureExtruderCurrents = false; 23 | for (const driver of smartDrivers) { 24 | const axis = model.move.axes.find(axis => axis.drivers.some(axisDriver => axisDriver.equals(driver.id))); 25 | const extruderIndex = model.move.extruders.findIndex(extruder => (extruder.driver !== null) && extruder.driver.equals(driver.id)); 26 | if (axis || extruderIndex >= 0) { -%> 27 | M569 <%- params({ 28 | P: driver.id, 29 | S: driver.forwards ? 1 : 0, 30 | D: driver.mode, 31 | V: (driver.mode === ConfigDriverMode.stealthChop) ? driver.tpwmThreshold : undefined 32 | }) -%> ; driver <%- driver.id.toString() -%> goes <%- driver.forwards ? "forwards" : "backwards" -%> (<%- axis ? `${axis.letter} axis` : `extruder ${extruderIndex}` -%>) 33 | <% if (driver.sgThreshold !== 0) { -%> 34 | M915 P<%- driver.id -%> S<%- driver.sgThreshold -%> ; set StallGuard threshold 35 | <% } 36 | hasSmartDrivers = true; 37 | configureExtruderCurrents |= extruderIndex >= 0; 38 | } 39 | } 40 | 41 | // Configure motor driver currents if possible 42 | if (hasSmartDrivers) { 43 | const currents = {}; 44 | for (const axis of model.move.axes) { 45 | if (smartDrivers.some(smartDriver => axis.drivers.some(axisDriver => axisDriver.equals(smartDriver.id)))) { 46 | currents[axis.letter] = axis.current; 47 | } 48 | } 49 | if (configureExtruderCurrents) { 50 | const extruderCurrents = []; 51 | for (const extruder of model.move.extruders) { 52 | if (smartDrivers.some(smartDriver => extruder.driver.equals(smartDriver.id))) { 53 | extruderCurrents.push(extruder.current); 54 | } else { 55 | extruderCurrents.push(0); 56 | } 57 | } 58 | currents['E'] = extruderCurrents; 59 | } 60 | 61 | if (preview) { -%> 62 | 63 | ; NOTE: This is later generated in the Axes and/or Extruders section 64 | M906 <%- params(currents) -%> ; set motor driver currents 65 | <% } 66 | } else if (preview) { -%> 67 | ; NOTE: No smart drivers mapped to axes or extruders 68 | <% } -%> 69 | -------------------------------------------------------------------------------- /public/templates/config/endstops.ejs: -------------------------------------------------------------------------------- 1 | <% for (let i = 0; i < model.move.axes.length; i++) { 2 | const axis = model.move.axes[i], endstopConfig = { 3 | [axis.letter]: 0 4 | }; 5 | 6 | if (model.sensors.endstops.length > i && model.sensors.endstops[i] !== null) { 7 | const endstop = model.sensors.endstops[i]; 8 | endstopConfig[axis.letter] = endstop.highEnd ? 2 : 1; 9 | 10 | switch (endstop.type) { 11 | case EndstopType.InputPin: 12 | endstopConfig.P = getCombinedPortString(ConfigPortFunction.endstop, i, false); 13 | endstopConfig.S = 1; 14 | break; 15 | case EndstopType.ZProbeAsEndstop: 16 | endstopConfig.S = 2; 17 | //endstopConfig.K = nnn; // TODO add support for multiple probes 18 | break; 19 | case EndstopType.motorStallAny: 20 | endstopConfig.S = 3; 21 | break; 22 | case EndstopType.motorStallIndividual: 23 | endstopConfig.S = 4; 24 | break; 25 | } 26 | } -%> 27 | M574 <%- params(endstopConfig) %> ; configure <%- axis.letter %> axis endstop 28 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/extruders.ejs: -------------------------------------------------------------------------------- 1 | <% const microstepping = [], microsteppingInterpolated = [], currents = []; 2 | for (let i = 0; i < model.move.extruders.length; i++) { 3 | const extruder = model.move.extruders[i]; 4 | if (extruder.microstepping.interpolated) { 5 | microsteppingInterpolated.push(extruder.microstepping.value); 6 | microstepping.push(-1); 7 | } else { 8 | microstepping.push(extruder.microstepping.value); 9 | microsteppingInterpolated.push(-1); 10 | } 11 | 12 | const board = model.boards.find(board => board.canAddress === extruder.driver.board); 13 | if (board) { 14 | const boardDefinition = (board.canAddress > 0) ? getExpansionBoardDefinition(board) : getBoardDefinition(model); 15 | if (boardDefinition?.hasSmartDrivers) { 16 | currents.push(extruder.current); 17 | } else { 18 | currents.push(0); 19 | } 20 | } else { 21 | currents.push(0); 22 | } 23 | } 24 | 25 | if (model.move.extruders.length > 0) { -%> 26 | M584 E<%- model.move.extruders.map(extruder => extruder.driver.toString()).join(':') %> ; set extruder mapping 27 | <% if (microstepping.some(value => value !== -1)) { -%> 28 | M350 <%- params({ 29 | E: microstepping, 30 | I: 0 31 | }) %> ; configure microstepping without interpolation 32 | <% } 33 | if (microsteppingInterpolated.some(value => value !== -1)) { -%> 34 | M350 <%- params({ 35 | E: microsteppingInterpolated, 36 | I: 1 37 | }) %> ; configure microstepping with interpolation 38 | <% } 39 | if (currents.some(value => value > 0)) { 40 | if (preview) { -%> 41 | ; NOTE: See Smart Drivers section for motor currents 42 | <% } else { -%> 43 | M906 E<%- currents.join(':') %> ; set extruder driver currents 44 | <% } 45 | } -%> 46 | M92 E<%- model.move.extruders.map(extruder => extruder.stepsPerMm).join(':') %> ; configure steps per mm 47 | M566 E<%- model.move.extruders.map(extruder => extruder.jerk * 60).join(':') %> ; set maximum instantaneous speed changes (mm/min) 48 | M203 E<%- model.move.extruders.map(extruder => extruder.speed * 60).join(':') %> ; set maximum speeds (mm/min) 49 | M201 E<%- model.move.extruders.map(extruder => extruder.acceleration).join(':') %> ; set accelerations (mm/s^2) 50 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/fans.ejs: -------------------------------------------------------------------------------- 1 | <% for (let i = 0; i < model.fans.length; i++) { 2 | const fan = model.fans[i], fanPort = getPort(ConfigPortFunction.fan, i); 3 | if (fan !== null && fanPort !== null) { -%> 4 | M950 <%- params({ 5 | F: i, 6 | C: getCombinedPortString([ConfigPortFunction.fan, ConfigPortFunction.fanTacho], i), 7 | Q: (fanPort.frequency != 250) ? fanPort.frequency : undefined 8 | }) %> ; create fan #<%- i %> 9 | M106 <%- params({ 10 | P: i, 11 | C: fan.name ? fan.name : undefined, 12 | S: fan.requestedValue, 13 | L: fan.thermostatic.sensors.length ? undefined : fan.min, 14 | X: fan.thermostatic.sensors.length ? undefined : fan.max, 15 | B: fan.blip, 16 | H: fan.thermostatic.sensors.length ? fan.thermostatic.sensors : undefined, 17 | T: fan.thermostatic.sensors.length ? reduce([fan.thermostatic.lowTemperature, fan.thermostatic.highTemperature]) : undefined 18 | }) %> ; configure fan #<%- i %> 19 | <% } 20 | } -%> -------------------------------------------------------------------------------- /public/templates/config/general.ejs: -------------------------------------------------------------------------------- 1 | G90 ; absolute coordinates 2 | M83 ; relative extruder moves 3 | M550 P<%- escape(model.network.name) -%> ; set hostname 4 | <% if (model.configTool.autoSave.enabled) { -%> 5 | M911 <%- params({ 6 | S: model.configTool.autoSave.saveThreshold, 7 | R: model.configTool.autoSave.resumeThreshold, 8 | P: model.configTool.autoSave.codesToRun 9 | }) -%> ; set voltage thresholds and actions to run on power loss 10 | <% } 11 | 12 | // Preview shows end of config.g 13 | if (preview) { 14 | const miscContent = await render("config/miscellaneous"); 15 | if (miscContent.trim() !== "") { -%> 16 | 17 | ; ... generated at the end of config.g: 18 | 19 | <%- miscContent -%> 20 | <% } 21 | } -%> -------------------------------------------------------------------------------- /public/templates/config/heaters.ejs: -------------------------------------------------------------------------------- 1 | <% for (let i = 0; i < model.heat.heaters.length; i++) { 2 | const heater = model.heat.heaters[i], heaterPort = getPort(ConfigPortFunction.heater, i); 3 | if (heater !== null && heaterPort !== null) { -%> 4 | M950 <%- params({ 5 | H: i, 6 | C: getPortString(ConfigPortFunction.heater, i), // in case it is mapped to multiple ports... 7 | Q: (heaterPort.frequency != 250) ? heaterPort.frequency : undefined, 8 | T: heater.sensor 9 | }) %> ; create heater #<%- i %> 10 | <% for (let k = 0; k < heater.monitors.length; k++) { 11 | if (heater.monitors[k].action !== null) { 12 | let condition = 0; 13 | switch (heater.monitors[k].condition) { 14 | case HeaterMonitorCondition.disabled: 15 | condition = -1; 16 | break; 17 | case HeaterMonitorCondition.tooHigh: 18 | condition = 0; 19 | break; 20 | case HeaterMonitorCondition.tooLow: 21 | condition = 1; 22 | break; 23 | } -%> 24 | M143 <%- params({ 25 | H: i, 26 | P: k, 27 | T: heater.monitors[k].sensor, 28 | C: condition, 29 | S: heater.monitors[k].limit, 30 | A: heater.monitors[k].action, 31 | }) %> ; configure heater monitor #<%- k %> for heater #<%- i %> 32 | <% } 33 | } -%> 34 | M307 <%- params({ 35 | H: i, 36 | R: heater.model.heatingRate, 37 | D: heater.model.deadTime, 38 | E: heater.model.coolingExp, 39 | K: reduce([heater.model.coolingRate, heater.model.fanCoolingRate]), 40 | B: heater.model.pid.used ? 0 : 1, 41 | I: heater.model.inverted ? 1 : undefined, 42 | S: (heater.model.maxPwm !== 1) ? heater.model.maxPwm : undefined, 43 | V: (heater.model.standardVoltage > 0) ? heater.model.standardVoltage : undefined 44 | }) %> ; configure model of heater #<%- i %> 45 | <% if (heater.model.pid.overridden) { -%> 46 | M301 <%- params({ 47 | H: i, 48 | P: heater.model.pid.p, 49 | I: heater.model.pid.i, 50 | D: heater.model.pid.d 51 | }) %> ; apply custom PID parameters for heater #<%- i %> 52 | <% } 53 | } 54 | } 55 | if (model.heat.bedHeaters.some(bedHeater => bedHeater >= 0)) { -%> 56 | 57 | ; Heated beds 58 | <% for (let i = 0; i < model.heat.bedHeaters.length; i++) { 59 | if (model.heat.bedHeaters[i] >= 0) { -%> 60 | M140 P<%- i %> H<%- model.heat.bedHeaters[i] %> ; configure heated bed #<%- i %> 61 | <% } 62 | } 63 | } 64 | if (model.heat.chamberHeaters.some(chamberHeater => chamberHeater >= 0)) { -%> 65 | 66 | ; Heated chambers 67 | <% for (let i = 0; i < model.heat.chamberHeaters.length; i++) { 68 | if (model.heat.chamberHeaters[i] >= 0) { -%> 69 | M141 P<%- i %> H<%- model.heat.chamberHeaters[i] %> ; configure heated chamber #<%- i %> 70 | <% } 71 | } 72 | } -%> -------------------------------------------------------------------------------- /public/templates/config/index.ejs: -------------------------------------------------------------------------------- 1 | ; Configuration file for RepRapFirmware on <%- model.boardType %> 2 | ; executed by the firmware on start-up 3 | ; 4 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 5 | 6 | <%- sections({ 7 | "General": await render("config/general"), 8 | "Accessories": await render("config/accessories"), 9 | "Network": await render("config/network") 10 | }) %> 11 | <% if (model.boards.some(board => board.canAddress)) { -%> 12 | 13 | ; Wait a moment for the CAN expansion boards to become available 14 | G4 S2 15 | <% } -%> 16 | 17 | <%- sections({ 18 | "LED Strips": await render("config/ledStrips"), 19 | "Accelerometers": await render("config/accelerometers"), 20 | "!Drivers": await render("config/drivers/index"), 21 | "Axes": await render("config/axes"), 22 | "Extruders": await render("config/extruders"), 23 | "Kinematics": await render("config/kinematics"), 24 | "Probes": await render("config/probes"), 25 | "Endstops": await render("config/endstops"), 26 | "!Compensation": await render("config/compensation/index"), 27 | "Sensors": await render("config/sensors"), 28 | "Heaters": await render("config/heaters"), 29 | "Spindles": await render("config/spindles"), 30 | "Lasers": await render("config/lasers"), 31 | "Fans": await render("config/fans"), 32 | "Tools": await render("config/tools"), 33 | "Miscellaneous": await render("config/miscellaneous") 34 | }) %> 35 | -------------------------------------------------------------------------------- /public/templates/config/kinematics.ejs: -------------------------------------------------------------------------------- 1 | <% if (model.move.kinematics instanceof CoreKinematics) { 2 | let kinematicsName, kParam; 3 | switch (model.move.kinematics.name) { 4 | case KinematicsName.cartesian: 5 | kinematicsName = "Cartesian"; 6 | kParam = 0; 7 | break; 8 | case KinematicsName.coreXY: 9 | kinematicsName = "CoreXY"; 10 | kParam = 1; 11 | break; 12 | case KinematicsName.coreXZ: 13 | kinematicsName = "CoreXZ"; 14 | kParam = 2; 15 | break; 16 | case KinematicsName.coreXYU: 17 | kinematicsName = "CoreXYU"; 18 | kParam = 5; 19 | break; 20 | case KinematicsName.coreXYUV: 21 | kinematicsName = "CoreXYUV"; 22 | kParam = 8; 23 | break; 24 | case KinematicsName.markForged: 25 | kinematicsName = "MarkForged"; 26 | kParam = 11; 27 | break; 28 | default: 29 | throw new Error(`Unsupported core kinematics: ${model.move.kinematics.name}`); 30 | } 31 | 32 | let moveCoefficients = ""; 33 | if (!isDefaultCoreKinematics(model.move.kinematics)) { 34 | const letters = "XYZUVAB"; 35 | for (let i = 0; i < model.move.kinematics.forwardMatrix.length; i++) { 36 | moveCoefficients += ` ${letters[i]}${model.move.kinematics.forwardMatrix[i].join(':')}`; 37 | } 38 | } -%> 39 | M669 K<%- kParam %><%- moveCoefficients %> ; configure <%- kinematicsName %> kinematics 40 | <% } else if (model.isDelta) { -%> 41 | M665 <%- params({ 42 | L: reduce(model.move.kinematics.towers.map(tower => tower.diagonal)), 43 | R: model.move.kinematics.deltaRadius, 44 | B: model.move.kinematics.printRadius, 45 | H: model.move.kinematics.homedHeight 46 | }) %> ; set delta radius, diagonal rod length, printable radius and homed height 47 | M208 Z<%- model.move.axes.find(axis => axis.letter === 'Z').min %> S1 ; set minimum Z 48 | M666 <%- params({ 49 | X: model.move.kinematics.towers[0].endstopAdjustment, 50 | Y: model.move.kinematics.towers[1].endstopAdjustment, 51 | Z: model.move.kinematics.towers[2].endstopAdjustment, 52 | A: model.move.kinematics.xTilt, 53 | B: model.move.kinematics.yTilt 54 | }) %> ; endstop adjustments and XY tilt, can be determined using auto calibration as well 55 | <% } else if (model.move.kinematics.name === KinematicsName.hangprinter) { -%> 56 | M669 <%- params({ 57 | K: 6, 58 | A: model.move.kinematics.anchors[0], 59 | B: model.move.kinematics.anchors[1], 60 | C: model.move.kinematics.anchors[2], 61 | D: model.move.kinematics.anchors[3], 62 | P: model.move.kinematics.printRadius 63 | }) %> ; configure hangprinter kinematics 64 | M208 S0 Z<%- model.move.axes.find(axis => axis.letter === 'Z').max %> ; set maximum height 65 | M208 S1 Z<%- model.move.axes.find(axis => axis.letter === 'Z').min %> ; set minimum height 66 | <% } else { -%> 67 | ; Unsupported kinematics: <%- model.move.kinematics.name %> 68 | <% } -%> 69 | -------------------------------------------------------------------------------- /public/templates/config/lasers.ejs: -------------------------------------------------------------------------------- 1 | <% const laserPort = getPort(ConfigPortFunction.laser, 0); 2 | if (laserPort !== null) { -%> 3 | M452 <%- params({ 4 | C: laserPort.toString(), 5 | F: laserPort.frequency, 6 | R: model.configTool.laser.maxIntensity, 7 | S: model.configTool.laser.sParamSticky ? 1 : 0 8 | }) %> ; configure Laser port 9 | <% } -%> 10 | -------------------------------------------------------------------------------- /public/templates/config/ledStrips.ejs: -------------------------------------------------------------------------------- 1 | <% for (let i = 0; i < model.ledStrips.length; i++) { 2 | const strip = model.ledStrips[i]; 3 | let type = undefined; 4 | switch (strip.type) { 5 | case LedStripType.DotStar: 6 | type = 0; 7 | break; 8 | case LedStripType.NeoPixel_RGB: 9 | type = 1; 10 | break; 11 | case LedStripType.NeoPixel_RGBW: 12 | type = 2; 13 | break; 14 | } 15 | -%> 16 | M950 <%- params({ 17 | E: i, 18 | C: getPortString(ConfigPortFunction.ledStrip, i), 19 | T: type 20 | }) %> ; configure LED strip #<%- i %> 21 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/miscellaneous.ejs: -------------------------------------------------------------------------------- 1 | <% if (model.configTool.configOverride) { -%> 2 | M501 ; load saved parameters from non-volatile memory 3 | <% } 4 | 5 | // FIXME The following second conition can be removed when lasers are defined using M950 6 | if (model.state.machineMode !== MachineMode.fff || model.configTool.capabilities.laser) { 7 | if (model.state.machineMode === MachineMode.fff) { -%> 8 | M451 ; select FFF mode 9 | <% } else if (model.state.machineMode === MachineMode.cnc) { -%> 10 | M453 ; select CNC mode 11 | <% } else if (model.state.machineMode === MachineMode.laser) { -%> 12 | M452 ; select Laser mode 13 | <% } 14 | } 15 | 16 | if (model.configTool.autoSelectFirstTool && model.tools.some(tool => tool !== null)) { -%> 17 | T<%- model.tools.findIndex(tool => tool !== null) %> ; select first tool 18 | <% } 19 | 20 | if (model.configTool.customSettings.trim() !== "") { -%> 21 | 22 | ; Custom settings 23 | <%- model.configTool.customSettings %> 24 | <% } -%> -------------------------------------------------------------------------------- /public/templates/config/network.ejs: -------------------------------------------------------------------------------- 1 | <% if (model.configTool.password !== "") { -%> 2 | M551 P<%- escape(model.configTool.password) %> ; set <%- (model.sbc === null) ? "machine" : "UI" %> password 3 | <% } 4 | 5 | if (model.sbc === null) { 6 | for (let i = 0; i < model.network.interfaces.length; i++) { 7 | const iface = model.network.interfaces[i]; 8 | if (model.network.interfaces.length === 1) { 9 | i = undefined; 10 | } 11 | 12 | if (iface.state !== NetworkInterfaceState.disabled) { 13 | // Configure network interface in standalone mode 14 | // TODO: Add support for MAC address 15 | if (iface.type === NetworkInterfaceType.lan) { -%> 16 | M552 <%- params({ 17 | I: i, 18 | P: [iface.configuredIP ?? "0.0.0.0"], 19 | S: 1 20 | }) %> ; configure Ethernet adapter 21 | <% if (iface.configuredIP && iface.configuredIP !== "0.0.0.0") { -%> 22 | M553 P<%- iface.subnet %> ; set netmask 23 | M554 P<%- iface.gateway %> ; set gateway 24 | <% } 25 | } else if (iface.type === NetworkInterfaceType.wifi) { -%> 26 | M552 <%- params({ 27 | I: i, 28 | S: 1 29 | }) %> ; configure WiFi adapter 30 | <% } 31 | 32 | // Configure network protocols 33 | for (const protocol of iface.activeProtocols) { 34 | let protocolNumber = undefined, protocolName = undefined; 35 | switch (protocol) { 36 | case NetworkProtocol.HTTP: 37 | protocolNumber = 0; 38 | protocolName = "HTTP"; 39 | break; 40 | case NetworkProtocol.FTP: 41 | protocolNumber = 1; 42 | protocolName = "FTP"; 43 | break; 44 | case NetworkProtocol.Telnet: 45 | protocolNumber = 2; 46 | protocolName = "Telnet"; 47 | break; 48 | } 49 | 50 | if (protocolNumber !== undefined) { -%> 51 | M586 <%- params({ 52 | I: i, 53 | P: protocolNumber, 54 | S: 1 55 | }) %> ; configure <%- protocolName %> 56 | <% } 57 | } 58 | } 59 | 60 | if (i === undefined) { 61 | break; 62 | } 63 | } 64 | } -%> 65 | -------------------------------------------------------------------------------- /public/templates/config/probes.ejs: -------------------------------------------------------------------------------- 1 | <% for (let probeIndex = 0; probeIndex < model.sensors.probes.length; probeIndex++) { 2 | const probe = model.sensors.probes[probeIndex]; 3 | if (probe !== null) { 4 | const probeConfig = { 5 | K: probeIndex, 6 | P: probe.type, 7 | C: getCombinedPortString([ConfigPortFunction.probeIn, ConfigPortFunction.probeMod], probeIndex), 8 | H: probe.diveHeight, 9 | F: reduce(probe.speeds.map(speed => speed * 60)), 10 | T: probe.travelSpeed, 11 | R: (probe.recoveryTime !== 0) ? probe.recoveryTime : undefined, 12 | A: (probe.maxProbeCount !== 1) ? probe.maxProbeCount : undefined, 13 | S: (probe.tolerance !== 0.03) ? probe.tolerance : undefined 14 | }; 15 | 16 | // Get probe details 17 | let type = "unknown"; 18 | switch (probe.type) { 19 | case ProbeType.none: 20 | type = "manual"; 21 | break; 22 | case ProbeType.analog: 23 | type = "analog"; 24 | break; 25 | case ProbeType.dumbModulated: 26 | type = "modulated"; 27 | break; 28 | case ProbeType.alternateAnalog: 29 | type = "alternate analog"; 30 | break; 31 | case ProbeType.endstopSwitch_obsolete: 32 | type = "endstop (obsolete)"; 33 | break; 34 | case ProbeType.digital: 35 | type = "digital"; 36 | break; 37 | case ProbeType.e1Switch_obsolete: 38 | type = "E1 endstop (obsolete)"; 39 | break; 40 | case ProbeType.zSwitch_obsolete: 41 | type = "Z endstop (obsolete)"; 42 | break; 43 | case ProbeType.unfilteredDigital: 44 | type = "unfiltered digital"; 45 | break; 46 | case ProbeType.blTouch: 47 | type = "BLTouch"; 48 | // BLTouch requires a pull-up resistor, which may not be a permanent one on certain boards 49 | const inputPort = getPort(ConfigPortFunction.probeIn, probeIndex); 50 | probeConfig.C = (inputPort.pullUp || model.getBoardDefinition(inputPort.canBoard)?.hasInputPullUps) ? inputPort.toString() : `^${inputPort.toString()}`; 51 | break; 52 | case ProbeType.zMotorStall: 53 | type = "Z motor stall"; 54 | break; 55 | case ProbeType.scanningAnalog: 56 | type = "scanning"; 57 | break; 58 | } -%> 59 | M558 <%- params(probeConfig) %> ; configure <%- type %> probe via slot #<%- probeIndex %> 60 | <% if (model.isDelta && model.configTool.delta.lowDiveHeight) { -%> 61 | M558 H30 ;*** Remove this line after delta calibration has been done and new delta parameters have been saved 62 | <% } -%> 63 | G31 <%- params({ 64 | P: probe.threshold, 65 | X: probe.offsets[0], 66 | Y: probe.offsets[1], 67 | Z: probe.triggerHeight 68 | }) %> ; set Z probe trigger value, offset and trigger height 69 | <% } 70 | if (probe.type === ProbeType.blTouch) { 71 | const servoIndex = getProbeServoIndex(probeIndex); -%> 72 | M950 <%- params({ 73 | S: servoIndex, 74 | C: getPortString(ConfigPortFunction.probeServo, probeIndex) 75 | }) -%> ; create servo #<%- servoIndex %> for BLtouch 76 | <% } 77 | } -%> -------------------------------------------------------------------------------- /public/templates/config/sensors.ejs: -------------------------------------------------------------------------------- 1 | <% function sensorSupportsADCAutoCalibration(index) { 2 | const port = model.configTool.ports.find(port => port.function === ConfigPortFunction.thermistor && port.index === index); 3 | if (port) { 4 | const boardDefinition = model.getBoardDefinition(port.canBoard); 5 | return (boardDefinition !== null) && boardDefinition.hasADCAutoCalibration; 6 | } 7 | return false; 8 | } 9 | 10 | for (let i = 0; i < model.sensors.analog.length; i++) { 11 | const sensor = model.sensors.analog[i], configSensor = model.configTool.sensors[i]; 12 | if (sensor !== null) { 13 | let port = undefined, sensorOptions = {}; 14 | if (sensor.type === AnalogSensorType.thermistor) { 15 | port = getPortString(ConfigPortFunction.thermistor, i); 16 | sensorOptions = { 17 | T: configSensor.r25, 18 | B: configSensor.beta, 19 | C: (configSensor.shC !== 0) ? configSensor.shC : undefined, 20 | //R: (configSensor.seriesR !== null) ? configSensor.seriesR : undefined, 21 | L: (!sensorSupportsADCAutoCalibration(i) && configSensor.adcLowOffset !== null) ? configSensor.adcLowOffset : undefined, 22 | H: (!sensorSupportsADCAutoCalibration(i) && configSensor.adcHighOffset !== null) ? configSensor.adcHighOffset : undefined 23 | }; 24 | } else if (sensor.type === AnalogSensorType.pt1000) { 25 | port = getPortString(ConfigPortFunction.thermistor, i); 26 | sensorOptions = { 27 | //R: (configSensor.seriesR !== null) ? configSensor.seriesR : undefined, 28 | L: (!sensorSupportsADCAutoCalibration(i) && configSensor.adcLowOffset !== null) ? configSensor.adcLowOffset : undefined, 29 | H: (!sensorSupportsADCAutoCalibration(i) && configSensor.adcHighOffset !== null) ? configSensor.adcHighOffset : undefined 30 | }; 31 | } else if (sensor.type === AnalogSensorType.max31856) { 32 | port = getPortString(ConfigPortFunction.spiCs, i); 33 | sensorOptions = { 34 | K: configSensor.thermocoupleType, 35 | F: configSensor.mainsFrequency 36 | }; 37 | } else if (sensor.type === AnalogSensorType.max31865) { 38 | port = getPortString(ConfigPortFunction.spiCs, i); 39 | sensorOptions = { 40 | R: (configSensor.rref !== null) ? configSensor.rref : undefined, 41 | W: configSensor.numWires, 42 | F: configSensor.mainsFrequency 43 | }; 44 | } else if (sensor.type === AnalogSensorType.linearAnalog) { 45 | port = getPortString(ConfigPortFunction.thermistor, i); 46 | sensorOptions = { 47 | F: configSensor.filtered ? 1 : 0, 48 | B: configSensor.minTemp, 49 | C: configSensor.maxTemp 50 | }; 51 | } else if ([AnalogSensorType.dht21, AnalogSensorType.dht22, AnalogSensorType.bme280].includes(sensor.type)) { 52 | port = getPortString(ConfigPortFunction.spiCs, i); 53 | } else if ([AnalogSensorType.dhtHumidity, AnalogSensorType.bme280pressure].includes(sensor.type)) { 54 | port = `${configSensor.baseSensor}.1`; 55 | } else if (sensor.type === AnalogSensorType.bme280humidity) { 56 | port = `${configSensor.baseSensor}.2`; 57 | } -%> 58 | M308 <%- params({ 59 | S: i, 60 | P: port, 61 | Y: sensor.type, 62 | A: (sensor.name.trim() !== "") ? sensor.name : undefined, 63 | ...sensorOptions 64 | }) %> ; configure sensor #<%- i %> 65 | <% } 66 | } -%> -------------------------------------------------------------------------------- /public/templates/config/spindles.ejs: -------------------------------------------------------------------------------- 1 | <% for (let i = 0; i < model.spindles.length; i++) { 2 | const spindle = model.spindles[i], spindlePwmPort = getPort(ConfigPortFunction.spindlePwm, i); 3 | if (spindle !== null && spindlePwmPort !== null) { -%> 4 | M950 <%- params({ 5 | C: getCombinedPortString([ConfigPortFunction.spindlePwm, ConfigPortFunction.spindleForwards, ConfigPortFunction.spindleBackwards], i), 6 | Q: spindlePwmPort.frequency, 7 | L: [spindle.min, spindle.max] 8 | }) %> ; configure spindle #<%- i %> 9 | <% } 10 | } -%> 11 | -------------------------------------------------------------------------------- /public/templates/config/tools.ejs: -------------------------------------------------------------------------------- 1 | <% for (let i = 0; i < model.tools.length; i++) { 2 | const tool = model.tools[i]; 3 | if (tool !== null) { -%> 4 | M563 <%- params({ 5 | P: i, 6 | S: tool.name ? tool.name : undefined, 7 | D: tool.extruders, 8 | H: tool.heaters, 9 | F: tool.fans, 10 | X: (tool.axes.length > 0 && tool.axes[0].length > 0 && tool.axes[0][0] !== model.move.axes.findIndex(axis => axis.letter === 'X')) ? tool.axes[0] : undefined, 11 | Y: (tool.axes.length > 1 && tool.axes[0].length > 0 && tool.axes[1][0] !== model.move.axes.findIndex(axis => axis.letter === 'Y')) ? tool.axes[1] : undefined, 12 | L: (tool.extruders.length !== 1 && tool.extruders[0] !== tool.filamentExtruder && tool.filamentExtruder >= 0) ? tool.filamentExtruder : undefined, 13 | R: (tool.spindle >= 0) ? tool.spindle : undefined 14 | }) %> ; create tool #<%- i %> 15 | <% const offsets = {}; 16 | for (let k = 0; k < Math.min(tool.offsets.length, model.move.axes.length); k++) { 17 | const axisLetter = model.move.axes[k].letter; 18 | offsets[axisLetter] = tool.offsets[k]; 19 | } 20 | if (Object.keys(offsets).length > 0) { -%> 21 | M568 <%- params({ 22 | P: i, 23 | ...offsets 24 | }) -%> ; set custom offsets for tool #<%- i %> 25 | <% } 26 | 27 | if (tool.heaters.length > 0) { -%> 28 | M568 P<%- tool.number %> R0 S0 ; set initial tool #<%- i %> active and standby temperatures to 0C 29 | <% } 30 | 31 | if (tool.extruders.length > 1) { -%> 32 | M567 <%- params({ 33 | P: i, 34 | E: tool.mix 35 | }) %> ; set mixing ratios for tool #<%- i %> 36 | <% } 37 | } 38 | } -%> -------------------------------------------------------------------------------- /public/templates/deployprobe.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; <%- filename %> 3 | ; called to deploy a physical Z probe 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } 8 | 9 | if (probe.type == ProbeType.blTouch) { -%> 10 | M280 P<%- getProbeServoIndex(probeIndex) %> S10 ; deploy BLTouch 11 | <% } else { -%> 12 | ; insert codes for deploying the Z probe here 13 | <% } -%> 14 | -------------------------------------------------------------------------------- /public/templates/homeall/hangprinter.ejs: -------------------------------------------------------------------------------- 1 | ; hangprinters do not have homing files. see https://docs.duet3d.com/User_manual/Machine_configuration/Configuration_Hangprinter -------------------------------------------------------------------------------- /public/templates/homeall/index.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; homeall.g 3 | ; called to home all axes 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | <% } -%> 7 | 8 | <% // home standard axes 9 | if (model.move.kinematics.name === KinematicsName.cartesian) { -%> 10 | <%- await render("homeall/cartesian") %> 11 | <% } else if (model.move.kinematics.name === KinematicsName.coreXZ) { -%> 12 | <%- await render("homeall/corexz") %> 13 | <% } else if (model.move.kinematics instanceof CoreKinematics) { -%> 14 | <%- await render("homeall/corexy") %> 15 | <% } else if (model.move.kinematics.name === KinematicsName.hangprinter) { -%> 16 | <%- await render("homeall/hangprinter") %> 17 | <% } else if (model.move.kinematics.name === KinematicsName.polar) { -%> 18 | <%- await render("homeall/polar") %> 19 | <% } else if (model.move.kinematics instanceof ScaraKinematics) { -%> 20 | <%- await render("homeall/scara") %> 21 | <% } else { -%> 22 | ; unsupported kinematics <%- model.move.kinematics.name -%> 23 | <% } -%> 24 | 25 | <% // home additional axes 26 | for (const axis of model.move.axes) { 27 | if (!['X', 'Y', 'Z'].includes(axis.letter) && model.canHomeIndividualAxis(axis.letter)) { -%> 28 | M98 P"home<%- /[a-z]/.test(axis.letter) ? `'${axis.letter}` : axis.letter %>.g" ; home <%- axis.letter %> axis 29 | <% } 30 | } -%> 31 | -------------------------------------------------------------------------------- /public/templates/homeall/polar.ejs: -------------------------------------------------------------------------------- 1 | ; not supported yet, missing OM fields. see https://docs.duet3d.com/User_manual/Machine_configuration/Configuration_Polar -------------------------------------------------------------------------------- /public/templates/homeall/scara.ejs: -------------------------------------------------------------------------------- 1 | ; not supported yet, missing OM properties. see https://docs.duet3d.com/User_manual/Machine_configuration/Configuration_SCARA#homing-files 2 | ; for five-bar scara see https://docs.duet3d.com/User_manual/Machine_configuration/Configuration_five_bar_parallel_scara -------------------------------------------------------------------------------- /public/templates/homeaxis.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; <%- filename %> 3 | ; called to home the <%- axisLetter %> axis 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } 8 | 9 | // Get Z-probe values 10 | const zProbe = model.sensors.probes.find(probe => probe !== null), zProbeIndex = model.sensors.probes.indexOf(zProbe); 11 | const diveHeight = zProbe ? zProbe.diveHeight : 5; 12 | const travelSpeed = zProbe ? zProbe.travelSpeed : 6000; 13 | 14 | // Get endstop 15 | const endstop = (axisIndex >= 0 && axisIndex < model.sensors.endstops.length) ? model.sensors.endstops[axisIndex] : null; 16 | const firstDriver = (axis.drivers.length > 0) ? model.configTool.drivers.find(driver => driver.id.equals(axis.drivers[0])) : null; 17 | const fastHomingSpeed = (firstDriver && firstDriver.homingSpeeds.length > 0) ? firstDriver.homingSpeeds[0] * 60 : 600; 18 | const slowHomingSpeed = (firstDriver && firstDriver.homingSpeeds.length > 1) ? firstDriver.homingSpeeds[1] * 60 : 300; 19 | const zLiftCanHitEndstop = model.sensors.endstops.some(endstop => (endstop !== null) && (endstop.letter === 'Z') && endstop.highEnd); 20 | 21 | // Check how the axis is supposed to be homed 22 | if (endstop) { 23 | // Increase Z if not homing Z or if homing using a probe 24 | if (axisLetter !== 'Z' || endstop.type === EndstopType.ZProbeAsEndstop) { -%> 25 | ; increase Z 26 | G91 ; relative positioning 27 | G1 <%- params({ 28 | H: zLiftCanHitEndstop ? 1 : 2, 29 | Z: diveHeight 30 | }) %> ; move Z relative to current position to avoid dragging nozzle over the bed 31 | G90 ; absolute positioning 32 | 33 | <% } -%> 34 | ; home <%- axisLetter %> 35 | <% if (axisLetter === 'Z' && endstop.type === EndstopType.ZProbeAsEndstop) { 36 | // Home Z using Z-probe 37 | if (model.move.compensation.probeGrid !== null && model.move.compensation.probeGrid.axes[0] === 'X' && model.move.compensation.probeGrid.axes[1] === 'Y') { 38 | if (preview) { -%> 39 | ; NOTE: The following XY position is determined from the probe grid defined in the next section 40 | <% } -%> 41 | var xCenter = move.compensation.probeGrid.mins[0] + (move.compensation.probeGrid.maxs[0] - move.compensation.probeGrid.mins[0]) / 2 - sensors.probes[<%- zProbeIndex %>].offsets[0] 42 | var yCenter = move.compensation.probeGrid.mins[1] + (move.compensation.probeGrid.maxs[1] - move.compensation.probeGrid.mins[1]) / 2 - sensors.probes[<%- zProbeIndex %>].offsets[1] 43 | <% } else { -%> 44 | var xCenter = model.move.axes[0].min + (model.move.axes[0].max - model.move.axes[0].min) / 2 - sensors.probes[<%- zProbeIndex %>].offsets[0] 45 | var yCenter = model.move.axes[1].min + (model.move.axes[1].max - model.move.axes[1].min) / 2 - sensors.probes[<%- zProbeIndex %>].offsets[1] 46 | <% } -%> 47 | G1 <%- params({ 48 | X: "{var.xCenter}", 49 | Y: "{var.yCenter}", 50 | F: travelSpeed 51 | }) %> ; go to bed centre 52 | G30 ; probe the bed 53 | <% } else { 54 | // Home cartesian axis 55 | -%> 56 | G91 ; relative positioning 57 | var maxTravel = move.axes[<%- axisIndex %>].max - move.axes[<%- axisIndex %>].min + 5 ; calculate how far <%- axisLetter %> can travel plus 5mm 58 | G1 <%- params({ 59 | H: 1, 60 | [axisLetter]: endstop.highEnd ? "{var.maxTravel}" : "{-var.maxTravel}", 61 | F: fastHomingSpeed 62 | }) %> ; <%- (fastHomingSpeed !== slowHomingSpeed) ? "coarse home" : "home" %> in the <%- (endstop.highEnd ? "+" : "-") + axisLetter %> direction 63 | <% if (fastHomingSpeed !== slowHomingSpeed) { 64 | // second pass 65 | -%> 66 | G1 <%- params({ 67 | [axisLetter]: endstop.highEnd ? -5 : 5, 68 | F: travelSpeed 69 | }) %> ; move back 5mm 70 | G1 <%- params({ 71 | H: 1, 72 | [axisLetter]: endstop.highEnd ? "{var.maxTravel}" : "{-var.maxTravel}", 73 | F: slowHomingSpeed 74 | }) %> ; fine home in the <%- (endstop.highEnd ? "+" : "-") + axisLetter %> direction 75 | G90 ; absolute positioning 76 | <% } 77 | 78 | // Decrease Z again only if Z isn't being homed 79 | if (axisLetter !== 'Z') { -%> 80 | 81 | ; decrease Z again 82 | G91 ; relative positioning 83 | G1 <%- params({ 84 | H: 2, 85 | Z: -diveHeight, 86 | F: travelSpeed 87 | }) %> ; move Z relative to current position 88 | G90 ; absolute positioning 89 | <% } 90 | } 91 | } else { 92 | -%> 93 | G92 <%- params({ [axisLetter]: 0 }) %> ; set <%- axisLetter %>=0 94 | M118 L1 P0 S"Warning: <%- axisLetter %> is NOT homed but its position has been set to 0." 95 | <% } -%> 96 | -------------------------------------------------------------------------------- /public/templates/homedelta.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; homedelta.g 3 | ; called to home all towers and extra axes 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } 8 | 9 | if (model.move.kinematics.name === KinematicsName.rotaryDelta) { -%> 10 | ; rotary delta is not supported yet, see https://docs.duet3d.com/User_manual/Machine_configuration/Configuration_rotary_delta 11 | <% } else { 12 | -%> 13 | ; home linear delta 14 | G91 ; relative positioning 15 | var homedHeight = <%- model.move.kinematics.homedHeight + 5 %> ; set homed height plus 5mm 16 | <% let canHome = true; 17 | for (let i = 0; i < 3; i++) { 18 | if (model.sensors.endstops[i] === null) { 19 | canHome = false; 20 | break; 21 | } 22 | } 23 | 24 | if (canHome) { 25 | const highEndstops = model.sensors.endstops[0].highEnd; 26 | let homingSpeeds = [10, 5]; 27 | if (model.move.axes[0].drivers.length > 0) { 28 | const firstDriver = model.configTool.drivers.find(driver => driver.id.equals(model.move.axes[0].drivers[0])); 29 | if (firstDriver) { 30 | homingSpeeds = firstDriver.homingSpeeds; 31 | } 32 | } 33 | const fastHomingSpeed = model.configTool.delta.slowHoming ? homingSpeeds[0] * 0.1 : homingSpeeds[0]; 34 | const slowHomingSpeed = model.configTool.delta.slowHoming ? Math.min(fastHomingSpeed, homingSpeeds[1]) : homingSpeeds[1]; 35 | const zProbe = model.sensors.probes.find(probe => probe !== null), travelSpeed = zProbe ? zProbe.travelSpeed : 6000; 36 | 37 | if (model.configTool.delta.slowHoming) { -%> 38 | ;*** Slow homing has been configured. Change F<%- fastHomingSpeed * 60 %> to F<%- homingSpeeds[0] * 60 %> below when your configuration is working 39 | <% } -%> 40 | G1 <%- params({ 41 | H: 1, 42 | X: highEndstops ? "{var.homedHeight}" : "{-var.homedHeight}", 43 | Y: highEndstops ? "{var.homedHeight}" : "{-var.homedHeight}", 44 | Z: highEndstops ? "{var.homedHeight}" : "{-var.homedHeight}", 45 | F: fastHomingSpeed * 60 46 | }) %> ; move all towers to the <%- highEndstops ? "high" : "low" %> end stopping at the endstops 47 | <% if (fastHomingSpeed !== slowHomingSpeed) { -%> 48 | G1 <%- params({ 49 | H: 2, 50 | X: highEndstops ? -10 : 10, 51 | Y: highEndstops ? -10 : 10, 52 | Z: highEndstops ? -10 : 10, 53 | F: travelSpeed 54 | }) %> ; go <%- highEndstops ? "down" : "up" %> a few mm 55 | G1 <%- params({ 56 | H: 1, 57 | X: highEndstops ? "{var.homedHeight}" : "{-var.homedHeight}", 58 | Y: highEndstops ? "{var.homedHeight}" : "{-var.homedHeight}", 59 | Z: highEndstops ? "{var.homedHeight}" : "{-var.homedHeight}", 60 | F: fastHomingSpeed * 60 61 | }) %> ; move all towers to the <%- highEndstops ? "high" : "low" %> end once more stopping at the endstops 62 | G90 ; absolute positioning 63 | G1 X0 Y0 F<%- travelSpeed %> ; move X and Y to the centre 64 | 65 | <% } 66 | } else { -%> 67 | G92 X0 Y0 Z0 ; set X=0 Y=0 Z=0 68 | M118 L1 P0 S"Warning: XYZ are NOT homed but their positions have been set to 0." 69 | <% } 70 | } 71 | 72 | // home additional axes 73 | for (const axis of model.move.axes) { 74 | if (!['X', 'Y', 'Z'].includes(axis.letter) && model.canHomeIndividualAxis(axis.letter)) { -%> 75 | M98 P"home<%- /[a-z]/.test(axis.letter) ? `'${axis.letter}` : axis.letter %>.g" ; home <%- axis.letter %> axis 76 | <% } 77 | } -%> 78 | -------------------------------------------------------------------------------- /public/templates/pause.ejs: -------------------------------------------------------------------------------- 1 | ; pause.g 2 | ; called to retract a physical Z probe 3 | ; 4 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 5 | M83 ; relative extruder moves 6 | <% if (model.configTool.capabilities.fff) { -%> 7 | G1 E-10 F3600 ; retract 10mm of filament 8 | <% } -%> 9 | G91 ; relative positioning 10 | G1 Z5 F360 ; lift Z by 5mm 11 | G90 ; absolute positioning 12 | G1 X0 Y0 F6000 ; go to X=0 Y=0 13 | -------------------------------------------------------------------------------- /public/templates/resume.ejs: -------------------------------------------------------------------------------- 1 | ; resume.g 2 | ; called to retract a physical Z probe 3 | ; 4 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 5 | G1 R1 X0 Y0 Z5 F6000 ; go to 5mm above position of the last move 6 | G1 R1 X0 Y0 Z0 ; go back to the last print move 7 | M83 ; relative extruder moves 8 | <% if (model.configTool.capabilities.fff) { -%> 9 | G1 E10 F3600 ; extrude 10mm of filament 10 | <% } -%> 11 | -------------------------------------------------------------------------------- /public/templates/retractprobe.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; <%- filename %> 3 | ; called to retract a physical Z probe 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } 8 | 9 | if (probe.type == ProbeType.blTouch) { -%> 10 | M280 P<%- getProbeServoIndex(probeIndex) %> S90 ; retract BLTouch 11 | <% } else { -%> 12 | ; insert codes for deploying the Z probe here 13 | <% } -%> -------------------------------------------------------------------------------- /public/templates/runonce.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; runonce.g 3 | ; called after config.g. As soon as this file has finished, it is automatically deleted! 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } 8 | 9 | // Show message box on first run in SBC mode 10 | if (model.sbc !== null) { -%> 11 | M291 P"Applying persistent configuration options" R"Please wait" S1 T60 ; show that persistent settings are being configured 12 | while exists(sbc) && plugins.DuetPiManagementPlugin.pid < 0 && iterations < 30 13 | G4 S2 ; wait for DuetPiManagementPlugin to become available 14 | G4 S2 ; wait another moment 15 | M550 P<%- escape(model.network.name) -%> ; set persistent hostname 16 | <% } 17 | 18 | // Configure network interfaces 19 | for (let i = 0; i < model.network.interfaces.length; i++) { 20 | const iface = model.network.interfaces[i]; 21 | if (model.network.interfaces.length === 1) { 22 | i = undefined; 23 | } 24 | 25 | if (iface.state !== NetworkInterfaceState.disabled) { 26 | if (iface.type === NetworkInterfaceType.lan) { 27 | if (model.sbc !== null) { -%> 28 | M552 <%- params({ 29 | I: i, 30 | P: [iface.configuredIP ?? "0.0.0.0"], 31 | S: 1 32 | }) %> ; configure Ethernet adapter 33 | <% if (iface.configuredIP && iface.configuredIP !== "0.0.0.0") { -%> 34 | M553 P<%- iface.subnet %> ; set netmask 35 | M554 P<%- iface.gateway %> ; set gateway 36 | <% } 37 | } 38 | } 39 | if (iface.type === NetworkInterfaceType.wifi && model.configTool.wifi.ssid.trim() !== "") { 40 | if (model.sbc === null) { -%> 41 | M552 <%- params({ 42 | I: i, 43 | S: 0 44 | }) %> ; disable WiFi adapter 45 | G4 P500 ; wait half a second 46 | M552 <%- params({ 47 | I: i, 48 | S: 1 49 | }) %> ; enable WiFi adapter 50 | G4 P1000 ; wait a second 51 | <% } 52 | 53 | if (iface.configuredIP === null || iface.configuredIP === "0.0.0.0") { -%> 54 | M587 <%- params({ 55 | S: model.configTool.wifi.ssid, 56 | P: model.configTool.wifi.psk 57 | }) %> ; configure WiFi using DHCP 58 | <% } else { -%> 59 | M587 <%- params({ 60 | S: model.configTool.wifi.ssid, 61 | P: model.configTool.wifi.psk, 62 | I: [iface.configuredIP ?? "0.0.0.0"], 63 | J: [iface.gateway], 64 | K: [iface.netmask] 65 | }) %> ; configure WiFi using static IP address 66 | <% } 67 | } 68 | } 69 | 70 | if (i === undefined) { 71 | break; 72 | } 73 | } 74 | 75 | if (model.sbc !== null) { 76 | // Configure network protocols 77 | for (const protocol of Object.values(NetworkProtocol)) { 78 | if (model.network.interfaces.some(iface => iface.state !== NetworkInterfaceState.disabled && iface.activeProtocols.has(protocol))) { 79 | let protocolNumber = undefined, protocolSecure = undefined, protocolName = undefined; 80 | switch (protocol) { 81 | case NetworkProtocol.HTTP: 82 | protocolNumber = 0; 83 | protocolName = "HTTP"; 84 | break; 85 | case NetworkInterfaceState.HTTPS: 86 | protocolNumber = 0; 87 | protocolSecure = 1; 88 | protocolName = "HTTPS"; 89 | break; 90 | case NetworkProtocol.FTP: 91 | protocolNumber = 1; 92 | protocolName = "FTP"; 93 | break; 94 | case NetworkInterfaceState.FTPS: 95 | protocolNumber = 1; 96 | protocolSecure = 1; 97 | protocolName = "SFTP"; 98 | break; 99 | case NetworkProtocol.Telnet: 100 | protocolNumber = 2; 101 | protocolName = "Telnet"; 102 | break; 103 | case NetworkProtocol.SSH: 104 | protocolNumber = 2; 105 | protocolSecure = 1; 106 | protocolName = "SSH"; 107 | break; 108 | } 109 | 110 | if (protocolNumber !== undefined) { -%> 111 | M586 <%- params({ 112 | P: protocolNumber, 113 | T: protocolSecure, 114 | S: 1 115 | }) %> ; configure <%- protocolName %> 116 | <% } 117 | } 118 | } 119 | } 120 | 121 | if (model.sbc !== null) { -%> 122 | M292 ; hide message box again upon completion 123 | <% } -%> -------------------------------------------------------------------------------- /public/templates/tfree.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; tfree<%- toolNumber %>.g 3 | ; called when tool <%- toolNumber %> is freed 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } else { -%> 8 | ; No content in this file 9 | <% } -%> 10 | -------------------------------------------------------------------------------- /public/templates/tpost.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; tpost<%- toolNumber %>.g 3 | ; called after tool <%- toolNumber %> has been selected 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } 8 | if (model.configTool.waitForToolTemperatures) { -%> 9 | M116 P<%- toolNumber %> ; wait for tool temperatures to be reached 10 | <% } else if (preview) { -%> 11 | ; No content in this file 12 | <% } -%> 13 | -------------------------------------------------------------------------------- /public/templates/tpre.ejs: -------------------------------------------------------------------------------- 1 | <% if (!preview) { -%> 2 | ; tpre<%- toolNumber %>.g 3 | ; called before tool <%- toolNumber %> is selected 4 | ; 5 | ; generated by RepRapFirmware Configuration Tool v<%- version %> on <%- (new Date()).toString() %> 6 | 7 | <% } else if (preview) { -%> 8 | ; No content in this file 9 | <% } -%> 10 | -------------------------------------------------------------------------------- /scss/style.scss: -------------------------------------------------------------------------------- 1 | $enable-negative-margins: true; 2 | $offcanvas-horizontal-width: 280px; 3 | $popover-max-width: 420px; 4 | $tooltip-max-width: 220px; 5 | 6 | .card-header > a { 7 | text-decoration: none; 8 | } 9 | 10 | .table th { 11 | padding-left: 0.5rem; 12 | } 13 | .table td { 14 | padding-left: 0.25rem; 15 | } 16 | 17 | .table th { 18 | vertical-align: middle; 19 | } 20 | 21 | input::-webkit-outer-spin-button, 22 | input::-webkit-inner-spin-button { 23 | -webkit-appearance: none; 24 | margin: 0; 25 | } 26 | input[type=number] { 27 | -moz-appearance: textfield; 28 | } 29 | 30 | .small-text { 31 | font-size: 0.75rem; 32 | } 33 | 34 | @import "../node_modules/bootstrap/scss/bootstrap"; -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 73 | 74 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/Card.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 59 | 60 | 157 | -------------------------------------------------------------------------------- /src/components/ConfigSection.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 38 | -------------------------------------------------------------------------------- /src/components/ProgressIcon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 29 | -------------------------------------------------------------------------------- /src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 117 | 118 | 133 | -------------------------------------------------------------------------------- /src/components/calculators/BaseCalculator.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 24 | 25 | 173 | -------------------------------------------------------------------------------- /src/components/calculators/StealthChopCalculator.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 53 | 54 | 103 | -------------------------------------------------------------------------------- /src/components/dialogs/BaseDialog.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 75 | -------------------------------------------------------------------------------- /src/components/dialogs/CoreKinematicsDialog.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 58 | -------------------------------------------------------------------------------- /src/components/dialogs/DeltaKinematicsDialog.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 92 | -------------------------------------------------------------------------------- /src/components/dialogs/HangprinterKinematicsDialog.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | -------------------------------------------------------------------------------- /src/components/dialogs/HeaterModelDialog.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 108 | -------------------------------------------------------------------------------- /src/components/dialogs/PolarKinematicsDialog.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 35 | -------------------------------------------------------------------------------- /src/components/dialogs/ScaraKinematicsDialog.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 39 | -------------------------------------------------------------------------------- /src/components/dialogs/ToolDialog.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 60 | -------------------------------------------------------------------------------- /src/components/inputs/CheckInput.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | 17 | 51 | -------------------------------------------------------------------------------- /src/components/inputs/DriverList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | 40 | 90 | -------------------------------------------------------------------------------- /src/components/inputs/DriverSelection.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 86 | -------------------------------------------------------------------------------- /src/components/inputs/ExtruderList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | 40 | 98 | -------------------------------------------------------------------------------- /src/components/inputs/FanList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | 40 | 101 | -------------------------------------------------------------------------------- /src/components/inputs/HeaterList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 39 | 40 | 98 | -------------------------------------------------------------------------------- /src/components/inputs/HomingSpeedsInput.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 33 | 34 | 122 | -------------------------------------------------------------------------------- /src/components/inputs/IpInput.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 15 | 89 | -------------------------------------------------------------------------------- /src/components/inputs/NumberInput.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | 24 | 140 | -------------------------------------------------------------------------------- /src/components/inputs/OptionalNumberInput.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/inputs/RatioInput.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 22 | 23 | 84 | -------------------------------------------------------------------------------- /src/components/inputs/SelectInput.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 49 | 50 | 138 | -------------------------------------------------------------------------------- /src/components/inputs/SensorList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 43 | 44 | 130 | -------------------------------------------------------------------------------- /src/components/inputs/TextInput.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 15 | 92 | -------------------------------------------------------------------------------- /src/components/inputs/TwoNumberInput.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 36 | 37 | 157 | -------------------------------------------------------------------------------- /src/components/monaco/GCodeInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/monaco/GCodeOutput.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/monaco/monaco-gcode.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from "monaco-editor/esm/vs/editor/editor.api"; 2 | import { gcodeFDMLanguage } from "@duet3d/monacotokens"; 3 | 4 | const languageConfiguration: monaco.languages.LanguageConfiguration = { 5 | comments: { 6 | lineComment: ";", 7 | }, 8 | }; 9 | 10 | monaco.languages.register({ id: "gcode-fdm" }); 11 | monaco.languages.setMonarchTokensProvider("gcode-fdm", gcodeFDMLanguage); 12 | monaco.languages.setLanguageConfiguration("gcode-fdm", languageConfiguration); 13 | -------------------------------------------------------------------------------- /src/components/monaco/monaco-worker.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from "monaco-editor/esm/vs/editor/editor.api"; 2 | import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; 3 | 4 | self.MonacoEnvironment = { 5 | getWorker() { 6 | return new editorWorker(); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/sections/Lasers.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /src/components/sections/LedStrips.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 69 | 70 | 88 | 89 | 139 | -------------------------------------------------------------------------------- /src/components/sections/Miscellaneous.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /src/components/sections/Spindles.vue: -------------------------------------------------------------------------------- 1 | 114 | 115 | 188 | -------------------------------------------------------------------------------- /src/directives/VPreset.ts: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "bootstrap"; 2 | import type { Directive, DirectiveBinding } from "vue"; 3 | 4 | /** 5 | * Interface for accessing tooltip elements 6 | */ 7 | interface VPresetElement { 8 | dataset: { originalTitle: string }; 9 | title: string; 10 | tooltip?: Tooltip; 11 | } 12 | 13 | /** 14 | * Get the title for a given HTML element 15 | * @param binding Binding of the directive 16 | * @param el HTML element 17 | */ 18 | function getTitle(binding: DirectiveBinding, el: any): string { 19 | // Get default preset if no explicit value is set 20 | const preset = binding.value, unit = el.dataset.unit || ""; 21 | 22 | // Get the corresponding caption for the default value 23 | let defaultValue: string = ""; 24 | if (preset !== undefined && preset !== null) { 25 | if (el.tagName === "SELECT") { 26 | for (let option of el.options) { 27 | const value = option.hasOwnProperty("_value") ? option._value : option.value; 28 | if (value instanceof Object && !(value instanceof Array)) { 29 | let equal = true; 30 | for (let key in preset as Object) { 31 | if (preset[key] != value[key]) { 32 | equal = false; 33 | break; 34 | } 35 | } 36 | if (equal) { 37 | defaultValue = option.textContent; 38 | break; 39 | } 40 | } else if (value == preset) { 41 | defaultValue = option.textContent; 42 | break; 43 | } 44 | } 45 | } else if (preset === "" && el.tagName === "INPUT" && el.placeholder !== "") { 46 | defaultValue = el.placeholder; 47 | } else if (el.classList.contains("btn-group-toggle")) { 48 | for (let child of el.children) { 49 | if (child.children.length > 0) { 50 | const subChild = child.children[0]; 51 | if (subChild.tagName === 'INPUT') { 52 | if (preset instanceof Array) { 53 | for (let element of preset) { 54 | if (element.toString() == subChild.value) { 55 | if (!defaultValue) { 56 | defaultValue = child.textContent; 57 | } else { 58 | defaultValue += ', ' + child.textContent; 59 | } 60 | } 61 | } 62 | } else if (subChild.value == preset.toString()) { 63 | defaultValue = child.textContent; 64 | break; 65 | } 66 | } 67 | } 68 | } 69 | if (defaultValue == undefined && preset instanceof Array) { 70 | defaultValue = 'None'; 71 | } 72 | } else if (typeof preset === "boolean") { 73 | defaultValue = preset ? "Enabled" : "Disabled"; 74 | } else { 75 | defaultValue = preset + unit; 76 | } 77 | } 78 | 79 | // Get the label for this value 80 | let title = el.dataset.originalTitle; 81 | if (!title) { 82 | for (let child of el.children) { 83 | if (child.title) { 84 | title = child.title; 85 | break; 86 | } 87 | } 88 | } 89 | 90 | // Get the range if possible 91 | let range = ""; 92 | if (title) { 93 | range = "
"; 94 | if (el.tagName === "INPUT") { 95 | if (el.min !== "" && el.max !== "") { 96 | range += `
Allowed Range: ${el.min} - ${el.max}${unit}`; 97 | } else if (el.min !== "") { 98 | range += `
Minimum Value: ${el.min}${unit}`; 99 | } else if (el.max !== "") { 100 | range += `
Maximum Value: ${el.max}${unit}`; 101 | } 102 | } 103 | } 104 | 105 | return defaultValue ? (title + range !== "" ? `${title}${range}
Default Value: ${defaultValue}` : `Default Value: ${defaultValue}`) : `${title}${range}`; 106 | } 107 | 108 | export const VPreset: Directive = { 109 | mounted(el: Element & VPresetElement, binding: DirectiveBinding): void { 110 | // Delete the default title so the value can be determined from getTitle() 111 | el.dataset.originalTitle = el.title; 112 | el.title = ""; 113 | 114 | // Register Bootstrap tooltip 115 | const targetEl = el.classList.contains("form-check") ? el.querySelector("label") ?? el : el; 116 | el.tooltip = new Tooltip(targetEl, { 117 | html: true, 118 | title: () => getTitle(binding, el), 119 | trigger: "hover" 120 | }); 121 | }, 122 | beforeUnmount(el: Element & VPresetElement) { 123 | if (el.tooltip) { 124 | el.tooltip.dispose(); 125 | el.tooltip = undefined; 126 | } 127 | } 128 | } 129 | 130 | export function closeAllTooltips() { 131 | const tooltipElements = document.querySelectorAll("[aria-describedby^=tooltip]"); 132 | for (let i = 0; i < tooltipElements.length; i++) { 133 | const el = tooltipElements[i] as any; 134 | if (el.tooltip) { 135 | el.tooltip.hide(); 136 | } 137 | } 138 | } 139 | 140 | export default VPreset 141 | -------------------------------------------------------------------------------- /src/directives/VTitle.ts: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "bootstrap"; 2 | import type { Directive, DirectiveBinding } from "vue"; 3 | 4 | /** 5 | * Interface for accessing tooltip elements 6 | */ 7 | interface VTitleElement { 8 | tooltip?: Tooltip; 9 | } 10 | 11 | export const VTitle: Directive = { 12 | mounted(el: Element & VTitleElement, binding: DirectiveBinding): void { 13 | el.tooltip = new Tooltip(el, { 14 | html: true, 15 | title: () => binding.value, 16 | trigger: "hover" 17 | }); 18 | }, 19 | beforeUnmount(el: Element & VTitleElement) { 20 | if (el.tooltip) { 21 | el.tooltip.dispose(); 22 | el.tooltip = undefined; 23 | } 24 | } 25 | } 26 | 27 | export default VTitle 28 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { createPinia } from "pinia"; 3 | 4 | import "../scss/style.scss" 5 | import "bootstrap/dist/js/bootstrap.bundle"; 6 | import "bootstrap-icons/font/bootstrap-icons.css"; 7 | 8 | import App from "./App.vue"; 9 | import VPreset from "./directives/VPreset"; 10 | import VTitle from "./directives/VTitle"; 11 | import router from "./router"; 12 | 13 | createApp(App) 14 | .use(createPinia()) 15 | .directive("preset", VPreset) 16 | .directive("title", VTitle) 17 | .use(router) 18 | .mount("#app"); 19 | 20 | // Prefetch Monaco component here so they're rendered faster later 21 | const _ = new Promise(async () => { 22 | await import("./components/monaco/GCodeInput.vue"); 23 | await import("./components/monaco/GCodeOutput.vue"); 24 | }); 25 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | 3 | import StartView from "@/views/StartView.vue"; 4 | import PresetsView from "@/views/PresetsView.vue"; 5 | 6 | const router = createRouter({ 7 | history: createWebHistory(import.meta.env.BASE_URL), 8 | routes: [ 9 | { 10 | path: "/", 11 | name: "start", 12 | component: StartView 13 | }, 14 | { 15 | path: "/Presets", 16 | name: "presets", 17 | component: PresetsView, 18 | }, 19 | { 20 | path: "/Configuration", 21 | name: "configuration", 22 | component: () => import('../views/ConfigurationView.vue') 23 | }, 24 | { 25 | path: "/Summary", 26 | name: "summary", 27 | component: () => import('../views/SummaryView.vue') 28 | } 29 | ], 30 | scrollBehavior(to, from, savedPosition) { 31 | if (savedPosition !== null) { 32 | return savedPosition; 33 | } 34 | if (to.hash) { 35 | return { el: to.hash, behavior: "smooth", top: 56 }; 36 | } 37 | return { left: 0, top: 0 }; 38 | } 39 | }); 40 | 41 | router.beforeEach((to) => { 42 | if (to.path.startsWith("/Summary") && document.body.querySelector(".is-invalid") !== null) { 43 | alert("There are errors in your configuration. You must fix them first before you can continue."); 44 | return false; 45 | } 46 | return true; 47 | }); 48 | 49 | export default router; 50 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/store/BaseBoard.ts: -------------------------------------------------------------------------------- 1 | import type { Board } from "@duet3d/objectmodel"; 2 | 3 | /** 4 | * Basic descriptor interface for supported main or expansion boards 5 | */ 6 | export interface BaseBoardDescriptor { 7 | hasADCAutoCalibration: boolean; 8 | hasClosedLoopDrivers: boolean; // TODO add this to object model -> boards 9 | hasInputPullUps: boolean; 10 | hasSmartDrivers: boolean; // TODO add this to object model -> boards 11 | hasStealthChop: boolean; 12 | hasVrefMonitor: boolean; 13 | motorWarnCurrent: number; 14 | motorMaxCurrent: number; 15 | minVoltage: number, 16 | maxVoltage: number, 17 | numDrivers: number; // TODO add this to object model -> boards 18 | microstepInterpolations: Array; // TODO add this to object model -> boards 19 | objectModelBoard: Board; 20 | ports: Record>; 21 | supportsAccelerometer: boolean; 22 | } 23 | 24 | /** 25 | * Enumeration of possible port types 26 | */ 27 | export enum PortType { 28 | analogIn = "analogIn", 29 | fan = "fan", 30 | fanTacho = "fanTacho", 31 | gpIn = "gpIn", 32 | gpInInterrupt = "gpInInterrupt", 33 | gpOut = "gpOut", 34 | heater = "heater", 35 | pwm = "pwm", 36 | scanning = "scanning", 37 | spiCs = "spiCs", 38 | thermistor = "thermistor", 39 | uart = "uart" 40 | } 41 | -------------------------------------------------------------------------------- /src/store/compatibility/LegacyExpansionBoards.ts: -------------------------------------------------------------------------------- 1 | export enum LegacyExpansionBoardType { 2 | DueX2 = "Duex 2", 3 | DueX5 = "Duex 5", 4 | Duet3Mini2Plus = "Dual Stepper Driver Expansion Module", 5 | EXP3HC = "EXP3HCC", 6 | TOOL1LC = "TOOL1LC", 7 | EXP1XD = "EXP1XD", 8 | EXP1HCL = "EXP1HCL" 9 | } 10 | 11 | export const LegacyExpansionBoards = { 12 | [LegacyExpansionBoardType.DueX2]: { 13 | isCanBoard: false, 14 | isToolBoard: false, 15 | numDrives: 2, 16 | heaterPorts: ['duex.e2heat', 'duex.e3heat'], 17 | fanPorts: ['duex.fan3', 'duex.fan4', 'duex.fan5', 'duex.fan6', 'duex.fan7', 'duex.fan8'], 18 | gpioPorts: ['exp.e2stop', 'exp.e3stop', 'duex.gp1', 'duex.gp2', 'duex.gp3', 'duex.gp4'], 19 | analogPorts: ['duex.e2temp', 'duex.e3temp'], 20 | pwmPorts: ['duex.e2heat', 'duex.e3heat', 'duex.gp1', 'duex.gp2', 'duex.gp3', 'duex.gp4', 'duex.pwm1', 'duex.pwm2'], 21 | spiCsPorts: ['duex.cs5', 'duex.cs6', 'duex.cs7', 'duex.cs8'], 22 | maxRtdBoards: 2 23 | }, 24 | [LegacyExpansionBoardType.DueX5]: { 25 | isCanBoard: false, 26 | isToolBoard: false, 27 | numDrives: 5, 28 | heaterPorts: ['duex.e2heat', 'duex.e3heat', 'duex.e4heat', 'duex.e5heat', 'duex.e6heat'], 29 | fanPorts: ['duex.fan3', 'duex.fan4', 'duex.fan5', 'duex.fan6', 'duex.fan7', 'duex.fan8'], 30 | gpioPorts: ['exp.e2stop', 'exp.e3stop', 'exp.e4stop', 'exp.e5stop', 'exp.e6stop', 'duex.gp1', 'duex.gp2', 'duex.gp3', 'duex.gp4', 'duex.pwm1', 'duex.pwm2', 'duex.pwm3', 'duex.pwm4', 'duex.pwm5'], 31 | analogPorts: ['duex.e2temp', 'duex.e3temp', 'duex.e4temp', 'duex.e5temp', 'duex.e6temp'], 32 | pwmPorts: ['duex.e2heat', 'duex.e3heat', 'duex.e4heat', 'duex.e5heat', 'duex.e6heat', 'duex.gp1', 'duex.gp2', 'duex.gp3', 'duex.gp4', 'duex.pwm1', 'duex.pwm2', 'duex.pwm3', 'duex.pwm4', 'duex.pwm5'], 33 | spiCsPorts: ['duex.cs5', 'duex.cs6', 'duex.cs7', 'duex.cs8'], 34 | maxRtdBoards: 2 35 | }, 36 | [LegacyExpansionBoardType.Duet3Mini2Plus]: { 37 | isCanBoard: false, 38 | isToolBoard: false, 39 | numDrives: 2, 40 | heaterPorts: [], 41 | fanPorts: [], 42 | fanTachoPorts: [], 43 | gpioPorts: [], 44 | analogPorts: [], 45 | pwmPorts: [], 46 | spiCsPorts: [], 47 | maxRtdBoards: 2 48 | }, 49 | 'EXP3HC': { 50 | isCanBoard: true, 51 | isToolBoard: false, 52 | numDrives: 3, 53 | heaterPorts: ['out0', 'out1', 'out2'], 54 | fanPorts: ['out3', 'out4', 'out5', 'out6', 'out7', 'out8'], 55 | gpioPorts: [/*'io0.out', 'io0.in',*/ 'io1.out', 'io1.in', 'io2.out', 'io2.in', 'io3.out', 'io3.in', 'io4.out', 'io4.in', 'io5.out', 'io5.in', 'out3.tach', 'out4.tach', 'out5.tach'], 56 | analogPorts: ['temp0', 'temp1', 'temp2', /*'io0.in',*/ 'io1.in', 'io2.in', 'io5.in'], 57 | pwmPorts: ['io1.out', 'io4.out'], 58 | spiCsPorts: ['spi.cs0', 'spi.cs1', 'spi.cs2', 'spi.cs3'], 59 | maxRtdBoards: 2 60 | }, 61 | 'TOOL1LC': { 62 | isCanBoard: true, 63 | isToolBoard: true, 64 | numDrives: 1, 65 | heaterPorts: ['out0'], 66 | fanPorts: ['out1', 'out2'], 67 | gpioPorts: ['io0.out', 'io0.in', 'io1.out', 'io1.in', 'io2.in', 'out1.tach', 'out2.tach'], 68 | analogPorts: ['temp0', 'temp1', 'io0.in'], 69 | pwmPorts: ['io0.out'], 70 | spiCsPorts: [], 71 | maxRtdBoards: 0 72 | }, 73 | 'EXP1XD': { 74 | isCanBoard: true, 75 | isToolBoard: true, 76 | numDrives: 1, 77 | heaterPorts: [], 78 | fanPorts: ['out0', 'out1'], 79 | gpioPorts: ['io0.out', 'io0.in', 'io1.in', 'io2.in', 'io2.out'], 80 | analogPorts: ['temp0'], 81 | pwmPorts: ['io0.out', 'io2.out'], 82 | spiCsPorts: [], 83 | maxRtdBoards: 0 84 | }, 85 | 'EXP1HCL': { 86 | isCanBoard: true, 87 | isToolBoard: true, 88 | numDrives: 1, 89 | heaterPorts: [], 90 | fanPorts: ['out0', 'out1'], 91 | gpioPorts: ['io0.out', 'io0.in', 'io1.out', 'io1.in'], 92 | analogPorts: ['temp0'], 93 | pwmPorts: ['io0.out'], 94 | spiCsPorts: [], 95 | maxRtdBoards: 0 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/store/compatibility/LegacyPreset.ts: -------------------------------------------------------------------------------- 1 | export enum LegacyGeometry { 2 | Cartesian = "cartesian", 3 | CoreXY = "corexy", 4 | CoreXZ = "corexz", 5 | Delta = "delta" 6 | } 7 | 8 | export interface LegacyMenuItem { 9 | name: string; 10 | value: string; 11 | } 12 | 13 | export enum LegacyEndstopType { 14 | None, 15 | Switch, 16 | Switch_deprecated, 17 | ZProbe, 18 | StallDetection 19 | } 20 | 21 | export enum LegacyEndstopLocation { 22 | LowEnd = 1, 23 | HighEnd, 24 | } 25 | 26 | export interface LegacyDrive { 27 | direction: boolean; 28 | microstepping: number; 29 | microstepping_interpolation: boolean; 30 | steps_per_mm: number; 31 | instant_dv: number; 32 | max_speed: number; 33 | acceleration: number; 34 | current: number; 35 | driver: number; // v1-2 only 36 | driver_v3: string; // v3+ 37 | endstop_pin: string | null; // v3+ 38 | endstop_type: LegacyEndstopType; 39 | endstop_location: LegacyEndstopLocation; 40 | } 41 | 42 | export interface LegacyPoint { 43 | x: number; 44 | y: number; 45 | z: number; 46 | } 47 | 48 | export interface LegacyHeater { 49 | temp_limit: number; 50 | scale_factor: number; 51 | series: number; 52 | thermistor: number; 53 | beta: number; 54 | a: number; 55 | b: number; 56 | c: number; 57 | channel: number; // v1-2 only 58 | sensor: number; // v1-2 only 59 | output_pin: string | null; // v3+ 60 | sensor_type: string; // v3+ 61 | sensor_pin: string | null; // v3+ 62 | } 63 | 64 | export interface LegacyTool { 65 | mix_ratio: Array; 66 | number: number; 67 | name: string; 68 | extruders: Array; 69 | heaters: Array; 70 | fans: Array; 71 | x_offset: number; 72 | y_offset: number; 73 | z_offset: number; 74 | } 75 | 76 | export interface LegacyFan { 77 | name: string; 78 | value: number; 79 | inverted: boolean; // v1-2 only 80 | frequency: number; 81 | thermostatic: boolean; 82 | heaters: Array; 83 | trigger_temperature: number; 84 | output_pin: string | null; // v3+ 85 | } 86 | 87 | export enum LegacyProbeType { 88 | None = "noprobe", 89 | Switch = "switch", 90 | Unmodulated = "unmodulated", 91 | Modulated = "modulated", 92 | SmartEffector = "effector", 93 | BLTouch = "bltouch" 94 | } 95 | 96 | export interface LegacyPreset { 97 | board: string; 98 | expansion_boards: Array; 99 | firmware: number; 100 | standalone: boolean; 101 | nvram: boolean; 102 | auto_save: { 103 | enabled: boolean; 104 | save_threshold: number; 105 | resume_threshold: number; 106 | gcodes_to_run: string; 107 | }; 108 | display: { 109 | type: number; 110 | encoder_steps: number; 111 | spi_frequency: number; 112 | menus: Array; 113 | images: Array; 114 | }, 115 | panelDue: boolean; 116 | geometry: { 117 | type: LegacyGeometry; 118 | 119 | // Cartesian, CoreXY, CoreXZ 120 | mins: Array; 121 | maxes: Array; 122 | 123 | // Delta 124 | delta_radius: number; 125 | homed_height: number; 126 | low_dive_height: boolean; 127 | max_carriage_travel: number; 128 | print_radius: number; 129 | rod_length: number; 130 | z_min: number; 131 | }; 132 | drives: Array; 133 | idle: { 134 | used: boolean; 135 | factor: number; 136 | timeout: number; 137 | }; 138 | homing_speed_fast: number; 139 | homing_speed_slow: number; 140 | travel_speed: number; 141 | z_dive_height: number; 142 | slow_homing: boolean; 143 | probe: { 144 | type: LegacyProbeType; 145 | recovery_time: number; 146 | trigger_height: number; 147 | trigger_value: number; 148 | x_offset: number; 149 | y_offset: number; 150 | speed: number; 151 | deploy: boolean; 152 | points: Array; 153 | pwm_channel: number; // v1-2 only 154 | pwm_inverted: boolean; // v1-2 only 155 | pwm_pin: string | null; // v3+ 156 | input_pin: string | null; // v3+ 157 | modulation_pin: string | null; // v3+ 158 | }, 159 | bed_is_nozzle: boolean; 160 | bed: { 161 | present: boolean; 162 | use_pid: boolean; 163 | heater: number; 164 | }, 165 | chamber: { 166 | present: boolean; 167 | use_pid: boolean; 168 | heater: number; 169 | }; 170 | heaters: Array; 171 | num_nozzles: number; 172 | toolchange_wait_for_temperatures: boolean; 173 | generate_t_code: boolean; 174 | tools: Array; 175 | compensation_x_offset: number; 176 | compensation_y_offset: number; 177 | peripheral_points: number; 178 | halfway_points: number; 179 | calibration_factors: number; 180 | probe_radius: number; 181 | mesh: { 182 | x_min: number; 183 | x_max: number; 184 | y_min: number; 185 | y_max: number; 186 | radius: number; 187 | spacing: number; 188 | }; 189 | home_first: boolean; 190 | orthogonal: { 191 | compensation: boolean; 192 | height: number; 193 | deviations: Array; 194 | }; 195 | network: { 196 | enabled: boolean; 197 | mac_address: string; 198 | name: string; 199 | password: string; 200 | ssid: string; 201 | ssid_password: string; 202 | dhcp: boolean; 203 | ip: string; 204 | netmask: string; 205 | gateway: string; 206 | protocols: { 207 | http: boolean; 208 | ftp: boolean; 209 | telnet: boolean; 210 | }; 211 | }, 212 | fans: Array; 213 | custom_settings: string; 214 | } 215 | -------------------------------------------------------------------------------- /src/store/defaults.ts: -------------------------------------------------------------------------------- 1 | import { CoreKinematics, DeltaKinematics, DeltaTower, initCollection, initObject, KinematicsName, NetworkInterface, NetworkInterfaceState, NetworkProtocol } from "@duet3d/objectmodel"; 2 | 3 | export type CoreKinematicsTypes = 4 | KinematicsName.cartesian | 5 | KinematicsName.coreXY | 6 | KinematicsName.coreXZ | 7 | KinematicsName.coreXYU | 8 | KinematicsName.coreXYUV | 9 | KinematicsName.markForged; 10 | 11 | export const DefaultForwardMatrix: { [Property in CoreKinematicsTypes]: ReadonlyArray> } = { 12 | [KinematicsName.cartesian]: [ 13 | [1, 0, 0], 14 | [0, 1, 0], 15 | [0, 0, 1] 16 | ], 17 | [KinematicsName.coreXY]: [ 18 | [0.5, 0.5, 0], 19 | [0.5, -0.5, 0], 20 | [0, 0, 1] 21 | ], 22 | [KinematicsName.coreXZ]: [ 23 | [0.5, 0, 0.167], 24 | [0, 1, 0], 25 | [0.5, 0, -0.167] 26 | ], 27 | [KinematicsName.coreXYU]: [ 28 | [0.5, 0.5, 0, -0.5], 29 | [0.5, -0.5, 0, 0.5], 30 | [0, 0, 1, 0], 31 | [0, 0, 0, 1] 32 | ], 33 | [KinematicsName.coreXYUV]: [ 34 | [0.5, 0.5, 0, 0, 0], 35 | [0.5, -0.5, 0, 0, 0], 36 | [0, 0, 1, 0, 0], 37 | [0, 0, 0, 0.5, 0.5], 38 | [0, 0, 0, 0.5, -0.5] 39 | ], 40 | [KinematicsName.markForged]: [ 41 | [1, 0, 0], 42 | [1, 1, 0], 43 | [0, 0, 1] 44 | ] 45 | } 46 | 47 | export const DefaultInverseMatrix: { [Property in CoreKinematicsTypes]: ReadonlyArray> } = { 48 | [KinematicsName.cartesian]: [ 49 | [1, 0, 0], 50 | [0, 1, 0], 51 | [0, 0, 1] 52 | ], 53 | [KinematicsName.coreXY]: [ 54 | [1, 1, 0], 55 | [1, -1, 0], 56 | [0, 0, 1] 57 | ], 58 | [KinematicsName.coreXZ]: [ 59 | [1, 0, 1], 60 | [0, 1, 0], 61 | [3, 0, -3] 62 | ], 63 | [KinematicsName.coreXYU]: [ 64 | [1, 1, 0, 0], 65 | [1, -1, 0, 1], 66 | [0, 0, 1, 0], 67 | [0, 0, 0, 1] 68 | ], 69 | [KinematicsName.coreXYUV]: [ 70 | [1, 1, 0, 0, 0], 71 | [1, -1, 0, 0, 0], 72 | [0, 0, 1, 0, 0], 73 | [0, 0, 0, 1, 1], 74 | [0, 0, 0, 1, -1] 75 | ], 76 | [KinematicsName.markForged]: [ 77 | [1, 0, 0], 78 | [-1, 1, 0], 79 | [0, 0, 1] 80 | ] 81 | } 82 | 83 | export const DefaultDeltaKinematics = new DeltaKinematics(KinematicsName.delta); 84 | DefaultDeltaKinematics.update({ 85 | deltaRadius: 105.6, 86 | homedHeight: 250, 87 | printRadius: 85, 88 | towers: [ 89 | { 90 | diagonal: 215 91 | }, 92 | { 93 | diagonal: 215 94 | }, 95 | { 96 | diagonal: 215 97 | } 98 | ] 99 | }); 100 | 101 | export function isDefaultCoreKinematics(kinematics: CoreKinematics) { 102 | return JSON.stringify(kinematics.forwardMatrix) === JSON.stringify(DefaultForwardMatrix[kinematics.name as CoreKinematicsTypes]); 103 | } 104 | 105 | export function preconfigureNetworkInterface(iface: NetworkInterface, updateProtocols: boolean = true) { 106 | if (updateProtocols && iface.activeProtocols.values.length === 0) { 107 | iface.activeProtocols.add(NetworkProtocol.HTTP); 108 | } 109 | iface.configuredIP ??= "0.0.0.0"; 110 | iface.subnet ??= "255.255.255.0"; 111 | iface.state ??= NetworkInterfaceState.active; 112 | } 113 | -------------------------------------------------------------------------------- /src/store/model/ConfigDriver.ts: -------------------------------------------------------------------------------- 1 | import { DriverId, ModelObject } from "@duet3d/objectmodel"; 2 | 3 | export enum ConfigDriverClosedLoopEncoderType { 4 | none = 0, 5 | quadratureOnAxis = 1, 6 | quadratureOnMotor = 2, 7 | magnetic = 3 8 | } 9 | 10 | export class ConfigDriverClosedLoop extends ModelObject { 11 | encoderType: ConfigDriverClosedLoopEncoderType = ConfigDriverClosedLoopEncoderType.none; 12 | countsPerFullStep: number | null = 5; 13 | } 14 | 15 | export class ConfigDriverExternal extends ModelObject { 16 | enablePolarity: boolean = false; 17 | minStepPulse: number = 5; 18 | minStepInterval: number = 5; 19 | dirSetupTime: number = 10; 20 | holdTime: number = 0; 21 | } 22 | 23 | export enum ConfigDriverMode { 24 | constantOffTime = 0, 25 | randomOffTime = 1, 26 | spreadCycle = 2, 27 | stealthChop = 3, 28 | closedLoop = 4 29 | } 30 | 31 | export class ConfigDriver extends ModelObject { 32 | readonly closedLoop: ConfigDriverClosedLoop = new ConfigDriverClosedLoop(); 33 | readonly external: ConfigDriverExternal = new ConfigDriverExternal(); 34 | forwards: boolean = true; 35 | homingSpeeds: Array = [10, 5]; 36 | id: DriverId = new DriverId(); 37 | mode: ConfigDriverMode = ConfigDriverMode.spreadCycle; 38 | tpwmThreshold: number = 2000; 39 | sgThreshold: number = 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/store/model/ConfigTempSensor.ts: -------------------------------------------------------------------------------- 1 | import { ModelObject } from "@duet3d/objectmodel"; 2 | 3 | export class ConfigTempSensor extends ModelObject { // TODO add all of this to object model -> sensors -> analog[] 4 | adcHighOffset: number | null = null; 5 | adcLowOffset: number | null = null; 6 | beta: number = 4725; 7 | r25: number = 100000; 8 | shC: number = 7.060e-8; 9 | seriesR: number | null = null; 10 | 11 | filtered: boolean = true; // only used by LinearAnalog 12 | minTemp: number = 0; // temp at ADC == min 13 | maxTemp: number = 200; // temp at ADC == max 14 | 15 | thermocoupleType: string = "K"; // only used by MAX31856 16 | mainsFrequency: number = 50; // used by MAX31856 and MAX31865 17 | numWires: 2 | 3 | 4 = 2; // only used by MAX31865 18 | rref: number | null = null; // only used by MAX31865 19 | 20 | baseSensor: number | null = null; // referenced sensor, e.g. by DHT humidity 21 | } 22 | -------------------------------------------------------------------------------- /src/store/model/ConfigToolModel.ts: -------------------------------------------------------------------------------- 1 | import { initObject, ModelCollection, ModelDictionary, ModelObject, ModelSet } from "@duet3d/objectmodel"; 2 | 3 | import { ConfigPort, ConfigPortFunction } from "@/store/model/ConfigPort"; 4 | import { ConfigTempSensor } from "@/store/model/ConfigTempSensor"; 5 | import { ConfigDriver } from "@/store/model/ConfigDriver"; 6 | import type { ExpansionBoardType } from "@/store/ExpansionBoards"; 7 | import { precise } from "@/utils"; 8 | 9 | export class ConfigAutoSaveModel extends ModelObject { 10 | enabled: boolean = false; 11 | codesToRun: string = "M913 X0 Y0 G91 M83 G1 Z3 E-5 F1000"; 12 | resumeThreshold: number = 22; 13 | saveThreshold: number = 19.8; 14 | } 15 | 16 | export class ConfigCapabilities extends ModelObject { 17 | cnc: boolean = false; 18 | fff: boolean = true; 19 | laser: boolean = false; 20 | } 21 | 22 | export class ConfigDeltaProbePoint extends ModelObject { 23 | x: number = 0; 24 | y: number = 0; 25 | heightCorrection: number = 0; 26 | } 27 | 28 | export class ConfigDeltaProperties extends ModelObject { 29 | peripheralPoints: number = 3; 30 | halfwayPoints: number = 3; 31 | factors: number = 6; 32 | slowHoming: boolean = false; 33 | lowDiveHeight: boolean = true; 34 | probeRadius: number = 85; // This is just for calibration and independent from move.compensation.probeGrid.radius 35 | readonly probePoints: ModelCollection = new ModelCollection(ConfigDeltaProbePoint); 36 | homeFirst: boolean = true; 37 | 38 | /** 39 | * Recalculate the Delta probe points 40 | * @param probeOffsetX X offset of the probe 41 | * @param probeOffsetY Y offset of the probe 42 | */ 43 | calculateProbePoints(probeOffsetX: number, probeOffsetY: number) { 44 | // Recalculate and add all probe points 45 | // Thanks to dc42 for providing the calculation code (original source from escher3d.com) 46 | const prevPoints = this.probePoints.splice(0); 47 | for (let i = 0; i < this.peripheralPoints; i++) { 48 | let probeX = this.probeRadius * Math.sin((2 * Math.PI * i) / this.peripheralPoints); 49 | let probeY = this.probeRadius * Math.cos((2 * Math.PI * i) / this.peripheralPoints); 50 | const rad = Math.sqrt(Math.pow(probeX + probeOffsetX, 2) + Math.pow(probeY + probeOffsetY, 2)) + 0.1; 51 | if (rad > this.probeRadius) { 52 | const factor = this.probeRadius / rad; 53 | probeX *= factor; 54 | probeY *= factor; 55 | } 56 | this.probePoints.push(initObject(ConfigDeltaProbePoint, { 57 | x: probeX, 58 | y: probeY, 59 | heightCorrection: (prevPoints.length > i) ? prevPoints[i].heightCorrection : 0 60 | })); 61 | } 62 | 63 | for (let i = 0; i < this.halfwayPoints; i++) { 64 | let probeX = (this.probeRadius / 2) * Math.sin((2 * Math.PI * i) / this.halfwayPoints); 65 | let probeY = (this.probeRadius / 2) * Math.cos((2 * Math.PI * i) / this.halfwayPoints); 66 | const rad = Math.sqrt(Math.pow(probeX + probeOffsetX, 2) + Math.pow(probeY + probeOffsetY, 2)) + 0.1; 67 | if (rad > this.probeRadius / 2) { 68 | const factor = (this.probeRadius / 2) / rad; 69 | probeX *= factor; 70 | probeY *= factor; 71 | } 72 | this.probePoints.push(initObject(ConfigDeltaProbePoint, { 73 | x: probeX, 74 | y: probeY, 75 | heightCorrection: (prevPoints.length > this.peripheralPoints + i) ? prevPoints[this.peripheralPoints + i].heightCorrection : 0 76 | })); 77 | } 78 | 79 | this.probePoints.push(initObject(ConfigDeltaProbePoint, { 80 | x: 0, 81 | y: 0, 82 | heightCorrection: 0 83 | })); 84 | 85 | for (const point of this.probePoints) { 86 | point.x = precise(point.x, 2); 87 | point.y = precise(point.y, 2); 88 | } 89 | } 90 | } 91 | 92 | export class ConfigDisplayFiles extends ModelObject { 93 | menus: ModelDictionary = new ModelDictionary(true); 94 | images: ModelDictionary = new ModelDictionary(true); 95 | } 96 | 97 | export class ConfigLaserModel extends ModelObject { 98 | maxIntensity: number = 255; 99 | sParamSticky: boolean = false; 100 | } 101 | 102 | export class ConfigWiFi extends ModelObject { 103 | ssid: string = ""; 104 | psk: string = ""; 105 | } 106 | 107 | export class ConfigToolModel extends ModelObject { 108 | version: number = 1; 109 | 110 | readonly autoSave: ConfigAutoSaveModel = new ConfigAutoSaveModel(); 111 | autoSelectFirstTool: boolean = false; 112 | readonly capabilities: ConfigCapabilities = new ConfigCapabilities(); 113 | configOverride: boolean = false; 114 | customSettings: string = ""; 115 | readonly delta: ConfigDeltaProperties = new ConfigDeltaProperties(); 116 | deployRetractProbes: ModelSet = new ModelSet(); 117 | readonly displayFiles: ConfigDisplayFiles = new ConfigDisplayFiles(); 118 | readonly drivers: ModelCollection = new ModelCollection(ConfigDriver); 119 | expansionBoard: ExpansionBoardType | null = null; 120 | homeBeforeAutoCalibration: boolean = false; 121 | homingSpeedFast: number = 30; 122 | homingSpeedSlow: number = 6; 123 | readonly laser: ConfigLaserModel = new ConfigLaserModel(); 124 | name: string | null = null; 125 | orthogonalDistance: number = 85; 126 | panelDueChecksum: boolean = false; 127 | panelDueBaudRate: number = 57600; 128 | password: string = ""; 129 | readonly ports: ModelCollection = new ModelCollection(ConfigPort); 130 | skewOffset: number = 100; 131 | readonly sensors: ModelCollection = new ModelCollection(ConfigTempSensor); 132 | waitForToolTemperatures: boolean = true; 133 | readonly wifi: ConfigWiFi = new ConfigWiFi(); 134 | 135 | assignPort(port: string, fn: ConfigPortFunction | null, index: number, frequency?: number): ConfigPort { 136 | for (const item of this.ports) { 137 | if (item.equals(port)) { 138 | item.function = fn; 139 | item.frequency = frequency ?? ((fn === ConfigPortFunction.fan || ConfigPortFunction.heater) ? 250 : 500); 140 | item.index = index; 141 | item.inverted = port.includes("!"); 142 | item.pullUp = port.includes("^"); 143 | return item; 144 | } 145 | } 146 | throw new Error(`Could not find port ${port}`); 147 | } 148 | 149 | getProbesByBoard(board: number | null): Set { 150 | const probes = new Set(); 151 | for (const port of this.ports) { 152 | if (port.canBoard === board && [ConfigPortFunction.probeIn, ConfigPortFunction.probeMod, ConfigPortFunction.probeServo].includes(port.function!)) { 153 | probes.add(port.index); 154 | } 155 | } 156 | return probes; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get a rounded value with a custom maximum number of fraction digits 3 | * @param value Value to round 4 | * @param maxFractionDigits Maximum number of fraction digits to show 5 | * @returns Rounded value 6 | */ 7 | export function precise(value: number, maxFractionDigits: number = 0) { 8 | const factor = 10 ** maxFractionDigits; 9 | return (Math.round(value * factor) / factor); 10 | } 11 | -------------------------------------------------------------------------------- /src/views/PresetsView.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | -------------------------------------------------------------------------------- /src/views/StartView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 84 | 85 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": [ 4 | "env.d.ts", 5 | "src/**/*", 6 | "src/**/*.vue" 7 | ], 8 | "exclude": [ 9 | "src/**/__tests__/*" 10 | ], 11 | "compilerOptions": { 12 | "composite": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 14 | "baseUrl": ".", 15 | "paths": { 16 | "@/*": [ 17 | "./src/*" 18 | ] 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": [ 4 | "vite.config.*" 5 | ], 6 | "compilerOptions": { 7 | "composite": true, 8 | "noEmit": true, 9 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 10 | 11 | "module": "ES2020", 12 | "moduleResolution": "Bundler", 13 | "types": ["node"] 14 | } 15 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | build: { 9 | chunkSizeWarningLimit: 5000000, 10 | rollupOptions: { 11 | output: { 12 | chunkFileNames: "js/[name]-[hash].js", 13 | entryFileNames: "js/[name]-[hash].js", 14 | 15 | assetFileNames: ({ name }) => { 16 | if (name) { 17 | if (/\.css$/.test(name)) { 18 | return "css/[name]-[hash][extname]"; 19 | } 20 | if (/\.(eot|woff|woff2|ttf)$/.test(name)) { 21 | return "fonts/[name]-[hash][extname]"; 22 | } 23 | if (/\.(gif|jpe?g|png|svg)$/.test(name)) { 24 | return "img/[name]-[hash][extname]"; 25 | } 26 | } 27 | 28 | // default value 29 | // ref: https://rollupjs.org/guide/en/#outputassetfilenames 30 | return "assets/[name]-[hash][extname]"; 31 | }, 32 | manualChunks: (id) => { 33 | if (id.includes("monaco")) { 34 | return "monaco"; 35 | } 36 | if (id.includes("CheckInput")) { 37 | return "components"; 38 | } 39 | } 40 | } 41 | } 42 | }, 43 | plugins: [ 44 | vue() 45 | ], 46 | resolve: { 47 | alias: { 48 | "@": fileURLToPath(new URL("./src", import.meta.url)) 49 | } 50 | }, 51 | worker: { 52 | rollupOptions: { 53 | output: { 54 | entryFileNames: "js/[name]-[hash].js" 55 | } 56 | } 57 | } 58 | }) 59 | --------------------------------------------------------------------------------