import { objectMap, objectOmit } from '@dzeio/object-util' import { exec as execAsync } from 'child_process' import express from 'express' import fs from 'fs/promises' import { evaluate } from 'mathjs' import util from 'util' import { createErrorResponse } from './HTTPError' import { getParams } from './gcodeUtils' const exec = util.promisify(execAsync) const server = express() async function exists(file: string) { try { await fs.stat(file) return true } catch { return false } } // make the files dir server.use((req, _, next) => { let data: Array = [] // req.setEncoding() req.on('data', function(chunk) { data.push(Buffer.from(chunk)) }); req.on('end', function() { req.body = Buffer.concat(data); next(); }); }); const path = process.cwd() server.get('/', (_, res) => { res.send('please read the README.md file...') }) server.post('/', async (req, res) => { const file = (Math.random() * 1000000).toFixed(0) console.log('started processing new request', file) await fs.mkdir('files', { recursive: true }) const overrides = objectOmit(req.query, 'algo', 'config') let config = `${path}/configs/` + (req.query?.config ?? 'config') + '.ini' if (!exists(config)) { console.log('request finished in error :(', file) return createErrorResponse(res, { title: 'Configuration file does not exist', status: 404, detail: `The configuration file you want to use does not exist`, config: req.query?.config ?? 'config' }) } const stlPath = `${path}/files/${file}.stl` const gcodePath = `${path}/files/${file}.gcode` // console.log(stlPath, req.body) await fs.writeFile(stlPath, req.body, { encoding: null }) // console.log(fs.statSync(stlPath).size, req.body.length) let additionnalParams = objectMap(overrides, (v, k) => `--${(k as string).replace(/_/g, '-')} ${v}`).join(' ') const slicer = process.env.SLICER_PATH ?? 'prusa-slicer' if (slicer.includes('prusa')) { additionnalParams += ' --export-gcode' } const cmd = `${slicer} ${stlPath} --load ${config} --output ${gcodePath} ${additionnalParams}` try { await exec(cmd, { timeout: 60000 }) } catch (e: any) { console.log('request finished in error :(', file) const line = e.toString() console.error(e) if (line.includes('Objects could not fit on the bed')) { await fs.rm(stlPath) return createErrorResponse(res, { title: 'Object is too large to fit on the bed', status: 413, detail: `The object you are trying to slice is too large to fit on the current bed` }) } else if (line.includes('No such file')) { await fs.rm(stlPath) return createErrorResponse(res, { title: 'Configuration file does not exist', status: 404, detail: `The configuration file you want to use does not exist`, config: req.query?.config ?? 'config' }) } else if (line.includes('Unknown option')) { await fs.rm(stlPath) return createErrorResponse(res, { title: 'An override does exist', status: 404, detail: `One or more of your overrides are not available in the current slicer`, overrides: overrides }) } else if (line.includes('ETIMEDOUT') || line.includes('Command failed')) { await fs.rm(stlPath) return createErrorResponse(res, { title: 'File is taking too long to process', status: 400, detail: `The file you are trying to process is too large (${req.body.length}o) to be processed in less than 60 secs`, fileId: file, config: req.query?.config ?? 'config', fileSize: req.body.length, overrides: overrides }) } return createErrorResponse(res, { title: 'General I/O error', status: 500, detail: `A server error make it impossible to slice the file`, fileId: file, config: req.query?.config ?? 'config', fileSize: req.body.length, overrides: overrides, serverMessage: e.toString().replace(cmd, 'software').replace(stlPath, `file ${file}`).replace(`${path}/configs/`, '').replace('.ini', '') }) } const gcode = await fs.readFile(gcodePath, 'utf-8') await fs.rm(stlPath) await fs.rm(gcodePath) const params = getParams(gcode) let price = -1 if (req.query?.algo) { let algo = req.query.algo as string // objectLoop(params, (value, key) => { // if (typeof value !== 'number') { // return // } // while (algo.includes(key)) { // algo = algo.replace(key, value.toString()) // } // }) price = evaluate(algo, params) } res.json({ price: parseFloat(price.toFixed(2)), ...getParams(gcode), gcode }) console.log('request successfull :)', file) // res.sendFile(gcodePath) }) server.listen(3000, () => { console.log(`🚀 Server ready at localhost:3000`); })