mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-21 07:52:32 +00:00
UltiMaker S5 Profile + Local API Support (#14062)
* Add Ultimaker S5 Profile - WIP * Add Ultimaker physical printer base * Ultimaker API WIP for base update. * Fix undefined reference error. * Add UMS5 profile, add placeholders for testAuth stuff. * Fix non-const func definitions, make func names align with style guide. * Localization stuff? IDK if this does anything or is required. * Add cover image * Auth code cleanup, add UI button for auth cred generation, implement various auth checks and tests. * Fix auth stuff * Clean up code * Get upload code sort-of working, fix typo * Update printer settings and start/end gcodes * Add makeGriffinCompatible preprocessor script to prevent machine crash, update machine profile. * Fix buildplate size, fix time missing bug, add S5 buildplate model * Correct capitalization to UltiMaker * Fix display bug, fix capitalization bug * Implement credential generation button and logic * Fix generate auth creds button, add todos, fix capitalization. * Actually fix generate auth credentials. * Fix generate auth creds message. * Update UM S5 machine limits * Revert accidental commit. * Fix postprocessor, clean up code. * Update presets for multi-extruder printing. * Add Single Extruder and Fast profiles. * Code cleanup * Register and validate the UltiMaker S5 presets * Show the Generate API Key button only for UltiMaker print hosts The PR added it to the Physical Printer dialog for every host type, where pressing it runs an ordinary connection test and reports "API Key created". Gate it on the selected host type instead. * Drop unused lambda captures in the UltiMaker host code clang promotes -Wunused-lambda-capture to an error under the project's -Werror, so the file failed to build on macOS; the five callbacks do not touch this. * Fix the Windows build of the UltiMaker print host --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
co-authored by
SoftFever
parent
505a46b280
commit
378635b568
@@ -19225,6 +19225,12 @@ msgstr ""
|
||||
msgid "Could not connect to Duet"
|
||||
msgstr ""
|
||||
|
||||
msgid "Connection to Ultimaker is working correctly."
|
||||
msgstr ""
|
||||
|
||||
msgid "Could not connect to Ultimaker"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unknown error occurred"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -250,6 +250,7 @@ src/slic3r/GUI/TroubleshootDialog.cpp
|
||||
src/slic3r/Utils/3DPrinterOS.cpp
|
||||
src/slic3r/Utils/AstroBox.cpp
|
||||
src/slic3r/Utils/Duet.cpp
|
||||
src/slic3r/Utils/UltiMaker.cpp
|
||||
src/slic3r/Utils/FlashAir.cpp
|
||||
src/slic3r/Utils/MKS.cpp
|
||||
src/slic3r/Utils/Moonraker.cpp
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
{
|
||||
"name": "UltiMaker",
|
||||
"url": "",
|
||||
"version": "02.04.00.02",
|
||||
"version": "02.04.00.03",
|
||||
"force_update": "0",
|
||||
"description": "UltiMaker configurations",
|
||||
"machine_model_list": [
|
||||
{
|
||||
"name": "UltiMaker 2",
|
||||
"sub_path": "machine/UltiMaker 2.json"
|
||||
},
|
||||
{
|
||||
"name": "UltiMaker S5",
|
||||
"sub_path": "machine/UltiMaker S5.json"
|
||||
}
|
||||
],
|
||||
"process_list": [
|
||||
@@ -23,9 +27,17 @@
|
||||
"name": "0.18mm Standard @UltiMaker 2",
|
||||
"sub_path": "process/0.18mm Standard @UltiMaker 2.json"
|
||||
},
|
||||
{
|
||||
"name": "0.18mm Standard @UltiMaker S5",
|
||||
"sub_path": "process/0.18mm Standard @UltiMaker S5.json"
|
||||
},
|
||||
{
|
||||
"name": "0.25mm Draft @UltiMaker 2",
|
||||
"sub_path": "process/0.25mm Darft @UltiMaker 2.json"
|
||||
},
|
||||
{
|
||||
"name": "0.2mm Fast @UltiMaker S5",
|
||||
"sub_path": "process/0.2mm Fast @UltiMaker S5.json"
|
||||
}
|
||||
],
|
||||
"filament_list": [],
|
||||
@@ -37,6 +49,14 @@
|
||||
{
|
||||
"name": "UltiMaker 2 0.4 nozzle",
|
||||
"sub_path": "machine/UltiMaker 2 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "UltiMaker S5 0.4 nozzle",
|
||||
"sub_path": "machine/UltiMaker S5 0.4 nozzle.json"
|
||||
},
|
||||
{
|
||||
"name": "UltiMaker S5 0.4 nozzle - Single Extruder",
|
||||
"sub_path": "machine/UltiMaker S5 0.4 nozzle Single Extruder.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "UltiMaker S5 0.4 nozzle - Single Extruder",
|
||||
"inherits": "UltiMaker S5 0.4 nozzle",
|
||||
"from": "system",
|
||||
"setting_id": "7BKZlwffTrzDsUgj",
|
||||
"instantiation": "true",
|
||||
"printer_model": "UltiMaker S5",
|
||||
"default_print_profile": "0.18mm Standard @UltiMaker S5",
|
||||
"default_nozzle_volume_type": [
|
||||
"Standard"
|
||||
],
|
||||
"extruder_colour": [
|
||||
"#FCE94F"
|
||||
],
|
||||
"extruder_offset": [
|
||||
"0x0"
|
||||
],
|
||||
"extruder_type": [
|
||||
"Direct Drive"
|
||||
],
|
||||
"machine_start_gcode": "; NOTE: Do NOT remove the following header. It is necessary for the UM printer to not crash.\n;START_OF_HEADER\n;HEADER_VERSION:0.1\n;FLAVOR:Griffin\n;GENERATOR.NAME:OrcaSlicer\n;GENERATOR.VERSION:5.11.0\n;GENERATOR.BUILD_DATE:2016-06-23\n;TARGET_MACHINE.NAME:[printer_model]\n;EXTRUDER_TRAIN.0.INITIAL_TEMPERATURE:{first_layer_temperature[0]}\n;EXTRUDER_TRAIN.0.MATERIAL.VOLUME_USED:{extruded_volume[0]}\n;EXTRUDER_TRAIN.0.MATERIAL.GUID:506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9\n;EXTRUDER_TRAIN.0.NOZZLE.DIAMETER:{nozzle_diameter[0]}\n;EXTRUDER_TRAIN.0.NOZZLE.NAME:AA {nozzle_diameter[0]}\n;BUILD_PLATE.INITIAL_TEMPERATURE:[bed_temperature_initial_layer_single]\n;BUILD_VOLUME.TEMPERATURE:28\n;PRINT.TIME:2\n;PRINT.SIZE.MIN.X:{first_layer_print_min[0]}\n;PRINT.SIZE.MIN.Y:{first_layer_print_min[1]}\n;PRINT.SIZE.MIN.Z:{first_layer_height}\n;PRINT.SIZE.MAX.X:{first_layer_print_max[0]}\n;PRINT.SIZE.MAX.Y:{first_layer_print_max[1]}\n;PRINT.SIZE.MAX.Z:{int(total_layer_count * first_layer_height)}\n;END_OF_HEADER\nT[initial_extruder]\n\nM82 ;absolute extrusion mode\nG92 E0\nM190 S[bed_temperature_initial_layer_single] ; Wait for bed temp\nM104 T[initial_extruder] S[first_layer_temperature[initial_extruder]] ; Heat extruder\nM109 T[initial_extruder] S[first_layer_temperature[initial_extruder]] ; Wait for extruder temp\n\n; Set the settings we removed in the post-processing (orcaslicer generated header).\nM73 P0 R0 ;TODO change the R value into the total minutes of print\nM201 X[machine_max_acceleration_x] Y[machine_max_acceleration_y] Z[machine_max_acceleration_z] E[machine_max_acceleration_e] ; accel\nM203 X[machine_max_speed_x] Y[machine_max_speed_y] Z[machine_max_speed_z] E[machine_max_speed_e] ; max speed\nM204 S[machine_max_acceleration_extruding] ; starting accel\nM204 P[machine_max_acceleration_extruding] R[machine_max_acceleration_retracting] T[machine_max_acceleration_travel]\nM205 X[machine_max_jerk_x] Y[machine_max_jerk_y] Z[machine_max_jerk_z] E[machine_max_jerk_e] ; jerk limits\n\nM107; start with the fan off\n\n; Prime blob for left extruder\nG0 F9000 X4 Y6 Z2\nG280 ; g280 is UM-specific\nG1 F2700 E-6.5 ;retract\n\nM82; set extruder to absolute mode\n\n; End of the start gcode sequence.\n;LAYER_COUNT:[total_layer_count]",
|
||||
"max_layer_height": [
|
||||
"0.4"
|
||||
],
|
||||
"min_layer_height": [
|
||||
"0.06"
|
||||
],
|
||||
"nozzle_diameter": [
|
||||
"0.4"
|
||||
],
|
||||
"printer_extruder_id": [
|
||||
"1"
|
||||
],
|
||||
"printer_extruder_variant": [
|
||||
"Direct Drive Standard"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
{
|
||||
"type": "machine",
|
||||
"name": "UltiMaker S5 0.4 nozzle",
|
||||
"inherits": "fdm_machine_common",
|
||||
"from": "system",
|
||||
"setting_id": "hmkJtw7J1KXbiYfj",
|
||||
"instantiation": "true",
|
||||
"printer_model": "UltiMaker S5",
|
||||
"default_print_profile": "0.18mm Standard @UltiMaker S5",
|
||||
"nozzle_diameter": [
|
||||
"0.4",
|
||||
"0.4"
|
||||
],
|
||||
"printable_area": [
|
||||
"0x0",
|
||||
"330x0",
|
||||
"330x240",
|
||||
"0x240"
|
||||
],
|
||||
"printable_height": "300",
|
||||
"extruder_clearance_height_to_rod": "60",
|
||||
"extruder_clearance_radius": "61",
|
||||
"nozzle_type": "brass",
|
||||
"auxiliary_fan": "0",
|
||||
"use_relative_e_distances": "0",
|
||||
"machine_max_acceleration_extruding": [
|
||||
"3500",
|
||||
"3500"
|
||||
],
|
||||
"machine_max_acceleration_retracting": [
|
||||
"3500",
|
||||
"3500"
|
||||
],
|
||||
"machine_max_acceleration_travel": [
|
||||
"3500",
|
||||
"3500"
|
||||
],
|
||||
"machine_max_acceleration_x": [
|
||||
"3500",
|
||||
"3500"
|
||||
],
|
||||
"machine_max_acceleration_y": [
|
||||
"3500",
|
||||
"3500"
|
||||
],
|
||||
"machine_max_acceleration_z": [
|
||||
"500",
|
||||
"500"
|
||||
],
|
||||
"machine_max_speed_e": [
|
||||
"120",
|
||||
"120"
|
||||
],
|
||||
"machine_max_speed_x": [
|
||||
"500",
|
||||
"500"
|
||||
],
|
||||
"machine_max_speed_y": [
|
||||
"500",
|
||||
"500"
|
||||
],
|
||||
"machine_max_speed_z": [
|
||||
"12",
|
||||
"12"
|
||||
],
|
||||
"machine_max_jerk_e": [
|
||||
"2.5",
|
||||
"2.5"
|
||||
],
|
||||
"machine_max_jerk_x": [
|
||||
"20",
|
||||
"20"
|
||||
],
|
||||
"machine_max_jerk_y": [
|
||||
"20",
|
||||
"20"
|
||||
],
|
||||
"machine_max_jerk_z": [
|
||||
"0.4",
|
||||
"0.4"
|
||||
],
|
||||
"max_layer_height": [
|
||||
"0.4",
|
||||
"0.4"
|
||||
],
|
||||
"min_layer_height": [
|
||||
"0.06",
|
||||
"0.06"
|
||||
],
|
||||
"printer_settings_id": "Qidi",
|
||||
"retraction_minimum_travel": [
|
||||
"5"
|
||||
],
|
||||
"retract_before_wipe": [
|
||||
"70%"
|
||||
],
|
||||
"retraction_length": [
|
||||
"4.5"
|
||||
],
|
||||
"retract_length_toolchange": [
|
||||
"11"
|
||||
],
|
||||
"retraction_speed": [
|
||||
"45"
|
||||
],
|
||||
"deretraction_speed": [
|
||||
"0"
|
||||
],
|
||||
"wipe_distance": [
|
||||
"0.2"
|
||||
],
|
||||
"single_extruder_multi_material": "0",
|
||||
"change_filament_gcode": "; Set temps to avoid oozing\nM104 T{previous_extruder} S{old_filament_temp - 5}\nM104 T{next_extruder} S{new_filament_temp}\n; Change to next extruder\nT{next_extruder}",
|
||||
"machine_pause_gcode": "M0",
|
||||
"default_filament_profile": [
|
||||
"Generic PLA @System"
|
||||
],
|
||||
"z_hop": [
|
||||
"1"
|
||||
],
|
||||
"z_hop_types": [
|
||||
"Normal Lift"
|
||||
],
|
||||
"extruder_type": [
|
||||
"Direct Drive",
|
||||
"Direct Drive"
|
||||
],
|
||||
"extruder_offset": [
|
||||
"0x0",
|
||||
"0x22"
|
||||
],
|
||||
"extra_loading_move": "0",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\n",
|
||||
"fan_speedup_time": "0.5",
|
||||
"machine_start_gcode": "; NOTE: Do NOT remove the following header. It is necessary for the UM printer to not crash.\n;START_OF_HEADER\n;HEADER_VERSION:0.1\n;FLAVOR:Griffin\n;GENERATOR.NAME:OrcaSlicer\n;GENERATOR.VERSION:5.11.0\n;GENERATOR.BUILD_DATE:2016-06-23\n;TARGET_MACHINE.NAME:[printer_model]\n{if is_extruder_used[0]};EXTRUDER_TRAIN.0.INITIAL_TEMPERATURE:{first_layer_temperature[0]}\n;EXTRUDER_TRAIN.0.MATERIAL.VOLUME_USED:{extruded_volume[0]}\n;EXTRUDER_TRAIN.0.MATERIAL.GUID:506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9\n;EXTRUDER_TRAIN.0.NOZZLE.DIAMETER:{nozzle_diameter[0]}\n;EXTRUDER_TRAIN.0.NOZZLE.NAME:AA {nozzle_diameter[0]}{endif}\n{if is_extruder_used[1]};EXTRUDER_TRAIN.1.INITIAL_TEMPERATURE:{first_layer_temperature[1]}\n;EXTRUDER_TRAIN.1.MATERIAL.VOLUME_USED:{extruded_volume[1]}\n;EXTRUDER_TRAIN.1.MATERIAL.GUID:506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9\n;EXTRUDER_TRAIN.1.NOZZLE.DIAMETER:{nozzle_diameter[1]}\n;EXTRUDER_TRAIN.1.NOZZLE.NAME:AA {nozzle_diameter[1]}{endif}\n;BUILD_PLATE.INITIAL_TEMPERATURE:[bed_temperature_initial_layer_single]\n;BUILD_VOLUME.TEMPERATURE:28\n;PRINT.TIME:2\n;PRINT.SIZE.MIN.X:{first_layer_print_min[0]}\n;PRINT.SIZE.MIN.Y:{first_layer_print_min[1]}\n;PRINT.SIZE.MIN.Z:{first_layer_height}\n;PRINT.SIZE.MAX.X:{first_layer_print_max[0]}\n;PRINT.SIZE.MAX.Y:{first_layer_print_max[1]}\n;PRINT.SIZE.MAX.Z:{int(total_layer_count * first_layer_height)}\n;END_OF_HEADER\nT[initial_extruder]\n\nM82 ;absolute extrusion mode\nG92 E0\nM190 S[bed_temperature_initial_layer_single] ; Wait for bed temp\nM104 T[initial_extruder] S[first_layer_temperature[initial_extruder]] ; Heat extruder\nM109 T[initial_extruder] S[first_layer_temperature[initial_extruder]] ; Wait for extruder temp\n\n\n; Set the settings we removed in the post-processing (orcaslicer generated header).\nM73 P0 R0 ;TODO change the R value into the total minutes of print\nM201 X[machine_max_acceleration_x] Y[machine_max_acceleration_y] Z[machine_max_acceleration_z] E[machine_max_acceleration_e] ; accel\nM203 X[machine_max_speed_x] Y[machine_max_speed_y] Z[machine_max_speed_z] E[machine_max_speed_e] ; max speed\nM204 S[machine_max_acceleration_extruding] ; starting accel\nM204 P[machine_max_acceleration_extruding] R[machine_max_acceleration_retracting] T[machine_max_acceleration_travel]\nM205 X[machine_max_jerk_x] Y[machine_max_jerk_y] Z[machine_max_jerk_z] E[machine_max_jerk_e] ; jerk limits\n\nM107; start with the fan off\n\n{if is_extruder_used[0]}T0\n; Prime blob for left extruder\nG0 F9000 X4 Y6 Z2\nG280 ; g280 is UM-specific\nG1 F2700 E-6.5 ;retract{endif}\n\n{if is_extruder_used[1]}T1\n; Prime blob for right extruder\nG0 F9000 X-4 Y6 Z2\nG280 ; g280 is UM-specific\nG1 F2700 E-6.5 ;retract\n{if initial_extruder == 0}T0{endif}{endif}\n\n\n\nM82; set extruder to absolute mode\n\n; End of the start gcode sequence.\n;LAYER_COUNT:[total_layer_count]",
|
||||
"machine_end_gcode": "; Move toolhead out of the way\nG28 X Y\n\nM204 S3000; reset accel\nM107; fan off\n\n; heaters off\nM104 S0\nM104 T0 S0\nM104 T1 S0\nM140 S0\n\n; disable motors\nM84\n\n\nM82 ;absolute extrusion mode\n\n;End of Gcode\n",
|
||||
"machine_tool_change_time": "1.5",
|
||||
"scan_first_layer": "0"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "machine_model",
|
||||
"name": "UltiMaker S5",
|
||||
"model_id": "UltiMaker-S5",
|
||||
"nozzle_diameter": "0.4",
|
||||
"machine_tech": "FFF",
|
||||
"family": "UltiMaker",
|
||||
"bed_model": "ultimaker_s5_buildplate_model.stl",
|
||||
"bed_texture": "ultimaker_2_buildplate_texture.png",
|
||||
"hotend_model": "ultimaker_hotend.stl",
|
||||
"default_materials": "Generic ABS @System;Generic PETG @System;Generic PLA @System"
|
||||
}
|
||||
@@ -112,5 +112,6 @@
|
||||
"default_print_profile": "",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
|
||||
"machine_start_gcode": "",
|
||||
"machine_end_gcode": ""
|
||||
"machine_end_gcode": "",
|
||||
"thumbnails": ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "0.18mm Standard @UltiMaker S5",
|
||||
"inherits": "fdm_process_common",
|
||||
"from": "system",
|
||||
"setting_id": "UAhqUBDi1CzMB2Vg",
|
||||
"instantiation": "true",
|
||||
"ooze_prevention": "1",
|
||||
"preheat_time": "20",
|
||||
"reduce_crossing_wall": "0",
|
||||
"layer_height": "0.18",
|
||||
"max_travel_detour_distance": "0",
|
||||
"bottom_surface_pattern": "monotonic",
|
||||
"bottom_shell_layers": "4",
|
||||
"bottom_shell_thickness": "0.8",
|
||||
"bridge_flow": "1",
|
||||
"bridge_speed": "60",
|
||||
"brim_width": "0",
|
||||
"brim_object_gap": "0",
|
||||
"compatible_printers_condition": "",
|
||||
"print_sequence": "by layer",
|
||||
"default_acceleration": "0",
|
||||
"top_surface_acceleration": "0",
|
||||
"bridge_no_support": "0",
|
||||
"draft_shield": "disabled",
|
||||
"elefant_foot_compensation": "0",
|
||||
"enable_arc_fitting": "0",
|
||||
"outer_wall_line_width": "0.4",
|
||||
"wall_infill_order": "inner wall/outer wall/infill",
|
||||
"line_width": "0.45",
|
||||
"infill_direction": "45",
|
||||
"sparse_infill_density": "15%",
|
||||
"sparse_infill_pattern": "crosshatch",
|
||||
"initial_layer_acceleration": "500",
|
||||
"travel_acceleration": "0",
|
||||
"inner_wall_acceleration": "0",
|
||||
"initial_layer_line_width": "0.45",
|
||||
"initial_layer_print_height": "0.25",
|
||||
"infill_combination": "0",
|
||||
"sparse_infill_line_width": "0.5",
|
||||
"infill_wall_overlap": "35%",
|
||||
"interface_shells": "0",
|
||||
"ironing_flow": "15%",
|
||||
"ironing_spacing": "0.1",
|
||||
"ironing_speed": "15",
|
||||
"ironing_type": "no ironing",
|
||||
"reduce_infill_retraction": "1",
|
||||
"filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode",
|
||||
"detect_overhang_wall": "1",
|
||||
"overhang_1_4_speed": "0",
|
||||
"overhang_2_4_speed": "20",
|
||||
"overhang_3_4_speed": "15",
|
||||
"overhang_4_4_speed": "10",
|
||||
"inner_wall_line_width": "0.45",
|
||||
"wall_loops": "3",
|
||||
"print_settings_id": "",
|
||||
"raft_layers": "0",
|
||||
"seam_position": "aligned",
|
||||
"skirt_distance": "3",
|
||||
"skirt_height": "1",
|
||||
"skirt_loops": "2",
|
||||
"minimum_sparse_infill_area": "10",
|
||||
"internal_solid_infill_line_width": "0.45",
|
||||
"spiral_mode": "0",
|
||||
"standby_temperature_delta": "-10",
|
||||
"enable_support": "0",
|
||||
"resolution": "0.012",
|
||||
"support_type": "normal(auto)",
|
||||
"support_style": "grid",
|
||||
"support_on_build_plate_only": "0",
|
||||
"support_top_z_distance": "0.2",
|
||||
"support_filament": "0",
|
||||
"support_line_width": "0.45",
|
||||
"support_interface_loop_pattern": "0",
|
||||
"support_interface_filament": "0",
|
||||
"support_interface_top_layers": "3",
|
||||
"support_interface_bottom_layers": "-1",
|
||||
"support_interface_spacing": "0.2",
|
||||
"support_interface_speed": "100%",
|
||||
"support_base_pattern": "rectilinear",
|
||||
"support_base_pattern_spacing": "0.2",
|
||||
"support_speed": "45",
|
||||
"support_threshold_angle": "30",
|
||||
"support_object_xy_distance": "50%",
|
||||
"tree_support_branch_angle": "40",
|
||||
"tree_support_wall_count": "0",
|
||||
"detect_thin_wall": "1",
|
||||
"top_surface_pattern": "monotonicline",
|
||||
"top_surface_line_width": "0.4",
|
||||
"top_shell_layers": "5",
|
||||
"top_shell_thickness": "0.8",
|
||||
"initial_layer_speed": "15",
|
||||
"initial_layer_infill_speed": "30",
|
||||
"initial_layer_travel_speed": "60",
|
||||
"outer_wall_speed": "30",
|
||||
"inner_wall_speed": "40",
|
||||
"internal_solid_infill_speed": "40",
|
||||
"top_surface_speed": "15",
|
||||
"gap_infill_speed": "20",
|
||||
"sparse_infill_speed": "40",
|
||||
"travel_speed": "120",
|
||||
"enable_prime_tower": "0",
|
||||
"wipe_tower_no_sparse_layers": "0",
|
||||
"prime_tower_width": "60",
|
||||
"xy_hole_compensation": "0",
|
||||
"xy_contour_compensation": "0",
|
||||
"compatible_printers": [
|
||||
"UltiMaker S5 0.4 nozzle",
|
||||
"UltiMaker S5 0.4 nozzle - Single Extruder"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"type": "process",
|
||||
"name": "0.2mm Fast @UltiMaker S5",
|
||||
"inherits": "0.18mm Standard @UltiMaker S5",
|
||||
"from": "system",
|
||||
"setting_id": "Ai0I1drAIYiCbydj",
|
||||
"instantiation": "true",
|
||||
"default_acceleration": "3500",
|
||||
"default_jerk": "20",
|
||||
"enable_support": "1",
|
||||
"gap_infill_speed": "25",
|
||||
"infill_combination": "1",
|
||||
"infill_combination_max_layer_height": "80%",
|
||||
"infill_jerk": "20",
|
||||
"initial_layer_acceleration": "1500",
|
||||
"initial_layer_infill_speed": "40",
|
||||
"initial_layer_jerk": "10",
|
||||
"initial_layer_speed": "30",
|
||||
"initial_layer_travel_speed": "150",
|
||||
"inner_wall_acceleration": "3500",
|
||||
"inner_wall_jerk": "20",
|
||||
"inner_wall_speed": "100",
|
||||
"layer_height": "0.2",
|
||||
"outer_wall_acceleration": "1500",
|
||||
"outer_wall_jerk": "10",
|
||||
"print_extruder_id": [
|
||||
"1"
|
||||
],
|
||||
"print_extruder_variant": [
|
||||
"Direct Drive Standard"
|
||||
],
|
||||
"print_settings_id": "",
|
||||
"sparse_infill_pattern": "adaptivecubic",
|
||||
"sparse_infill_speed": "100",
|
||||
"support_style": "default",
|
||||
"support_type": "tree(auto)",
|
||||
"top_shell_layers": "1",
|
||||
"top_surface_acceleration": "1500",
|
||||
"top_surface_jerk": "10",
|
||||
"top_surface_speed": "45",
|
||||
"travel_acceleration": "3500",
|
||||
"travel_jerk": "20",
|
||||
"travel_speed": "150",
|
||||
"wall_loops": "2"
|
||||
}
|
||||
Binary file not shown.
@@ -152,6 +152,7 @@ static t_config_enum_values s_keys_map_PrintHostType {
|
||||
{ "octoprint", htOctoPrint },
|
||||
{ "crealityprint", htCrealityPrint },
|
||||
{ "duet", htDuet },
|
||||
{ "ultimaker", htUltiMaker },
|
||||
{ "flashair", htFlashAir },
|
||||
{ "astrobox", htAstroBox },
|
||||
{ "repetier", htRepetier },
|
||||
@@ -5401,6 +5402,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_values.push_back("prusaconnect");
|
||||
def->enum_values.push_back("octoprint");
|
||||
def->enum_values.push_back("duet");
|
||||
def->enum_values.push_back("ultimaker");
|
||||
def->enum_values.push_back("flashair");
|
||||
def->enum_values.push_back("astrobox");
|
||||
def->enum_values.push_back("repetier");
|
||||
@@ -5417,6 +5419,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_labels.push_back("PrusaConnect");
|
||||
def->enum_labels.push_back("Octo/Klipper");
|
||||
def->enum_labels.push_back("Duet");
|
||||
def->enum_labels.push_back("UltiMaker");
|
||||
def->enum_labels.push_back("FlashAir");
|
||||
def->enum_labels.push_back("AstroBox");
|
||||
def->enum_labels.push_back("Repetier");
|
||||
|
||||
@@ -98,7 +98,7 @@ enum class WipeTowerType {
|
||||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint, htElegooLink, ht3DPrinterOS, htMoonraker
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htUltiMaker, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint, htElegooLink, ht3DPrinterOS, htMoonraker
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
|
||||
@@ -776,6 +776,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
Utils/SimplyPrint.hpp
|
||||
Utils/TCPConsole.cpp
|
||||
Utils/TCPConsole.hpp
|
||||
Utils/UltiMaker.cpp
|
||||
Utils/UltiMaker.hpp
|
||||
Utils/UndoRedo.cpp
|
||||
Utils/UndoRedo.hpp
|
||||
Utils/WebSocketClient.hpp
|
||||
|
||||
@@ -265,6 +265,43 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||
return sizer;
|
||||
};
|
||||
|
||||
|
||||
auto ultimaker_generate_creds = [=](wxWindow* parent) {
|
||||
auto sizer = create_sizer_with_btn(parent, &m_printhost_generate_creds_btn, "ultimaker_generate_creds", _L("Generate API Key"));
|
||||
|
||||
m_printhost_generate_creds_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||
if (!host) {
|
||||
const wxString text = _L("Could not get a valid Printer Host reference");
|
||||
show_error(this, text);
|
||||
return;
|
||||
}
|
||||
|
||||
wxString msg = "generate_auth_creds";
|
||||
bool result;
|
||||
{
|
||||
// Show a wait cursor during the connection test, as it is blocking UI.
|
||||
wxBusyCursor wait;
|
||||
// Send request to printer for api key and such
|
||||
result = host->test(msg); // using test with special input because I don't want to create the generate_auth_creds func for every printer
|
||||
|
||||
// Prompt user to approve access on the machine.
|
||||
show_info(this, "API Key created. Go to the physical printer and hit \"authorize\" on the screen, then run \"Test\" again.\n"+msg, "API Key created.");
|
||||
|
||||
|
||||
}
|
||||
if (result)
|
||||
show_info(this, host->get_test_ok_msg(), _L("Success!"));
|
||||
else
|
||||
show_error(this, host->get_test_failed_msg(msg));
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
|
||||
auto print_host_logout = [&](wxWindow* parent) {
|
||||
auto sizer = create_sizer_with_btn(parent, &m_printhost_logout_btn, "", _L("Log Out"));
|
||||
|
||||
@@ -303,6 +340,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||
Line host_line = m_optgroup->create_single_option_line(option);
|
||||
host_line.append_widget(printhost_browse);
|
||||
host_line.append_widget(print_host_test);
|
||||
host_line.append_widget(ultimaker_generate_creds);
|
||||
host_line.append_widget(print_host_logout);
|
||||
m_optgroup->append_line(host_line);
|
||||
|
||||
@@ -606,8 +644,7 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
||||
m_optgroup->hide_field("bbl_use_print_host_webui");
|
||||
m_optgroup->enable_field("printhost_cafile");
|
||||
m_optgroup->enable_field("printhost_ssl_ignore_revoke");
|
||||
if (m_printhost_cafile_browse_btn)
|
||||
m_printhost_cafile_browse_btn->Enable();
|
||||
if (m_printhost_cafile_browse_btn) { m_printhost_cafile_browse_btn->Enable(); }
|
||||
|
||||
// hide pre-configured address, in case user switched to a different host type
|
||||
if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) {
|
||||
@@ -704,7 +741,24 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
||||
m_optgroup->hide_field("printhost_authorization_type");
|
||||
} else {
|
||||
m_optgroup->hide_field("flashforge_serial_number");
|
||||
}
|
||||
}
|
||||
|
||||
if (opt->value == htUltiMaker) {
|
||||
m_optgroup->hide_field("printhost_apikey");
|
||||
m_optgroup->hide_field("printhost_authorization_type");
|
||||
m_optgroup->hide_field("bbl_use_print_host_webui");
|
||||
m_optgroup->hide_field("printhost_cafile");
|
||||
m_optgroup->show_field("printhost_user");
|
||||
m_optgroup->show_field("printhost_password");
|
||||
m_optgroup->enable_field("print_host");
|
||||
m_optgroup->show_field("print_host_webui");
|
||||
if (m_printhost_cafile_browse_btn) {
|
||||
m_printhost_cafile_browse_btn->Disable();
|
||||
}
|
||||
if (m_printhost_generate_creds_btn) {
|
||||
m_printhost_generate_creds_btn->Enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
|
||||
@@ -720,6 +774,10 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
||||
m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword);
|
||||
}
|
||||
|
||||
// The "Generate API Key" button is only meaningful for UltiMaker printers.
|
||||
if (m_printhost_generate_creds_btn)
|
||||
m_printhost_generate_creds_btn->Show(tech == ptFFF && m_config->opt_enum<PrintHostType>("host_type") == htUltiMaker);
|
||||
|
||||
m_optgroup->show_field("printhost_port", supports_multiple_printers);
|
||||
m_printhost_port_browse_btn->Show(supports_multiple_printers);
|
||||
|
||||
@@ -787,6 +845,7 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
|
||||
m_printhost_browse_btn->Rescale();
|
||||
m_printhost_test_btn->Rescale();
|
||||
m_printhost_generate_creds_btn->Rescale();
|
||||
m_printhost_logout_btn->Rescale();
|
||||
if (m_printhost_cafile_browse_btn)
|
||||
m_printhost_cafile_browse_btn->Rescale();
|
||||
|
||||
@@ -31,6 +31,7 @@ class PhysicalPrinterDialog : public DPIDialog
|
||||
|
||||
Button* m_printhost_browse_btn {nullptr};
|
||||
Button* m_printhost_test_btn {nullptr};
|
||||
Button* m_printhost_generate_creds_btn {nullptr};
|
||||
Button* m_printhost_logout_btn {nullptr};
|
||||
Button* m_printhost_cafile_browse_btn {nullptr};
|
||||
Button* m_printhost_port_browse_btn {nullptr};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "libslic3r/Channel.hpp"
|
||||
#include "OctoPrint.hpp"
|
||||
#include "Duet.hpp"
|
||||
#include "UltiMaker.hpp"
|
||||
#include "FlashAir.hpp"
|
||||
#include "AstroBox.hpp"
|
||||
#include "Repetier.hpp"
|
||||
@@ -57,6 +58,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||
switch (host_type) {
|
||||
case htOctoPrint: return new OctoPrint(config);
|
||||
case htDuet: return new Duet(config);
|
||||
case htUltiMaker: return new UltiMaker(config);
|
||||
case htFlashAir: return new FlashAir(config);
|
||||
case htAstroBox: return new AstroBox(config);
|
||||
case htRepetier: return new Repetier(config);
|
||||
|
||||
@@ -0,0 +1,666 @@
|
||||
#include "UltiMaker.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
UltiMaker::UltiMaker(DynamicPrintConfig *config) :
|
||||
|
||||
m_host(config->opt_string("print_host")),
|
||||
host(config->opt_string("print_host")),
|
||||
|
||||
m_api_username(config->opt_string("printhost_user")),
|
||||
m_api_password(config->opt_string("printhost_password")),
|
||||
m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
|
||||
{}
|
||||
|
||||
const char* UltiMaker::get_name() const { return "UltiMaker"; }
|
||||
|
||||
// Modified from OctoPrint::test in OctoPrint.cpp
|
||||
bool UltiMaker::test(wxString &msg) const
|
||||
{
|
||||
// Returns true on success, false on error.
|
||||
// If called with specific arg, just generate the auth creds.
|
||||
if (msg == "generate_auth_creds") {
|
||||
BOOST_LOG_TRIVIAL(debug) << "UltiMaker: test called with generate_auth_creds! Generating the auth credentials.";
|
||||
return this->generate_auth_creds(msg);
|
||||
}
|
||||
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: Attempting to test machine");
|
||||
|
||||
const char* name = get_name();
|
||||
|
||||
bool res = true;
|
||||
auto url = (boost::format("http://%1%/api/v1/system/variant") % host).str();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Get system variant at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting system variant: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([name, &res](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got system variant: %2%") % name % body;
|
||||
|
||||
// Validate that response is correct ("UltiMaker 3", "UltiMaker 3 extended" or "UltiMaker S5")
|
||||
res = (boost::starts_with(body, "\"Ultimaker 3") || body == "\"Ultimaker S5\"");
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Testing auth.") % name;
|
||||
std::string auth_result = test_auth();
|
||||
if ( !boost::starts_with(auth_result,"OK") ) { msg = auth_result; res = false; };
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Auth test result: %2%") % name % auth_result;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
wxString UltiMaker::get_test_ok_msg () const
|
||||
{
|
||||
return _("Connection to UltiMaker works correctly.");
|
||||
}
|
||||
|
||||
wxString UltiMaker::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return GUI::from_u8((boost::format("%s: %s")
|
||||
% _utf8("Could not connect to UltiMaker")
|
||||
% std::string(msg.ToUTF8())).str());
|
||||
}
|
||||
|
||||
|
||||
#pragma region Auth
|
||||
// Modified from PrusaLink::set_auth in OctoPrint.cpp
|
||||
void UltiMaker::set_auth(Http& http) const
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: set_auth, m_api_username of %1% and m_api_password of %2%") % get_api_username() % get_api_password();
|
||||
http.auth_digest(m_api_username, m_api_password);
|
||||
}
|
||||
|
||||
|
||||
bool UltiMaker::has_auth_creds() const{
|
||||
// True if has authentication credentials, false otherwise
|
||||
return (!m_api_username.empty()) && (!m_api_password.empty());
|
||||
}
|
||||
|
||||
|
||||
bool UltiMaker::is_authorized() const{
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: is_authorized() checking if creds are valid.");
|
||||
|
||||
const char* name = get_name();
|
||||
|
||||
bool rtn = false;
|
||||
auto url = (boost::format("http://%1%/api/v1/auth/verify") % host).str();
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error verifying credentials: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
|
||||
if (status == 403 || body == "{\"message\": \"Authorization required.\"}") {
|
||||
rtn = false;
|
||||
}
|
||||
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: url completed without error: %1%") % url;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got credential verification: %2%") % name % body;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: [auth] rtn=%2%") % name % rtn; // DEBUG
|
||||
|
||||
try {
|
||||
// Validate that response is correct
|
||||
rtn = (boost::starts_with(body, "\"ok\"") || body == "{\"message\": \"ok\"}");
|
||||
}
|
||||
catch (const std::exception &) {
|
||||
rtn = false;
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: is_authorized Caught error") % name;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
// msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: [auth] rtn=%2%") % name % rtn;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::string UltiMaker::auth_status() const{
|
||||
// returns 'authorized', 'unauthorized', or 'waiting', or 'ERROR: unknown value' on a different output
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: auth_status() verifying credentials.");
|
||||
|
||||
const char* name = get_name();
|
||||
|
||||
std::string rtn = "ERROR: unknown value";
|
||||
auto url = (boost::format("http://%1%/api/v1/auth/check/%2%") % host % m_api_username).str();
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error verifying credentials: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: url completed without error: %1%") % url;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got credential verification: %2%") % name % body;
|
||||
|
||||
try {
|
||||
if (body == "{\"message\": \"unknown\"}") { rtn = "waiting";}
|
||||
if (body == "{\"message\": \"authorized\"}" ) {rtn = "authorized";}
|
||||
if (body == "{\"message\": \"unauthorized\"}") {rtn = "unauthorized";}
|
||||
|
||||
}
|
||||
catch (const std::exception &) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: auth_status Caught error") % name;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
// msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
if (boost::starts_with(rtn, "ERROR")) { BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: [auth][auth_status] Unknown value encountered in api/v1/auth/check") % name;}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool UltiMaker::generate_auth_creds(wxString &msg) const {
|
||||
// Returns true if no error, false otherwise.
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "UltiMaker: generate_auth_creds called!";
|
||||
bool ret = true;
|
||||
/*
|
||||
The auth POST request returns json of the form
|
||||
{
|
||||
"id": "string",
|
||||
"key": "string"
|
||||
}
|
||||
where id is the m_api_username and key is the m_api_password
|
||||
*/
|
||||
const std::string name = get_name();
|
||||
std::string application = "OrcaSlicer";
|
||||
std::string application_key = "application";
|
||||
std::string user = "OrcaSlicer";
|
||||
std::string user_key = "user";
|
||||
std::string exclusionKey = "OrcaSlicer";
|
||||
std::string exclusionKey_key = "OrcaSlicer";
|
||||
auto url = (boost::format("http://%1%/api/v1/auth/request") % host).str();
|
||||
auto http = Http::post(std::move(url));
|
||||
|
||||
// Note: the body/form cannot be blank or OrcaSlicer's Http library crashes on perform_sync()
|
||||
http.form_clear();
|
||||
http.mime_form_add_text(application_key,application);
|
||||
http.mime_form_add_text(user_key,user);
|
||||
http.mime_form_add_text(exclusionKey_key,exclusionKey);
|
||||
|
||||
http.on_error([name, &msg, &ret](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error generating credentials: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
msg = "Error generating credentials! See the log for details.";
|
||||
ret = false;
|
||||
})
|
||||
.on_complete([name, &msg, &ret](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: body: %2%") % name % body;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: POST request completed without error") % name;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got credential verification: %2%") % name % body;
|
||||
|
||||
std::stringstream sin(body);
|
||||
boost::property_tree::ptree pt;
|
||||
|
||||
try {
|
||||
// Parse JSON into property tree
|
||||
boost::property_tree::read_json(sin, pt);
|
||||
// Extract the keys from the json
|
||||
std::string id = pt.get<std::string>("id", "");
|
||||
std::string key = pt.get<std::string>("key", "");
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: Got id and key. ID=%1%") % id;
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: KEY=%1%") % key;
|
||||
|
||||
if (id.empty() || key.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Either ID or KEY is empty.") % name;
|
||||
msg = GUI::from_u8("ERROR: Either ID or KEY is empty. ID=" + id + " KEY=" + key);
|
||||
ret = false;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: ID and KEY are both present. Setting message appropriately.") % name;
|
||||
msg = GUI::from_u8("Username: " + id + " Password: " + key);
|
||||
}
|
||||
}
|
||||
catch (const std::exception & e) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: generate_auth_creds Caught error") % name;
|
||||
BOOST_LOG_TRIVIAL(error) << "Json parse err: " << e.what();
|
||||
ret = false;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
// msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
|
||||
// Wait for user to authorize on the physical machine
|
||||
BOOST_LOG_TRIVIAL(debug) << "UltiMaker: generate_auth_creds done! ret=" << ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string UltiMaker::test_auth() const {
|
||||
/*
|
||||
Used in UltiMaker::test(), creates creds if they don't already
|
||||
exist, and returns various error strings if errors.
|
||||
Returns "OK" if no errors and creds are valid.
|
||||
*/
|
||||
const char* name = get_name();
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Testing for presence of auth creds...") % name;
|
||||
|
||||
// Auth credentials are alreay existing (user-entered)
|
||||
if (has_auth_creds()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Has auth credentials. Testing validity...") % name;
|
||||
|
||||
std::string auth_stat = auth_status();
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: auth_status returned %2%") % name % auth_stat;
|
||||
|
||||
if (auth_stat == "waiting") {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Auth credentials are waiting for user approval at the physical machine.") % name;
|
||||
return "Authentication creds waiting on approval; Please approve access via the dialog box on the printer's screen.";
|
||||
|
||||
} else if (auth_stat == "authorized" && is_authorized()) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Auth credentials are valid. Returning OK") % name;
|
||||
return "OK";
|
||||
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: Auth credentials are INVALID. auth_status returned %2%") % name % auth_stat;
|
||||
return "Invalid authentication credentials";
|
||||
}
|
||||
|
||||
} else { // Auth credentials do not exist
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Auth credentials do NOT already exist. Returning error string.") % name;
|
||||
return "Error: No authentication credentials found!";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma endregion Auth
|
||||
|
||||
|
||||
int UltiMaker::getPrintTime(std::string filepath) const {
|
||||
// Get the total print time in seconds from the provided gcode filepath
|
||||
std::ifstream file_in(filepath);
|
||||
std::string line;
|
||||
int ret = 0;
|
||||
bool found = false;
|
||||
int h = 0;
|
||||
int m = 0;
|
||||
int s = 0;
|
||||
|
||||
if (file_in.is_open()){
|
||||
while (getline(file_in, line) && !found) {
|
||||
if (boost::starts_with(line, "; estimated printing time (normal mode) =")) {
|
||||
if (sscanf(line.c_str(), "; estimated printing time (normal mode) = %dh %dm %ds", &h, &m, &s) == 3) {
|
||||
ret = 3600*h + 60*m + s;
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("UltiMaker: Hours=%1% Minutes=%2% Seconds=%3% Total Seconds=%4%") % h % m % s % ret;
|
||||
found = true;
|
||||
} else if (sscanf(line.c_str(), "; estimated printing time (normal mode) = %dm %ds", &m, &s) == 2) {
|
||||
ret = 60*m + s;
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("UltiMaker: No Hours, Minutes=%1% Seconds=%2% Total Seconds=%3%") % m % s % ret;
|
||||
found = true;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("UltiMaker: ERROR: sscanf error while getting total print time, no vars assigned.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker postprocessing ERROR: file %1% or file %2% was unable to open!") % filepath % (filepath+".temp");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker: Print time estimate not found!!");
|
||||
}
|
||||
|
||||
file_in.close();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool UltiMaker::makeGriffinCompatible(std::string filepath) const {
|
||||
// Modifies the header of the given file path to remove the double colon
|
||||
// at the timestamp because >1 colons in a line makes UltiMakers crash.
|
||||
|
||||
// Opening and modifying files code was modified from https://www.w3resource.com/cpp-exercises/file-handling/cpp-file-handling-exercise-6.php
|
||||
std::string file_temp = filepath+".temp";
|
||||
std::ifstream file_in(filepath);
|
||||
std::ofstream file_out(file_temp);
|
||||
std::string line;
|
||||
bool write_on = false;
|
||||
|
||||
if (file_in.is_open() && file_out.is_open()){
|
||||
while (getline(file_in, line)) {
|
||||
|
||||
if (write_on) {
|
||||
if (boost::starts_with(line, "; generated by OrcaSlicer")) {
|
||||
boost::replace_all(line, ":", "-"); // Replace the colons in the date with dashes to prevent printer crash.
|
||||
}
|
||||
|
||||
// UM machines need total print time in seconds.
|
||||
if (boost::starts_with(line, ";PRINT.TIME:")) {
|
||||
file_out << ";PRINT.TIME:"<< getPrintTime(filepath) << "\n";
|
||||
|
||||
} else {
|
||||
file_out << line << "\n"; // Write the modified line to the output file
|
||||
}
|
||||
|
||||
} else if (boost::starts_with(line, ";START_OF_HEADER")) {
|
||||
write_on = true;
|
||||
file_out << line << "\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker: file %1% or file %2% was unable to open!") % filepath % (filepath+".temp");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!write_on) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("UltiMaker: while postprocessing to make compatible with Griffin, headers not found!");
|
||||
}
|
||||
|
||||
file_in.close();
|
||||
file_out.close();
|
||||
|
||||
// Now replace the upload file with the temp one
|
||||
std::remove(filepath.c_str());
|
||||
std::rename(file_temp.c_str(), filepath.c_str());
|
||||
// boost::filesystem::copy_file(file_temp, filepath); // replacement of the above line to keep in /tmp/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool UltiMaker::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
// Preprocess the file so the machine doesn't crash
|
||||
if (makeGriffinCompatible(upload_data.source_path.string())) {
|
||||
BOOST_LOG_TRIVIAL(info) << "UltiMaker: [upload] Griffin compatibility appeared successful.";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UltiMaker: [upload] Griffin compatibility FAILED.";
|
||||
return false;
|
||||
}
|
||||
|
||||
wxString connect_msg;
|
||||
auto connectionType = connect(connect_msg);
|
||||
if (connectionType == ConnectionType::error) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("UltiMaker: [upload] connectionType is of type error!");
|
||||
error_fn(std::move(connect_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
bool dsf = (connectionType == ConnectionType::dsf);
|
||||
|
||||
auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType);
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: Uploading file %1%, filepath: %2%, post_action: %3%, command: %4%")
|
||||
% upload_data.source_path
|
||||
% upload_data.upload_path
|
||||
% int(upload_data.post_action)
|
||||
% upload_cmd;
|
||||
|
||||
auto http = (dsf ? Http::put(std::move(upload_cmd)) : Http::post(std::move(upload_cmd)));
|
||||
http.form_add("jobname",upload_data.upload_path.string());
|
||||
http.form_add_file("file", upload_data.source_path, upload_data.upload_path.string());
|
||||
|
||||
set_auth(http);
|
||||
http.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: File uploaded: HTTP %1%: %2%") % status % body;
|
||||
|
||||
int err_code = dsf ? (status == 201 ? 0 : 1) : get_err_code_from_body(body);
|
||||
if (err_code != 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker: Request completed but error code was received: %1%") % err_code;
|
||||
error_fn(format_error(body, L("Unknown error occurred"), 0));
|
||||
res = false;
|
||||
} else if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||
wxString errormsg;
|
||||
res = start_print(errormsg, upload_data.upload_path.string(), connectionType);
|
||||
if (! res) {
|
||||
error_fn(std::move(errormsg));
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << "UltiMaker: Upload canceled";
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
disconnect(connectionType);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
UltiMaker::ConnectionType UltiMaker::connect(wxString &msg) const
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("UltiMaker: Attempting to connect");
|
||||
|
||||
auto res = ConnectionType::error;
|
||||
auto url = get_connect_url();
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
set_auth(http);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: Connecting to url %1%") % url;
|
||||
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
auto dsfUrl = get_connect_url();
|
||||
auto dsfHttp = Http::get(std::move(dsfUrl));
|
||||
dsfHttp.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
res = ConnectionType::dsf;
|
||||
})
|
||||
.perform_sync();
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: Got: %1%") % body;
|
||||
|
||||
int err_code = get_err_code_from_body(body);
|
||||
switch (err_code) {
|
||||
case 0:
|
||||
res = ConnectionType::rrf;
|
||||
break;
|
||||
case 1:
|
||||
msg = format_error(body, L("Wrong password"), 0);
|
||||
break;
|
||||
case 2:
|
||||
msg = format_error(body, L("Could not get resources to create a new connection"), 0);
|
||||
break;
|
||||
default:
|
||||
msg = format_error(body, L("Unknown error occurred"), 0);
|
||||
break;
|
||||
}
|
||||
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void UltiMaker::disconnect(ConnectionType connectionType) const
|
||||
{
|
||||
// we don't need to disconnect from DSF or if it failed anyway
|
||||
if (connectionType != ConnectionType::rrf) {
|
||||
return;
|
||||
}
|
||||
auto url = (boost::format("%1%rr_disconnect")
|
||||
% get_base_url()).str();
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
// we don't care about it, if disconnect is not working UltiMaker will disconnect automatically after some time
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
})
|
||||
.perform_sync();
|
||||
}
|
||||
|
||||
|
||||
#pragma region Get Constants
|
||||
std::string UltiMaker::get_upload_url(const std::string &filename, ConnectionType connectionType) const
|
||||
{
|
||||
assert(connectionType != ConnectionType::error);
|
||||
|
||||
if (connectionType == ConnectionType::dsf) {
|
||||
return (boost::format("%1%/machine/file/gcodes/%2%")
|
||||
% get_base_url()
|
||||
% Http::url_encode(filename)).str();
|
||||
} else {
|
||||
return get_base_url()+"/print_job";
|
||||
}
|
||||
}
|
||||
|
||||
std::string UltiMaker::get_status_url() const
|
||||
{
|
||||
return (boost::format("%1%/printer/status") % get_base_url()).str();
|
||||
}
|
||||
|
||||
std::string UltiMaker::get_connect_url() const
|
||||
{
|
||||
return (boost::format("%1%/printer/status") % get_base_url()).str();
|
||||
}
|
||||
|
||||
std::string UltiMaker::get_base_url() const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return host;
|
||||
} else {
|
||||
return (boost::format("%1%/api/v1") % host).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/api/v1") % host).str();
|
||||
}
|
||||
}
|
||||
|
||||
std::string UltiMaker::timestamp_str() const
|
||||
{
|
||||
enum { BUFFER_SIZE = 32 };
|
||||
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm);
|
||||
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
#pragma endregion Get Constants
|
||||
|
||||
bool UltiMaker::start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const
|
||||
{
|
||||
assert(connectionType != ConnectionType::error);
|
||||
|
||||
bool res = false;
|
||||
bool dsf = (connectionType == ConnectionType::dsf);
|
||||
|
||||
auto url = dsf
|
||||
? (boost::format("%1%machine/code") % get_base_url()).str()
|
||||
: (boost::format("%1%rr_gcode?gcode=M32%%20\"0:/gcodes/%2%\"") % get_base_url() % Http::url_encode(filename)).str();
|
||||
|
||||
auto http = (dsf ? Http::post(std::move(url)) : Http::get(std::move(url)));
|
||||
set_auth(http);
|
||||
if (dsf) {
|
||||
http.set_post_body(
|
||||
(boost::format("M32 \"0:/gcodes/%1%\"") % filename).str()
|
||||
);
|
||||
}
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("UltiMaker: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("UltiMaker: Got: %1%") % body;
|
||||
res = true;
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int UltiMaker::get_err_code_from_body(const std::string &body) const
|
||||
{
|
||||
pt::ptree root;
|
||||
std::istringstream iss (body); // wrap returned json to istringstream
|
||||
pt::read_json(iss, root);
|
||||
|
||||
return root.get<int>("err", 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef slic3r_UltiMaker_hpp_
|
||||
#define slic3r_UltiMaker_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class UltiMaker : public PrintHost
|
||||
{
|
||||
public:
|
||||
explicit UltiMaker(DynamicPrintConfig *config);
|
||||
~UltiMaker() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString &msg) const override;
|
||||
|
||||
bool has_auth_creds() const;
|
||||
bool is_authorized() const;
|
||||
std::string auth_status() const;
|
||||
bool generate_auth_creds(wxString &msg) const;
|
||||
std::string test_auth() const;
|
||||
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
std::string get_host() const override { return host; }
|
||||
const std::string& get_api_user() const { return m_api_username; }
|
||||
const std::string& get_api_username() const { return m_api_username; }
|
||||
const std::string& get_api_password() const { return m_api_password; }
|
||||
|
||||
int getPrintTime(std::string filepath) const;
|
||||
|
||||
protected:
|
||||
std::string m_host;
|
||||
|
||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||
std::string m_api_username;
|
||||
std::string m_api_password;
|
||||
|
||||
bool m_ssl_revoke_best_effort;
|
||||
|
||||
bool makeGriffinCompatible(std::string filepath) const;
|
||||
|
||||
private:
|
||||
enum class ConnectionType { rrf, dsf, error };
|
||||
std::string host;
|
||||
|
||||
std::string get_upload_url(const std::string &filename, ConnectionType connectionType) const;
|
||||
std::string get_status_url() const;
|
||||
std::string get_connect_url() const;
|
||||
std::string get_base_url() const;
|
||||
std::string timestamp_str() const;
|
||||
ConnectionType connect(wxString &msg) const;
|
||||
void set_auth(Http& http) const;
|
||||
void disconnect(ConnectionType connectionType) const;
|
||||
bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const;
|
||||
int get_err_code_from_body(const std::string &body) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user