64
src/gcodeUtils.ts
Normal file
64
src/gcodeUtils.ts
Normal file
@ -0,0 +1,64 @@
|
||||
function parseNumber(str: string): number | undefined {
|
||||
if (!/^(\d|\.)+$/g.test(str)) {
|
||||
return undefined
|
||||
}
|
||||
const float = parseFloat(str)
|
||||
const int = parseInt(str, 10)
|
||||
if (isNaN(int)) {
|
||||
return undefined
|
||||
}
|
||||
if (int !== float) {
|
||||
return float
|
||||
}
|
||||
return int
|
||||
}
|
||||
|
||||
function decodeTime(text: string): number {
|
||||
let timeInSec = 0
|
||||
for (const it of text.split(' ')) {
|
||||
const lastChar = it.charAt(it.length - 1)
|
||||
const time = parseInt(it.slice(0, it.length - 1), 10)
|
||||
switch (lastChar) {
|
||||
case 'm':
|
||||
timeInSec += time * 60
|
||||
break;
|
||||
case 's':
|
||||
timeInSec += time
|
||||
break;
|
||||
case 'h':
|
||||
timeInSec += time * 60 * 60
|
||||
break;
|
||||
case 'd':
|
||||
timeInSec += time * 60 * 60 * 24
|
||||
break;
|
||||
default:
|
||||
throw new Error(`error parsing time ${it} (${time})`)
|
||||
}
|
||||
}
|
||||
return timeInSec
|
||||
}
|
||||
|
||||
export function getParams(data: string) {
|
||||
const lines = data.split('\n').filter((it) => it.startsWith(';') && it.includes('='))
|
||||
const obj: Record<string, string | number> = {}
|
||||
for (const line of lines) {
|
||||
const [key, value] = line.split('=', 2).map((it) => it.slice(1).trim())
|
||||
let realKey = key.replace(/ /g, '_').replace(/\[|\]|\(|\)/g, '')
|
||||
const realValue = parseNumber(value) ?? value
|
||||
let offset = 0
|
||||
while (obj[`${realKey}${offset > 0 ? `_${offset}` : ''}`] && obj[`${realKey}${offset > 0 ? `_${offset}` : ''}`] !== realValue) {
|
||||
offset++
|
||||
}
|
||||
if (offset > 0) {
|
||||
realKey = `${realKey}_${offset}`
|
||||
}
|
||||
if (obj[realKey] && obj[realKey] !== realValue) {
|
||||
throw new Error(`Key collision ${key}=${realValue} ${realKey}=${obj[realKey]}`)
|
||||
}
|
||||
obj[realKey] = parseNumber(value) ?? value
|
||||
if (realKey === 'estimated_printing_time_normal_mode') {
|
||||
obj['estimated_printing_time_seconds'] = decodeTime(value)
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
89
src/main.js
89
src/main.js
@ -1,89 +0,0 @@
|
||||
const express = require('express')
|
||||
const fs = require('fs')
|
||||
const execSync = require('child_process').execSync;
|
||||
const { objectMap } = require('@dzeio/object-util')
|
||||
|
||||
const server = express()
|
||||
|
||||
fs.mkdirSync('files', { recursive: true })
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} data the GCode source
|
||||
* @returns {number} the time in seconds
|
||||
*/
|
||||
function decodeGCode(data) {
|
||||
|
||||
const line = data.split('\n').find((it) => it.includes('estimated printing time'))
|
||||
if (!line) {
|
||||
console.error('line not found :(')
|
||||
process.exit(1)
|
||||
}
|
||||
const time = line.split('=')[1].trim()
|
||||
let timeInSec = 0
|
||||
for (const it of time.split(' ')) {
|
||||
const lastChar = it.charAt(it.length - 1)
|
||||
const time = parseInt(it.slice(0, it.length - 1), 10)
|
||||
console.log(it)
|
||||
switch (lastChar) {
|
||||
case 'm':
|
||||
timeInSec += time * 60
|
||||
break;
|
||||
case 's':
|
||||
timeInSec += time
|
||||
break;
|
||||
case 'h':
|
||||
timeInSec += time * 60 * 60
|
||||
break;
|
||||
case 'd':
|
||||
timeInSec += time * 60 * 60 * 24
|
||||
break;
|
||||
default:
|
||||
throw new Error(`error parsing time ${it} (${time})`)
|
||||
}
|
||||
}
|
||||
console.log('orig', time, 'secs:', timeInSec, 'mins:', timeInSec / 60, 'hours:', timeInSec / 60 / 60, 'days:', timeInSec / 60 / 60 / 24)
|
||||
return timeInSec
|
||||
}
|
||||
|
||||
|
||||
server.use ((req, res, next) => {
|
||||
var data = '';
|
||||
req.setEncoding('utf8');
|
||||
req.on('data', function(chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
req.on('end', function() {
|
||||
req.body = data;
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
const path = process.cwd()
|
||||
|
||||
server.get('/', (req, res) => {
|
||||
res.send('send through POST in body the stl')
|
||||
})
|
||||
|
||||
const config = `${path}/configs/config.ini`
|
||||
|
||||
server.post('/', (req, res) => {
|
||||
const overrides = req.query
|
||||
const file = (Math.random() * 1000000).toFixed(0)
|
||||
const stlPath = `${path}/files/${file}.stl`
|
||||
const gcodePath = `${path}/files/${file}.gcode`
|
||||
console.log(stlPath)
|
||||
fs.writeFileSync(stlPath, req.body)
|
||||
const additionnalParams = objectMap(overrides, (v, k) => `--${k} ${v}`).join(' ')
|
||||
const cmd = `${process.env.SLICER_PATH} --export-gcode ${stlPath} --load ${config} --output ${gcodePath} ${additionnalParams}`
|
||||
execSync(cmd)
|
||||
const gcode = fs.readFileSync(gcodePath, 'utf-8')
|
||||
const time = decodeGCode(gcode)
|
||||
res.json({ timeToPrint: time, gcode })
|
||||
// res.sendFile(gcodePath)
|
||||
})
|
||||
|
||||
server.listen(3000, undefined, () => {
|
||||
console.log(`🚀 Server ready at localhost:3000`);
|
||||
})
|
123
src/main.ts
Normal file
123
src/main.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import express from 'express'
|
||||
import fs from 'fs/promises'
|
||||
import { exec as execAsync } from 'child_process'
|
||||
import { objectMap, objectOmit } from '@dzeio/object-util'
|
||||
import { getParams } from './gcodeUtils'
|
||||
import util from 'util'
|
||||
import { evaluate } from 'mathjs'
|
||||
|
||||
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)
|
||||
res.status(403).send('Configuration file not existing')
|
||||
return
|
||||
}
|
||||
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)
|
||||
res.status(403).send('Object is too large')
|
||||
return
|
||||
} else if (line.includes('No such file')) {
|
||||
await fs.rm(stlPath)
|
||||
res.status(403).send('Configuration file not existing')
|
||||
return
|
||||
} else if (line.includes('Unknown option')) {
|
||||
await fs.rm(stlPath)
|
||||
res.status(403).send('an override does not exists, please contact an administrator or refer to the documentation')
|
||||
return
|
||||
} else if (line.includes('ETIMEDOUT') || line.includes('Command failed')) {
|
||||
await fs.rm(stlPath)
|
||||
res.status(403).send('the file is taking too long to process :(')
|
||||
return
|
||||
}
|
||||
res.status(500).send(`Error while making the file:<br />${e.toString().replace(cmd, '<i>software</i>').replace(stlPath, `<i>file ${file}</i>`).replace(`${path}/configs/`, '').replace('.ini', '')}`)
|
||||
return
|
||||
}
|
||||
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`);
|
||||
})
|
Reference in New Issue
Block a user