feat: nearly complete
Signed-off-by: Avior <github@avior.me>
This commit is contained in:
parent
9249edf1c4
commit
dd330b7da9
@ -1,2 +0,0 @@
|
||||
files/*
|
||||
node_modules/
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
files/
|
||||
files/
|
||||
dist/
|
Binary file not shown.
30
Dockerfile
30
Dockerfile
@ -26,16 +26,23 @@ RUN npm run build
|
||||
##############
|
||||
FROM node:latest as PROD_IMAGE
|
||||
|
||||
# ADD https://github.com/prusa3d/PrusaSlicer/releases/download/version_2.5.2/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 ./
|
||||
# RUN tar -xvf PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 -C /opt
|
||||
# ENV PATH /opt/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201/bin:$PATH
|
||||
# Download & Install Slic3r
|
||||
# RUN apt-get update \
|
||||
# && apt-get install -y --no-install-recommends \
|
||||
# slic3r \
|
||||
# && rm -rf /var/lib/apt/lists/*
|
||||
# ENV SLICER_PATH slic3r
|
||||
|
||||
# Install deps
|
||||
# Download & install PrusaSlicer
|
||||
ADD https://github.com/prusa3d/PrusaSlicer/releases/download/version_2.5.2/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 ./
|
||||
RUN tar -xvf PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 -C /opt
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
prusa-slicer \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
prusa-slicer \
|
||||
&& apt-get remove prusa-slicer -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
ENV PATH /opt/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201/bin:$PATH
|
||||
ENV SLICER_PATH prusa-slicer
|
||||
|
||||
# run as non root user
|
||||
USER node
|
||||
@ -43,14 +50,15 @@ USER node
|
||||
# go to work folder
|
||||
WORKDIR /home/node
|
||||
|
||||
ADD --chown=node:node https://github.com/bambulab/BambuStudio/releases/download/v01.05.00.61/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage ./
|
||||
ENV SLICER_PATH /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
RUN chmod +x /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
# download & Install Bambu Studio
|
||||
# ADD --chown=node:node https://github.com/bambulab/BambuStudio/releases/download/v01.05.00.61/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage ./
|
||||
# ENV SLICER_PATH /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
# RUN chmod +x /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
|
||||
# copy from build image
|
||||
COPY --chown=node:node --from=BUILD_IMAGE /home/node/node_modules ./node_modules
|
||||
COPY --chown=node:node --from=BUILD_IMAGE /home/node/configs ./configs
|
||||
COPY --chown=node:node --from=BUILD_IMAGE /home/node/src ./src
|
||||
COPY --chown=node:node --from=BUILD_IMAGE /home/node/dist ./dist
|
||||
COPY --chown=node:node --from=BUILD_IMAGE /home/node/package.json /home/node/.env* ./
|
||||
|
||||
# Expose port
|
||||
|
18
README.md
Normal file
18
README.md
Normal file
@ -0,0 +1,18 @@
|
||||
example command: `http://127.0.0.1:3000/?algo=total_filament_used_g%20*%200.6%20%2B%20(estimated_printing_time_seconds%20%2F%2060%20%2F%2060%20*%2040)`
|
||||
|
||||
example decoded algo: `total_filament_used_g * 0.6 + (estimated_printing_time_seconds / 60 / 60 * 40)`, it will result in `price` being the response to the algorithm
|
||||
|
||||
the .stl file MUST be the only thing in the body of the request
|
||||
|
||||
Query parameters:
|
||||
|
||||
- `algo`: the pricing algorithm
|
||||
- `config`: use another config than the default one
|
||||
|
||||
other queries:
|
||||
- run `prusa-slicer --help-fff` in the terminal or look at the params in the response to get the full list
|
||||
|
||||
examples:
|
||||
- `layer_height` = `0.05`
|
||||
- `scale` = `200%`
|
||||
- `fill_density` = `90%`
|
293
configs/dzeio-printer.ini
Normal file
293
configs/dzeio-printer.ini
Normal file
@ -0,0 +1,293 @@
|
||||
# generated by PrusaSlicer 2.5.2 on 2023-04-04 at 23:17:15 UTC
|
||||
avoid_crossing_perimeters = 0
|
||||
avoid_crossing_perimeters_max_detour = 0
|
||||
bed_custom_model =
|
||||
bed_custom_texture =
|
||||
bed_shape = 0x0,270x0,270x270,0x270
|
||||
bed_temperature = 60
|
||||
before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0
|
||||
between_objects_gcode =
|
||||
bottom_fill_pattern = monotonic
|
||||
bottom_solid_layers = 4
|
||||
bottom_solid_min_thickness = 1.2
|
||||
bridge_acceleration = 500
|
||||
bridge_angle = 0
|
||||
bridge_fan_speed = 100
|
||||
bridge_flow_ratio = 0.8
|
||||
bridge_speed = 80
|
||||
brim_separation = 0
|
||||
brim_type = outer_only
|
||||
brim_width = 0
|
||||
clip_multipart_objects = 1
|
||||
color_change_gcode = M600
|
||||
colorprint_heights =
|
||||
compatible_printers_condition_cummulative = "printer_model=~/(X1|Genius).*/ and nozzle_diameter[0]==0.4";
|
||||
complete_objects = 0
|
||||
cooling = 1
|
||||
cooling_tube_length = 5
|
||||
cooling_tube_retraction = 91.5
|
||||
default_acceleration = 500
|
||||
default_filament_profile = "Generic PLA @Artillery"
|
||||
default_print_profile = 0.20mm NORMAL @Artillery
|
||||
deretract_speed = 0
|
||||
disable_fan_first_layers = 2
|
||||
dont_support_bridges = 0
|
||||
draft_shield = disabled
|
||||
duplicate_distance = 6
|
||||
elefant_foot_compensation = 0
|
||||
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
|
||||
end_gcode = G91 ;Relative positionning\nG1 E-1 F2700 ;Retract a bit\nG1 E-1 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\nG1 X0 Y150 ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\nM84 X Y E ;Disable all steppers but Z
|
||||
ensure_vertical_shell_thickness = 1
|
||||
external_perimeter_acceleration = 0
|
||||
external_perimeter_extrusion_width = 0.4
|
||||
external_perimeter_speed = 40
|
||||
external_perimeters_first = 0
|
||||
extra_loading_move = -2
|
||||
extra_perimeters = 0
|
||||
extruder_clearance_height = 25
|
||||
extruder_clearance_radius = 45
|
||||
extruder_colour = #FFFF00
|
||||
extruder_offset = 0x0
|
||||
extrusion_axis = E
|
||||
extrusion_multiplier = 1
|
||||
extrusion_width = 0.4
|
||||
fan_always_on = 1
|
||||
fan_below_layer_time = 60
|
||||
filament_colour = #29B2B2
|
||||
filament_cooling_final_speed = 3.4
|
||||
filament_cooling_initial_speed = 2.2
|
||||
filament_cooling_moves = 4
|
||||
filament_cost = 19.95
|
||||
filament_density = 1.24
|
||||
filament_deretract_speed = nil
|
||||
filament_diameter = 1.75
|
||||
filament_load_time = 0
|
||||
filament_loading_speed = 28
|
||||
filament_loading_speed_start = 3
|
||||
filament_max_volumetric_speed = 0
|
||||
filament_minimal_purge_on_wipe_tower = 15
|
||||
filament_notes = ""
|
||||
filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6"
|
||||
filament_retract_before_travel = nil
|
||||
filament_retract_before_wipe = nil
|
||||
filament_retract_layer_change = nil
|
||||
filament_retract_length = nil
|
||||
filament_retract_lift = nil
|
||||
filament_retract_lift_above = nil
|
||||
filament_retract_lift_below = nil
|
||||
filament_retract_restart_extra = nil
|
||||
filament_retract_speed = nil
|
||||
filament_settings_id = "PLA Francofil"
|
||||
filament_soluble = 0
|
||||
filament_spool_weight = 1000
|
||||
filament_toolchange_delay = 0
|
||||
filament_type = PLA
|
||||
filament_unload_time = 0
|
||||
filament_unloading_speed = 90
|
||||
filament_unloading_speed_start = 100
|
||||
filament_vendor = (Unknown)
|
||||
filament_wipe = nil
|
||||
fill_angle = 45
|
||||
fill_density = 20%
|
||||
fill_pattern = triangles
|
||||
first_layer_acceleration = 500
|
||||
first_layer_acceleration_over_raft = 0
|
||||
first_layer_bed_temperature = 60
|
||||
first_layer_extrusion_width = 0.4
|
||||
first_layer_height = 0.2
|
||||
first_layer_speed = 20
|
||||
first_layer_speed_over_raft = 30
|
||||
first_layer_temperature = 215
|
||||
full_fan_speed_layer = 2
|
||||
fuzzy_skin = none
|
||||
fuzzy_skin_point_dist = 0.8
|
||||
fuzzy_skin_thickness = 0.3
|
||||
gap_fill_enabled = 1
|
||||
gap_fill_speed = 30
|
||||
gcode_comments = 1
|
||||
gcode_flavor = marlin
|
||||
gcode_label_objects = 1
|
||||
gcode_resolution = 0.0125
|
||||
gcode_substitutions =
|
||||
high_current_on_filament_swap = 0
|
||||
host_type = octoprint
|
||||
infill_acceleration = 500
|
||||
infill_anchor = 600%
|
||||
infill_anchor_max = 50
|
||||
infill_every_layers = 1
|
||||
infill_extruder = 1
|
||||
infill_extrusion_width = 0.45
|
||||
infill_first = 0
|
||||
infill_only_where_needed = 0
|
||||
infill_overlap = 25%
|
||||
infill_speed = 60
|
||||
inherits_cummulative = "0.16mm OPTIMAL @Artillery";;"Sidewinder X1 BL-TOUCH"
|
||||
interface_shells = 0
|
||||
ironing = 0
|
||||
ironing_flowrate = 15%
|
||||
ironing_spacing = 0.1
|
||||
ironing_speed = 15
|
||||
ironing_type = top
|
||||
layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z]
|
||||
layer_height = 0.16
|
||||
machine_limits_usage = time_estimate_only
|
||||
machine_max_acceleration_e = 1500,5000
|
||||
machine_max_acceleration_extruding = 2000,1250
|
||||
machine_max_acceleration_retracting = 2000,1250
|
||||
machine_max_acceleration_travel = 1500,1250
|
||||
machine_max_acceleration_x = 500,960
|
||||
machine_max_acceleration_y = 500,960
|
||||
machine_max_acceleration_z = 500,1000
|
||||
machine_max_feedrate_e = 120,120
|
||||
machine_max_feedrate_x = 150,100
|
||||
machine_max_feedrate_y = 150,100
|
||||
machine_max_feedrate_z = 12,12
|
||||
machine_max_jerk_e = 1.5,1.5
|
||||
machine_max_jerk_x = 8,8
|
||||
machine_max_jerk_y = 8,8
|
||||
machine_max_jerk_z = 0.4,0.4
|
||||
machine_min_extruding_rate = 0,0
|
||||
machine_min_travel_rate = 0,0
|
||||
max_fan_speed = 100
|
||||
max_layer_height = 0.3
|
||||
max_print_height = 400
|
||||
max_print_speed = 150
|
||||
max_volumetric_extrusion_rate_slope_negative = 0
|
||||
max_volumetric_extrusion_rate_slope_positive = 0
|
||||
max_volumetric_speed = 0
|
||||
min_bead_width = 85%
|
||||
min_fan_speed = 80
|
||||
min_feature_size = 25%
|
||||
min_layer_height = 0.15
|
||||
min_print_speed = 15
|
||||
min_skirt_length = 4
|
||||
mmu_segmented_region_max_width = 0
|
||||
notes =
|
||||
nozzle_diameter = 0.4
|
||||
only_retract_when_crossing_perimeters = 0
|
||||
ooze_prevention = 0
|
||||
output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode
|
||||
overhangs = 1
|
||||
parking_pos_retraction = 92
|
||||
pause_print_gcode =
|
||||
perimeter_acceleration = 500
|
||||
perimeter_extruder = 1
|
||||
perimeter_extrusion_width = 0
|
||||
perimeter_generator = arachne
|
||||
perimeter_speed = 40
|
||||
perimeters = 4
|
||||
physical_printer_settings_id =
|
||||
post_process =
|
||||
print_host =
|
||||
print_settings_id = PLA 1.75mm
|
||||
printer_model = X1
|
||||
printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Artillery\nPRINTER_MODEL_X1
|
||||
printer_settings_id = Sidewinder X1 nono
|
||||
printer_technology = FFF
|
||||
printer_variant = 0.4
|
||||
printer_vendor =
|
||||
printhost_apikey =
|
||||
printhost_cafile =
|
||||
raft_contact_distance = 0.1
|
||||
raft_expansion = 1.5
|
||||
raft_first_layer_density = 90%
|
||||
raft_first_layer_expansion = 3
|
||||
raft_layers = 0
|
||||
remaining_times = 0
|
||||
resolution = 0
|
||||
retract_before_travel = 1
|
||||
retract_before_wipe = 80%
|
||||
retract_layer_change = 0
|
||||
retract_length = 0.8
|
||||
retract_length_toolchange = 4
|
||||
retract_lift = 0.2
|
||||
retract_lift_above = 0
|
||||
retract_lift_below = 380
|
||||
retract_restart_extra = 0
|
||||
retract_restart_extra_toolchange = 0
|
||||
retract_speed = 30
|
||||
seam_position = aligned
|
||||
silent_mode = 0
|
||||
single_extruder_multi_material = 0
|
||||
single_extruder_multi_material_priming = 0
|
||||
skirt_distance = 4
|
||||
skirt_height = 1
|
||||
skirts = 3
|
||||
slice_closing_radius = 0.049
|
||||
slicing_mode = regular
|
||||
slowdown_below_layer_time = 2
|
||||
small_perimeter_speed = 30
|
||||
solid_infill_acceleration = 0
|
||||
solid_infill_below_area = 0
|
||||
solid_infill_every_layers = 0
|
||||
solid_infill_extruder = 1
|
||||
solid_infill_extrusion_width = 0.45
|
||||
solid_infill_speed = 45
|
||||
spiral_vase = 0
|
||||
standby_temperature_delta = -5
|
||||
start_filament_gcode = "; Filament gcode\n"
|
||||
start_gcode = G28 ; home all axes\nG29;\nM420 S1; \nG92 E0 ;\nG1 Z3 F3000 ; move z up little to prevent scratching of surface\nG1 X20 Y5 Z0.3 F5000.0 ; move to start-line position\nG1 Z0.3 F1000 ; print height\nG1 X200 Y5 F1500.0 E15 ; draw 1st line\nG1 X200 Y5.3 Z0.3 F5000.0 ; move to side a little\nG1 X5.3 Y5.3 Z0.3 F1500.0 E30 ; draw 2nd line\nG1 Z3 F3000 ; move z up little to prevent scratching of surface\nG92 E0 ; Reset Extruder\n ; Reset Extruder\n
|
||||
support_material = 1
|
||||
support_material_angle = 0
|
||||
support_material_auto = 1
|
||||
support_material_bottom_contact_distance = 0
|
||||
support_material_bottom_interface_layers = -1
|
||||
support_material_buildplate_only = 1
|
||||
support_material_closing_radius = 2
|
||||
support_material_contact_distance = 0.3
|
||||
support_material_enforce_layers = 0
|
||||
support_material_extruder = 0
|
||||
support_material_extrusion_width = 0.35
|
||||
support_material_interface_contact_loops = 0
|
||||
support_material_interface_extruder = 0
|
||||
support_material_interface_layers = 2
|
||||
support_material_interface_pattern = rectilinear
|
||||
support_material_interface_spacing = 0.2
|
||||
support_material_interface_speed = 100%
|
||||
support_material_pattern = honeycomb
|
||||
support_material_spacing = 2
|
||||
support_material_speed = 60
|
||||
support_material_style = snug
|
||||
support_material_synchronize_layers = 0
|
||||
support_material_threshold = 30
|
||||
support_material_with_sheath = 1
|
||||
support_material_xy_spacing = 50%
|
||||
temperature = 210
|
||||
template_custom_gcode =
|
||||
thick_bridges = 0
|
||||
thin_walls = 1
|
||||
threads = 8
|
||||
thumbnails =
|
||||
thumbnails_format = PNG
|
||||
toolchange_gcode =
|
||||
top_fill_pattern = monotonic
|
||||
top_infill_extrusion_width = 0.4
|
||||
top_solid_infill_acceleration = 0
|
||||
top_solid_infill_speed = 40
|
||||
top_solid_layers = 4
|
||||
top_solid_min_thickness = 0
|
||||
travel_speed = 80
|
||||
travel_speed_z = 0
|
||||
use_firmware_retraction = 0
|
||||
use_relative_e_distances = 1
|
||||
use_volumetric_e = 0
|
||||
variable_layer_height = 1
|
||||
wall_distribution_count = 1
|
||||
wall_transition_angle = 10
|
||||
wall_transition_filter_deviation = 25%
|
||||
wall_transition_length = 100%
|
||||
wipe = 1
|
||||
wipe_into_infill = 0
|
||||
wipe_into_objects = 0
|
||||
wipe_tower = 0
|
||||
wipe_tower_bridging = 10
|
||||
wipe_tower_brim_width = 2
|
||||
wipe_tower_no_sparse_layers = 0
|
||||
wipe_tower_rotation_angle = 0
|
||||
wipe_tower_width = 60
|
||||
wipe_tower_x = 170
|
||||
wipe_tower_y = 125
|
||||
wiping_volumes_extruders = 70,70
|
||||
wiping_volumes_matrix = 0
|
||||
xy_size_compensation = 0
|
||||
z_offset = 0
|
4859
package-lock.json
generated
4859
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -1,10 +1,18 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@dzeio/object-util": "^1.5.0",
|
||||
"express": "^4.18.2"
|
||||
"express": "^4.18.2",
|
||||
"mathjs": "^11.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/main.js",
|
||||
"build": "true"
|
||||
"dev": "ts-node-dev src/main.ts",
|
||||
"start": "node dist/main.js",
|
||||
"build": "tsc --project tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dzeio/config": "^1.1.12",
|
||||
"@types/express": "^4.17.17",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.0.3"
|
||||
}
|
||||
}
|
||||
|
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`);
|
||||
})
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./node_modules/@dzeio/config/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user