feat: Add moer element

Signed-off-by: Florian BOUILLON <f.bouillon@aptatio.com>
This commit is contained in:
2023-06-27 18:30:44 +02:00
parent 4cd2a365ae
commit d76f412b82
19 changed files with 540 additions and 90 deletions

View File

@ -3,17 +3,23 @@ import { objectMap, objectOmit } from '@dzeio/object-util'
import URLManager from '@dzeio/url-manager'
import type { APIRoute } from 'astro'
import { evaluate } from 'mathjs'
import { exec as execSync, spawn } from 'node:child_process'
import { spawn } from 'node:child_process'
import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { promisify } from 'node:util'
import { buildRFC7807 } from '../../../libs/RFCs/RFC7807'
import { getParams } from '../../../libs/gcodeUtils'
import { validateAuth } from '../../../libs/validateAuth'
import DaoFactory from '../../../models/DaoFactory'
import { buildRFC7807 } from '../../../../libs/RFCs/RFC7807'
import RateLimiter from '../../../../libs/RateLimiter'
import ResponseBuilder from '../../../../libs/ResponseBuilder'
import StatusCode from '../../../../libs/StatusCode'
import { getParams } from '../../../../libs/gcodeUtils'
import { validateAuth } from '../../../../libs/validateAuth'
import DaoFactory from '../../../../models/DaoFactory'
interface SliceError {
code: number
output: Array<string>
}
const exec = promisify(execSync)
let tmpDir: string
@ -23,15 +29,7 @@ let tmpDir: string
* price: algorithm from settings
* adionnal settings from https://manual.slic3r.org/advanced/command-line
*/
export const post: APIRoute = async ({ params, request }) => {
const res = await validateAuth(request, {
name: 'slicing.slice',
api: true,
cookie: true
})
if (res) {
return res
}
export const post: APIRoute = async ({ params, request, locals }) => {
if (!tmpDir) {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'saas-'))
}
@ -41,13 +39,24 @@ export const post: APIRoute = async ({ params, request }) => {
if (!config) {
return buildRFC7807({
type: '/missing-config',
status: 404,
type: '/docs/errors/missing-config',
status: StatusCode.NOT_FOUND,
title: 'The configuration does not exists',
details: `The configuration ${configId} does not exists`
})
}
const input = new Uint8Array(Buffer.from(await request.arrayBuffer()))
if (input.length <= 0) {
return buildRFC7807({
type: '/docs/errors/missing-input-file',
status: StatusCode.BAD_REQUEST,
title: 'You are missing the STL file',
details: `To process a file you need to input the file binary datas as the only body in your request`
})
}
const query = new URLManager(request.url).query()
const processId = (Math.random() * 1000000).toFixed(0)
@ -72,7 +81,7 @@ export const post: APIRoute = async ({ params, request }) => {
logger.log('writing STL to filesystem')
// write input
await fs.writeFile(stlPath, new Uint8Array(Buffer.from(await request.arrayBuffer())), {
await fs.writeFile(stlPath, input, {
encoding: null
})
@ -85,87 +94,112 @@ export const post: APIRoute = async ({ params, request }) => {
if (config.type === 'prusa' || true) {
slicerPath = import.meta.env.PRUSASLICER_PATH ?? 'prusa-slicer'
additionnalParams += ' --export-gcode'
additionnalParams += ' --export-gcode --loglevel 4'
slicerCommand = `${path.normalize(stlPath)} --load ${path.normalize(`${processFolder}/config.ini`)} --output ${path.normalize(gcodePath)} ${additionnalParams}`
}
// TODO: check if it does work on a linux environment
// TODO: Externalise IO for the different slicers
try {
logger.log('Running', slicerPath, slicerCommand)
await new Promise<void>((res, rej) => {
const slicer = spawn(slicerPath, slicerCommand.split(' '))
slicer.stdout.on('data', (data) => {
logger.log('[stdout]',data.toString('utf8'))
const logs: Array<string> = []
const slicer = spawn(`"${slicerPath}"`, slicerCommand.split(' '), {
shell: true
})
slicer.stderr.on('data', (data: Buffer) => {
logger.log('[stderr]', data.toString('utf8'))
const log = (data: Buffer) => {
const line = `${data.toString('utf-8')}`
logger.log(line)
logs.push(line)
}
slicer.stdout.on('data', log)
slicer.stderr.on('data', log)
slicer.on('spawn', () => {
logs.push('Process spawned')
logger.log('Process spawned')
})
slicer.on('error', (err) => {
logs.push('Process error')
logger.log('Process error')
logger.log('error', err)
logs.push(err.toString())
rej(err)
})
slicer.on('close', (code, signal) => {
logger.log('code', code)
logger.log('signal', signal)
if (typeof code === 'number' && code > 0) {
rej(code)
slicer.on('close', (code, signal) => {
logs.push('Process closed')
logger.log('Process closed')
logs.push(`with code ${code}`)
logger.log(`with code ${code}`)
logs.push(`and signal ${signal}`)
logger.log(`and signal ${signal}`)
if (typeof code === 'number' && code !== 0) {
rej({
code: code,
output: logs
} as SliceError)
return
}
res()
})
})
} catch (e: any) {
const err = e as SliceError
logger.log('request finished in error :(', processId)
const line = e.toString()
logger.error(e)
if (line.includes('Objects could not fit on the bed')) {
const line = err.toString()
logger.error('error', err, typeof err)
if (err.code === 3221226505 || line.includes('Objects could not fit on the bed')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/object-too-large',
status: 413,
title: 'Object is too large'
})
type: '/docs/errors/object-too-large',
status: StatusCode.PAYLOAD_TOO_LARGE,
title: 'Object is too large',
details: 'The STL you are trying to compile is too large for the configuration you chose'
}, locals.responseBuilder)
} else if (line.includes('No such file')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/missing-config-file',
status: 404,
type: '/docs/errors/missing-config-file',
status: StatusCode.NOT_FOUND,
title: 'Configuration file is missing',
details: `the configuration file "${configId}" is not available on the remote server`
})
}, locals.responseBuilder)
} else if (line.includes('Unknown option')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/slicer-option-unknown',
type: '/docs/errors/slicer-option-unknown',
status: 400,
title: ' config override doew not exists',
details: 'an override does not exists, please contact an administrator or refer to the documentation'
})
}, locals.responseBuilder)
} else if (
line.includes('is not recognized as an internal or external command') ||
line.includes('.dll was not loaded')
) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/slicer-not-found',
status: 408,
type: '/docs/errors/slicer-not-found',
status: StatusCode.SERVICE_UNAVAILABLE,
title: 'the slicer used to process this file has not been found',
details: 'the server has a misconfiguration meaning that we can\'t process the request, please contact an administrator',
additionnalInfo: line.includes('dll') ? 'Missing DLL' : 'Slicer not found '
})
}, locals.responseBuilder)
} else if (line.includes('ETIMEDOUT')) {
await fs.rm(stlPath)
return buildRFC7807({
type: '/timed-out-slicing',
status: 408,
type: '/docs/errors/timed-out-slicing',
status: StatusCode.PAYLOAD_TOO_LARGE,
title: 'Timed out slicing file',
detail: `The file you are trying to process takes too long to be processed`,
processingTimeoutMillis: 60000
})
}, locals.responseBuilder)
}
return buildRFC7807({
type: '/general-input-output-error',
status: 500,
type: '/docs/errors/general-input-output-error',
status: StatusCode.INTERNAL_SERVER_ERROR,
title: 'General I/O error',
details: 'A server error make it impossible to slice the file, please contact an administrator with the json error',
fileId: processId,
@ -173,8 +207,8 @@ export const post: APIRoute = async ({ params, request }) => {
// fileSize: req.body.length,
overrides: overrides,
serverMessage:
e.toString().replace(new RegExp(stlPath), `***FILE***`).replace(new RegExp(processFolder), '')
})
err.output.map((line) => line.replace(new RegExp(stlPath), `***FILE***`).replace(new RegExp(processFolder), ''))
}, locals.responseBuilder)
}
const gcode = await fs.readFile(gcodePath, 'utf-8')
await fs.rm(processFolder, { recursive: true, force: true })
@ -198,35 +232,35 @@ export const post: APIRoute = async ({ params, request }) => {
price = tmp.toFixed(2)
} else {
return buildRFC7807({
type: '/algorithm-error',
type: '/docs/errors/algorithm-error',
status: 500,
title: 'Algorithm compilation error',
details: 'It seems the algorithm resolution failed',
algorithm: algo,
algorithmError: 'Algorithm return a Unit',
parameters: gcodeParams
})
}, locals.responseBuilder)
}
} catch (e) {
logger.dir(e)
return buildRFC7807({
type: '/algorithm-error',
type: '/docs/errors/algorithm-error',
status: 500,
title: 'Algorithm compilation error',
details: 'It seems the algorithm resolution failed',
algorithm: algo,
algorithmError: e,
parameters: gcodeParams
})
}, locals.responseBuilder)
}
}
logger.log('request successfull :)')
return {
status: 200,
body: JSON.stringify({
return locals.responseBuilder
.body({
price: price ? parseFloat(price) : undefined,
...getParams(gcode),
gcode
})
}
.status(200)
.build()
}