|
|
|
@ -1,15 +1,17 @@
|
|
|
|
|
import Logger from '@dzeio/logger'
|
|
|
|
|
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 } from 'node:child_process'
|
|
|
|
|
import { exec as execSync, spawn } from 'node:child_process'
|
|
|
|
|
import fs from 'node:fs/promises'
|
|
|
|
|
import os from 'node:os'
|
|
|
|
|
import path from 'node:path/posix'
|
|
|
|
|
import path from 'node:path'
|
|
|
|
|
import { promisify } from 'node:util'
|
|
|
|
|
import FilesUtils from '../../../libs/FilesUtils'
|
|
|
|
|
import { buildRFC7807 } from '../../../libs/RFCs/RFC7807'
|
|
|
|
|
import { getParams } from '../../../libs/gcodeUtils'
|
|
|
|
|
import { validateAuth } from '../../../libs/validateAuth'
|
|
|
|
|
import DaoFactory from '../../../models/DaoFactory'
|
|
|
|
|
|
|
|
|
|
const exec = promisify(execSync)
|
|
|
|
|
|
|
|
|
@ -21,50 +23,101 @@ let tmpDir: string
|
|
|
|
|
* price: algorithm from settings
|
|
|
|
|
* adionnal settings from https://manual.slic3r.org/advanced/command-line
|
|
|
|
|
*/
|
|
|
|
|
export const post: APIRoute = async ({ request }) => {
|
|
|
|
|
export const post: APIRoute = async ({ params, request }) => {
|
|
|
|
|
const res = await validateAuth(request, {
|
|
|
|
|
name: 'slicing.slice',
|
|
|
|
|
api: true,
|
|
|
|
|
cookie: true
|
|
|
|
|
})
|
|
|
|
|
if (res) {
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
if (!tmpDir) {
|
|
|
|
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'paas-'))
|
|
|
|
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'saas-'))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const configId = params.configId ?? 'undefined'
|
|
|
|
|
const config = await DaoFactory.get('config').get(configId)
|
|
|
|
|
|
|
|
|
|
if (!config) {
|
|
|
|
|
return buildRFC7807({
|
|
|
|
|
type: '/missing-config',
|
|
|
|
|
status: 404,
|
|
|
|
|
title: 'The configuration does not exists',
|
|
|
|
|
details: `The configuration ${configId} does not exists`
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const query = new URLManager(request.url).query()
|
|
|
|
|
const file = (Math.random() * 1000000).toFixed(0)
|
|
|
|
|
console.log('started processing new request', file)
|
|
|
|
|
await fs.mkdir(`${tmpDir}/files`, { recursive: true })
|
|
|
|
|
|
|
|
|
|
const overrides = objectOmit(query, 'algo', 'config')
|
|
|
|
|
const configName = query?.config ?? 'config'
|
|
|
|
|
let config = `${import.meta.env.CONFIGS_PATH}/` + configName + '.ini'
|
|
|
|
|
if (!await FilesUtils.exists(config)) {
|
|
|
|
|
console.log('request finished in error :(', file)
|
|
|
|
|
return buildRFC7807({
|
|
|
|
|
type: '/missing-config-file',
|
|
|
|
|
status: 404,
|
|
|
|
|
title: 'Configuration file is missing',
|
|
|
|
|
details: `the configuration file "${configName}" is not available on the remote server`
|
|
|
|
|
})
|
|
|
|
|
const processId = (Math.random() * 1000000).toFixed(0)
|
|
|
|
|
const logger = new Logger(`process-${processId}`)
|
|
|
|
|
const processFolder = `${tmpDir}/${processId}`
|
|
|
|
|
const pouet = await fs.mkdir(processFolder, { recursive: true })
|
|
|
|
|
|
|
|
|
|
logger.log('poeut', pouet)
|
|
|
|
|
|
|
|
|
|
logger.log('started processing request')
|
|
|
|
|
|
|
|
|
|
logger.log('writing configs to dir')
|
|
|
|
|
for (const file of config.files) {
|
|
|
|
|
await fs.writeFile(`${processFolder}/${file.name}`, file.data)
|
|
|
|
|
}
|
|
|
|
|
const stlPath = `${tmpDir}/files/${file}.stl`
|
|
|
|
|
const gcodePath = `${tmpDir}/files/${file}.gcode`
|
|
|
|
|
|
|
|
|
|
// write file
|
|
|
|
|
|
|
|
|
|
const overrides = objectOmit(query, 'algo')
|
|
|
|
|
|
|
|
|
|
const stlPath = `${processFolder}/input.stl`
|
|
|
|
|
const gcodePath = `${processFolder}/output.gcode`
|
|
|
|
|
|
|
|
|
|
logger.log('writing STL to filesystem')
|
|
|
|
|
// write input
|
|
|
|
|
await fs.writeFile(stlPath, new Uint8Array(Buffer.from(await request.arrayBuffer())), {
|
|
|
|
|
encoding: null
|
|
|
|
|
})
|
|
|
|
|
// console.log(fs.statSync(stlPath).size, req.body.length)
|
|
|
|
|
|
|
|
|
|
// additionnal parameters
|
|
|
|
|
let additionnalParams = objectMap(overrides, (value, key) => `--${(key as string).replace(/_/g, '-')} ${value}`).join(' ')
|
|
|
|
|
const slicer = import.meta.env.PRUSASLICER_PATH ?? 'prusa-slicer'
|
|
|
|
|
if (slicer.includes('prusa')) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let slicerPath: string
|
|
|
|
|
let slicerCommand: string
|
|
|
|
|
|
|
|
|
|
if (config.type === 'prusa' || true) {
|
|
|
|
|
slicerPath = import.meta.env.PRUSASLICER_PATH ?? 'prusa-slicer'
|
|
|
|
|
additionnalParams += ' --export-gcode'
|
|
|
|
|
slicerCommand = `${path.normalize(stlPath)} --load ${path.normalize(`${processFolder}/config.ini`)} --output ${path.normalize(gcodePath)} ${additionnalParams}`
|
|
|
|
|
}
|
|
|
|
|
const cmd = `${slicer} ${stlPath} --load ${config} --output ${gcodePath} ${additionnalParams}`
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await exec(cmd, {
|
|
|
|
|
timeout: 60000
|
|
|
|
|
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'))
|
|
|
|
|
})
|
|
|
|
|
slicer.stderr.on('data', (data: Buffer) => {
|
|
|
|
|
logger.log('[stderr]', data.toString('utf8'))
|
|
|
|
|
})
|
|
|
|
|
slicer.on('error', (err) => {
|
|
|
|
|
logger.log('error', err)
|
|
|
|
|
rej(err)
|
|
|
|
|
})
|
|
|
|
|
slicer.on('close', (code, signal) => {
|
|
|
|
|
logger.log('code', code)
|
|
|
|
|
logger.log('signal', signal)
|
|
|
|
|
if (typeof code === 'number' && code > 0) {
|
|
|
|
|
|
|
|
|
|
rej(code)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
res()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.log('request finished in error :(', file)
|
|
|
|
|
logger.log('request finished in error :(', processId)
|
|
|
|
|
const line = e.toString()
|
|
|
|
|
console.error(e)
|
|
|
|
|
logger.error(e)
|
|
|
|
|
if (line.includes('Objects could not fit on the bed')) {
|
|
|
|
|
await fs.rm(stlPath)
|
|
|
|
|
return buildRFC7807({
|
|
|
|
@ -78,7 +131,7 @@ export const post: APIRoute = async ({ request }) => {
|
|
|
|
|
type: '/missing-config-file',
|
|
|
|
|
status: 404,
|
|
|
|
|
title: 'Configuration file is missing',
|
|
|
|
|
details: `the configuration file "${configName}" is not available on the remote server`
|
|
|
|
|
details: `the configuration file "${configId}" is not available on the remote server`
|
|
|
|
|
})
|
|
|
|
|
} else if (line.includes('Unknown option')) {
|
|
|
|
|
await fs.rm(stlPath)
|
|
|
|
@ -115,18 +168,18 @@ export const post: APIRoute = async ({ request }) => {
|
|
|
|
|
status: 500,
|
|
|
|
|
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: file,
|
|
|
|
|
config: configName,
|
|
|
|
|
fileId: processId,
|
|
|
|
|
config: configId,
|
|
|
|
|
// fileSize: req.body.length,
|
|
|
|
|
overrides: overrides,
|
|
|
|
|
serverMessage:
|
|
|
|
|
e.toString().replace(cmd, '***SLICER***').replace(stlPath, configName ?? `***FILE***`).replace(`${path}/configs/`, '').replace('.ini', '')
|
|
|
|
|
e.toString().replace(new RegExp(stlPath), `***FILE***`).replace(new RegExp(processFolder), '')
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
const gcode = await fs.readFile(gcodePath, 'utf-8')
|
|
|
|
|
await fs.rm(stlPath)
|
|
|
|
|
await fs.rm(gcodePath)
|
|
|
|
|
const params = getParams(gcode)
|
|
|
|
|
await fs.rm(processFolder, { recursive: true, force: true })
|
|
|
|
|
logger.log('Getting parameters')
|
|
|
|
|
const gcodeParams = getParams(gcode)
|
|
|
|
|
let price: string | undefined
|
|
|
|
|
if (query?.algo) {
|
|
|
|
|
let algo = decodeURI(query.algo as string)
|
|
|
|
@ -139,7 +192,8 @@ export const post: APIRoute = async ({ request }) => {
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
try {
|
|
|
|
|
const tmp = evaluate(algo, params)
|
|
|
|
|
logger.log('Evaluating Alogrithm')
|
|
|
|
|
const tmp = evaluate(algo, gcodeParams)
|
|
|
|
|
if (typeof tmp === 'number') {
|
|
|
|
|
price = tmp.toFixed(2)
|
|
|
|
|
} else {
|
|
|
|
@ -150,11 +204,11 @@ export const post: APIRoute = async ({ request }) => {
|
|
|
|
|
details: 'It seems the algorithm resolution failed',
|
|
|
|
|
algorithm: algo,
|
|
|
|
|
algorithmError: 'Algorithm return a Unit',
|
|
|
|
|
parameters: params
|
|
|
|
|
parameters: gcodeParams
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.dir(e)
|
|
|
|
|
logger.dir(e)
|
|
|
|
|
return buildRFC7807({
|
|
|
|
|
type: '/algorithm-error',
|
|
|
|
|
status: 500,
|
|
|
|
@ -162,11 +216,11 @@ export const post: APIRoute = async ({ request }) => {
|
|
|
|
|
details: 'It seems the algorithm resolution failed',
|
|
|
|
|
algorithm: algo,
|
|
|
|
|
algorithmError: e,
|
|
|
|
|
parameters: params
|
|
|
|
|
parameters: gcodeParams
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
console.log('request successfull :)', file)
|
|
|
|
|
logger.log('request successfull :)')
|
|
|
|
|
return {
|
|
|
|
|
status: 200,
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|