156 lines
4.6 KiB
TypeScript
156 lines
4.6 KiB
TypeScript
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<any> = []
|
|
// 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, '<i>software</i>').replace(stlPath, `<i>file ${file}</i>`).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`);
|
|
})
|