From ab6835e93beb82bd7005e6c31155bbc9d4f9da50 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 3 Jan 2020 08:51:03 +0100 Subject: [PATCH 01/33] Back-ported RF100 V1, V2, and XL resources files from [dok-net RF100](https://github.com/dok-net/RF100) to Renkforce COTS stock firmware (temperature, flow rate; build volume on V1). --- .../definitions/renkforce_rf100.def.json | 59 +++-- .../definitions/renkforce_rf100_v2.def.json | 234 ++++++++++++++++++ .../definitions/renkforce_rf100_xl.def.json | 222 +++++++++++++++++ .../renkforce_rf100_xl_extruder_0.def.json | 15 ++ 4 files changed, 514 insertions(+), 16 deletions(-) create mode 100644 resources/definitions/renkforce_rf100_v2.def.json create mode 100644 resources/definitions/renkforce_rf100_xl.def.json create mode 100644 resources/extruders/renkforce_rf100_xl_extruder_0.def.json diff --git a/resources/definitions/renkforce_rf100.def.json b/resources/definitions/renkforce_rf100.def.json index 2ff34a7519..0ef269886b 100644 --- a/resources/definitions/renkforce_rf100.def.json +++ b/resources/definitions/renkforce_rf100.def.json @@ -18,10 +18,10 @@ "default_value": "skirt" }, "bottom_thickness": { - "value": "0.5" + "value": "0.6" }, "brim_width": { - "value": "2.0" + "value": "3.0" }, "cool_fan_enabled": { "value": "True" @@ -47,11 +47,20 @@ "infill_before_walls": { "value": "True" }, + "infill_line_width": { + "value": "0.6" + }, "infill_overlap": { "value": "15.0" }, + "infill_sparse_density": { + "value": "26.0" + }, + "ironing_enabled": { + "value": "True" + }, "layer_0_z_overlap": { - "value": "0.22" + "value": "0.11" }, "layer_height_0": { "value": "0.3" @@ -60,11 +69,23 @@ "value": "100" }, "machine_end_gcode": { - "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 E-5 X-20 Y-20 ;retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG0 Z{machine_height} ;move the platform all the way down\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" + "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-2 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [-26, -27], + [38, -27], + [38, 55], + [-26, 55] + ] + }, + "gantry_height": { + "value": "8" + }, "machine_height": { "value": "100" }, @@ -72,7 +93,7 @@ "default_value": "Renkforce RF100" }, "machine_start_gcode": { - "default_value": ";Start GCode\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\n;Put printing message on LCD screen\nM117 Printing..." + "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E4.0 ;extrude 4.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." }, "machine_width": { "value": "100" @@ -90,7 +111,7 @@ "value": "True" }, "raft_airgap": { - "value": "0.22" + "value": "0.33" }, "raft_base_line_spacing": { "value": "3.0" @@ -111,22 +132,25 @@ "value": "0.27" }, "raft_margin": { - "value": "5.0" + "value": "6.0" + }, + "raft_speed": { + "value": "20.0" }, "raft_surface_layers": { - "value": "2.0" + "value": "2" }, "raft_surface_line_spacing": { - "value": "3.0" + "value": "0.4" }, "raft_surface_line_width": { "value": "0.4" }, "raft_surface_thickness": { - "value": "0.27" + "value": "0.1" }, "retraction_amount": { - "value": "2.0" + "value": "3.0" }, "retraction_combing": { "default_value": "all" @@ -134,7 +158,7 @@ "retraction_enable": { "value": "True" }, - "retraction_hop_enabled": { + "retraction_hop": { "value": "1.0" }, "retraction_min_travel": { @@ -185,6 +209,9 @@ "support_infill_rate": { "value": "15 if support_enable else 0 if support_tree_enable else 15" }, + "support_line_width": { + "value": "0.6" + }, "support_pattern": { "default_value": "lines" }, @@ -192,13 +219,13 @@ "default_value": "everywhere" }, "support_xy_distance": { - "value": "0.5" + "value": "0.7" }, "support_z_distance": { - "value": "0.1" + "value": "0.35" }, - "top_thickness": { - "value": "0.5" + "top_bottom_thickness": { + "value": "0.8" }, "wall_thickness": { "value": "0.8" diff --git a/resources/definitions/renkforce_rf100_v2.def.json b/resources/definitions/renkforce_rf100_v2.def.json new file mode 100644 index 0000000000..5467ff0ba8 --- /dev/null +++ b/resources/definitions/renkforce_rf100_v2.def.json @@ -0,0 +1,234 @@ +{ + "version": 2, + "name": "Renkforce RF100 V2", + "inherits": "fdmprinter", + "metadata": { + "author": "Simon Peter (based on RF100.ini by Conrad Electronic SE)", + "file_formats": "text/x-gcode", + "manufacturer": "Renkforce", + "visible": true, + "machine_extruder_trains": + { + "0": "renkforce_rf100_extruder_0" + } + }, + + "overrides": { + "adhesion_type": { + "default_value": "skirt" + }, + "bottom_thickness": { + "value": "0.6" + }, + "brim_width": { + "value": "3.0" + }, + "cool_fan_enabled": { + "value": "True" + }, + "cool_fan_full_at_height": { + "value": "0.5" + }, + "cool_fan_speed_max": { + "value": "100.0" + }, + "cool_fan_speed_min": { + "value": "100.0" + }, + "cool_lift_head": { + "value": "True" + }, + "cool_min_layer_time": { + "value": "5.0" + }, + "cool_min_speed": { + "value": "10.0" + }, + "infill_before_walls": { + "value": "True" + }, + "infill_line_width": { + "value": "0.6" + }, + "infill_overlap": { + "value": "15.0" + }, + "infill_sparse_density": { + "value": "26.0" + }, + "ironing_enabled": { + "value": "True" + }, + "layer_0_z_overlap": { + "value": "0.11" + }, + "layer_height_0": { + "value": "0.3" + }, + "machine_depth": { + "value": "120" + }, + "machine_end_gcode": { + "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-2 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" + }, + "machine_gcode_flavor": { + "default_value": "RepRap (Marlin/Sprinter)" + }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [-26, -27], + [38, -27], + [38, 55], + [-26, 55] + ] + }, + "gantry_height": { + "value": "8" + }, + "machine_height": { + "value": "120" + }, + "machine_name": { + "default_value": "Renkforce RF100 V2" + }, + "machine_start_gcode": { + "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E4.0 ;extrude 4.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." + }, + "machine_width": { + "value": "120" + }, + "material_bed_temperature": { + "enabled": false + }, + "material_flow": { + "value": "110" + }, + "material_print_temperature": { + "value": "210.0" + }, + "ooze_shield_enabled": { + "value": "True" + }, + "raft_airgap": { + "value": "0.33" + }, + "raft_base_line_spacing": { + "value": "3.0" + }, + "raft_base_line_width": { + "value": "1.0" + }, + "raft_base_thickness": { + "value": "0.3" + }, + "raft_interface_line_spacing": { + "value": "3.0" + }, + "raft_interface_line_width": { + "value": "0.4" + }, + "raft_interface_thickness": { + "value": "0.27" + }, + "raft_margin": { + "value": "6.0" + }, + "raft_speed": { + "value": "20.0" + }, + "raft_surface_layers": { + "value": "2" + }, + "raft_surface_line_spacing": { + "value": "0.4" + }, + "raft_surface_line_width": { + "value": "0.4" + }, + "raft_surface_thickness": { + "value": "0.1" + }, + "retraction_amount": { + "value": "3.0" + }, + "retraction_combing": { + "default_value": "all" + }, + "retraction_enable": { + "value": "True" + }, + "retraction_hop": { + "value": "1.0" + }, + "retraction_min_travel": { + "value": "1.5" + }, + "retraction_speed": { + "value": "40.0" + }, + "skin_overlap": { + "value": "15.0" + }, + "skirt_brim_minimal_length": { + "value": "150.0" + }, + "skirt_gap": { + "value": "3.0" + }, + "skirt_line_count": { + "value": "3" + }, + "speed_infill": { + "value": "50.0" + }, + "speed_layer_0": { + "value": "15.0" + }, + "speed_print": { + "value": "50.0" + }, + "speed_topbottom": { + "value": "30.0" + }, + "speed_travel": { + "value": "50.0" + }, + "speed_wall_0": { + "value": "25.0" + }, + "speed_wall_x": { + "value": "35.0" + }, + "support_angle": { + "value": "60.0" + }, + "support_enable": { + "value": "False" + }, + "support_infill_rate": { + "value": "15 if support_enable else 0 if support_tree_enable else 15" + }, + "support_line_width": { + "value": "0.6" + }, + "support_pattern": { + "default_value": "lines" + }, + "support_type": { + "default_value": "everywhere" + }, + "support_xy_distance": { + "value": "0.7" + }, + "support_z_distance": { + "value": "0.35" + }, + "top_bottom_thickness": { + "value": "0.8" + }, + "wall_thickness": { + "value": "0.8" + } + } +} diff --git a/resources/definitions/renkforce_rf100_xl.def.json b/resources/definitions/renkforce_rf100_xl.def.json new file mode 100644 index 0000000000..cf5bbcd006 --- /dev/null +++ b/resources/definitions/renkforce_rf100_xl.def.json @@ -0,0 +1,222 @@ +{ + "version": 2, + "name": "Renkforce RF100 XL", + "inherits": "fdmprinter", + "metadata": { + "author": "Simon Peter (based on RF100.ini by Conrad Electronic SE)", + "file_formats": "text/x-gcode", + "manufacturer": "Renkforce", + "visible": true, + "machine_extruder_trains": + { + "0": "renkforce_rf100_xl_extruder_0" + } + }, + + "overrides": { + "adhesion_type": { + "default_value": "skirt" + }, + "bottom_thickness": { + "value": "0.6" + }, + "brim_width": { + "value": "3.0" + }, + "cool_fan_enabled": { + "value": "True" + }, + "cool_fan_full_at_height": { + "value": "0.5" + }, + "cool_fan_speed_max": { + "value": "100.0" + }, + "cool_fan_speed_min": { + "value": "100.0" + }, + "cool_lift_head": { + "value": "True" + }, + "cool_min_layer_time": { + "value": "5.0" + }, + "cool_min_speed": { + "value": "10.0" + }, + "infill_before_walls": { + "value": "True" + }, + "infill_line_width": { + "value": "0.6" + }, + "infill_overlap": { + "value": "15.0" + }, + "infill_sparse_density": { + "value": "26.0" + }, + "ironing_enabled": { + "value": "True" + }, + "layer_0_z_overlap": { + "value": "0.11" + }, + "layer_height_0": { + "value": "0.3" + }, + "machine_depth": { + "value": "200" + }, + "machine_end_gcode": { + "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-2 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" + }, + "machine_gcode_flavor": { + "default_value": "RepRap (Marlin/Sprinter)" + }, + "machine_heated_bed": { + "default_value": "true" + }, + "machine_height": { + "value": "200" + }, + "machine_name": { + "default_value": "Renkforce RF100 XL" + }, + "machine_start_gcode": { + "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E4.0 ;extrude 4.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." + }, + "machine_width": { + "value": "200" + }, + "material_bed_temperature": { + "value": "70" + }, + "material_print_temperature": { + "value": "210.0" + }, + "ooze_shield_enabled": { + "value": "True" + }, + "raft_airgap": { + "value": "0.33" + }, + "raft_base_line_spacing": { + "value": "3.0" + }, + "raft_base_line_width": { + "value": "1.0" + }, + "raft_base_thickness": { + "value": "0.3" + }, + "raft_interface_line_spacing": { + "value": "3.0" + }, + "raft_interface_line_width": { + "value": "0.4" + }, + "raft_interface_thickness": { + "value": "0.27" + }, + "raft_margin": { + "value": "6.0" + }, + "raft_speed": { + "value": "20.0" + }, + "raft_surface_layers": { + "value": "2" + }, + "raft_surface_line_spacing": { + "value": "0.4" + }, + "raft_surface_line_width": { + "value": "0.4" + }, + "raft_surface_thickness": { + "value": "0.1" + }, + "retraction_amount": { + "value": "3.0" + }, + "retraction_combing": { + "default_value": "all" + }, + "retraction_enable": { + "value": "True" + }, + "retraction_hop": { + "value": "1.0" + }, + "retraction_min_travel": { + "value": "1.5" + }, + "retraction_speed": { + "value": "40.0" + }, + "skin_overlap": { + "value": "15.0" + }, + "skirt_brim_minimal_length": { + "value": "150.0" + }, + "skirt_gap": { + "value": "3.0" + }, + "skirt_line_count": { + "value": "3" + }, + "speed_infill": { + "value": "50.0" + }, + "speed_layer_0": { + "value": "15.0" + }, + "speed_print": { + "value": "50.0" + }, + "speed_topbottom": { + "value": "30.0" + }, + "speed_travel": { + "value": "50.0" + }, + "speed_wall_0": { + "value": "25.0" + }, + "speed_wall_x": { + "value": "35.0" + }, + "support_angle": { + "value": "60.0" + }, + "support_enable": { + "value": "False" + }, + "support_infill_rate": { + "value": "15 if support_enable else 0 if support_tree_enable else 15" + }, + "support_line_width": { + "value": "0.6" + }, + "support_pattern": { + "default_value": "lines" + }, + "support_type": { + "default_value": "everywhere" + }, + "support_xy_distance": { + "value": "0.7" + }, + "support_z_distance": { + "value": "0.35" + }, + "top_bottom_thickness": { + "value": "0.8" + }, + "wall_thickness": { + "value": "0.8" + } + } +} diff --git a/resources/extruders/renkforce_rf100_xl_extruder_0.def.json b/resources/extruders/renkforce_rf100_xl_extruder_0.def.json new file mode 100644 index 0000000000..718a3738c4 --- /dev/null +++ b/resources/extruders/renkforce_rf100_xl_extruder_0.def.json @@ -0,0 +1,15 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": { + "machine": "renkforce_rf100_xl", + "position": "0" + }, + + "overrides": { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} From a61207ca0488d45ea2c8cdf324f7e471906ca457 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 3 Jan 2020 15:45:31 +0100 Subject: [PATCH 02/33] Release v3.0.3 of [dok-net RF100](https://github.com/dok-net/RF100) brings less retraction stringing and much better qualitiy for small objects/layers. --- resources/definitions/renkforce_rf100.def.json | 13 +++++-------- resources/definitions/renkforce_rf100_v2.def.json | 13 +++++-------- resources/definitions/renkforce_rf100_xl.def.json | 13 +++++-------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/resources/definitions/renkforce_rf100.def.json b/resources/definitions/renkforce_rf100.def.json index 0ef269886b..33be020a95 100644 --- a/resources/definitions/renkforce_rf100.def.json +++ b/resources/definitions/renkforce_rf100.def.json @@ -39,10 +39,10 @@ "value": "True" }, "cool_min_layer_time": { - "value": "5.0" + "value": "1.0" }, "cool_min_speed": { - "value": "10.0" + "value": "5.0" }, "infill_before_walls": { "value": "True" @@ -69,7 +69,7 @@ "value": "100" }, "machine_end_gcode": { - "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-2 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" + "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-4 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" @@ -93,7 +93,7 @@ "default_value": "Renkforce RF100" }, "machine_start_gcode": { - "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E4.0 ;extrude 4.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." + "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E6.0 ;extrude 6.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." }, "machine_width": { "value": "100" @@ -150,7 +150,7 @@ "value": "0.1" }, "retraction_amount": { - "value": "3.0" + "value": "5.0" }, "retraction_combing": { "default_value": "all" @@ -164,9 +164,6 @@ "retraction_min_travel": { "value": "1.5" }, - "retraction_speed": { - "value": "40.0" - }, "skin_overlap": { "value": "15.0" }, diff --git a/resources/definitions/renkforce_rf100_v2.def.json b/resources/definitions/renkforce_rf100_v2.def.json index 5467ff0ba8..c1dd9b003b 100644 --- a/resources/definitions/renkforce_rf100_v2.def.json +++ b/resources/definitions/renkforce_rf100_v2.def.json @@ -39,10 +39,10 @@ "value": "True" }, "cool_min_layer_time": { - "value": "5.0" + "value": "1.0" }, "cool_min_speed": { - "value": "10.0" + "value": "5.0" }, "infill_before_walls": { "value": "True" @@ -69,7 +69,7 @@ "value": "120" }, "machine_end_gcode": { - "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-2 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" + "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-4 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" @@ -93,7 +93,7 @@ "default_value": "Renkforce RF100 V2" }, "machine_start_gcode": { - "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E4.0 ;extrude 4.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." + "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E6.0 ;extrude 6.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." }, "machine_width": { "value": "120" @@ -150,7 +150,7 @@ "value": "0.1" }, "retraction_amount": { - "value": "3.0" + "value": "5.0" }, "retraction_combing": { "default_value": "all" @@ -164,9 +164,6 @@ "retraction_min_travel": { "value": "1.5" }, - "retraction_speed": { - "value": "40.0" - }, "skin_overlap": { "value": "15.0" }, diff --git a/resources/definitions/renkforce_rf100_xl.def.json b/resources/definitions/renkforce_rf100_xl.def.json index cf5bbcd006..f7707913e2 100644 --- a/resources/definitions/renkforce_rf100_xl.def.json +++ b/resources/definitions/renkforce_rf100_xl.def.json @@ -39,10 +39,10 @@ "value": "True" }, "cool_min_layer_time": { - "value": "5.0" + "value": "1.0" }, "cool_min_speed": { - "value": "10.0" + "value": "5.0" }, "infill_before_walls": { "value": "True" @@ -69,7 +69,7 @@ "value": "200" }, "machine_end_gcode": { - "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-2 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" + "default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-4 F300 ;move Z up a bit and retract filament even more\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG0 Z{machine_height} F1800 ;move the platform all the way down\nG28 X0 Y0 F1800 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" @@ -84,7 +84,7 @@ "default_value": "Renkforce RF100 XL" }, "machine_start_gcode": { - "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E4.0 ;extrude 4.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." + "default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG1 Z5.0 F1800 ;move Z to 5mm\nG28 X0 Y0 F1800 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstop\nG92 E0 ;zero the extruded length\nG1 F200 E6.0 ;extrude 6.0mm of feed stock to build pressure\nG1 Z5.0 F300 ;move the platform down 5mm\nG92 E0 ;zero the extruded length again\nG1 F1800\n;Put printing message on LCD screen\nM117 Printing..." }, "machine_width": { "value": "200" @@ -138,7 +138,7 @@ "value": "0.1" }, "retraction_amount": { - "value": "3.0" + "value": "5.0" }, "retraction_combing": { "default_value": "all" @@ -152,9 +152,6 @@ "retraction_min_travel": { "value": "1.5" }, - "retraction_speed": { - "value": "40.0" - }, "skin_overlap": { "value": "15.0" }, From 317c4aa559be938258c3e7c9595544465593ae12 Mon Sep 17 00:00:00 2001 From: PurpleHullPeas <39073039+PurpleHullPeas@users.noreply.github.com> Date: Tue, 7 Jan 2020 18:28:32 -0600 Subject: [PATCH 03/33] Adding Monoprice Mini Delta definition file. --- resources/definitions/mp_mini_delta.def.json | 80 ++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 resources/definitions/mp_mini_delta.def.json diff --git a/resources/definitions/mp_mini_delta.def.json b/resources/definitions/mp_mini_delta.def.json new file mode 100644 index 0000000000..1804f30b37 --- /dev/null +++ b/resources/definitions/mp_mini_delta.def.json @@ -0,0 +1,80 @@ +{ + "id": "mp_mini_delta", + "version": 2, + "name": "MP Mini Delta", + "inherits": "fdmprinter", + "metadata": { + "author": "MPMD Facebook Group", + "manufacturer": "Monoprice", + "category": "Other", + "file_formats": "text/x-gcode", + "platform": "mp_mini_delta_platform.stl", + "supports_usb_connection": true, + "has_machine_quality": false, + "visible": true, + "platform_offset": [0, 0, 0], + "has_materials": true, + "has_variants": false, + "has_machine_materials": false, + "has_variant_materials": false, + "preferred_quality_type": "normal", + "machine_extruder_trains": + { + "0": "mp_mini_delta_extruder_0" + } + }, + + "overrides": { + "machine_start_gcode": + { + "default_value": ";MPMD Basic Calibration Tutorial: \n; https://www.thingiverse.com/thing:3892011 \n; \n; If you want to put calibration values in your \n; Start Gcode, put them here. \n; \n;If on stock firmware, at minimum, consider adding \n;M665 R here since there is a firmware bug. \n; \n; Calibration part ends here \n; \nG90 ; switch to absolute positioning \nG92 E0 ; reset extrusion distance \nG1 E20 F200 ; purge 20mm of filament to prime nozzle. \nG92 E0 ; reset extrusion distance \nG4 S5 ; Pause for 5 seconds to allow time for removing extruded filament \nG28 ; start from home position \nG1 E-6 F900 ; retract 6mm of filament before starting the bed leveling process \nG92 E0 ; reset extrusion distance \nG4 S5 ; pause for 5 seconds to allow time for removing extruded filament \nG29 P2 Z0.28 ; Auto-level ; ADJUST Z higher or lower to set first layer height. Start with 0.02 adjustments. \nG1 Z30 ; raise Z 30mm to prepare for priming the nozzle \nG1 E5 F200 ; extrude 5mm of filament to help prime the nozzle just prior to the start of the print \nG92 E0 ; reset extrusion distance \nG4 S5 ; pause for 5 seconds to allow time for cleaning the nozzle and build plate if needed " + }, + "machine_end_gcode": + { + "default_value": "M107; \nM104 S0; turn off hotend heater \nM140 S0; turn off bed heater \nG91; Switch to use Relative Coordinates \nG1 E-2 F300; retract the filament a bit before lifting the nozzle to release some of the pressure \nG1 Z5 E-5 F4800; move nozzle up a bit and retract filament even more \nG28 X0; return to home positions so the nozzle is out of the way \nM84; turn off stepper motors \nG90; switch to absolute positioning \nM82; absolute extrusion mode" + }, + "gantry_height": { + "default_value": 120 + }, + "machine_width": { "default_value": 110 }, + "machine_depth": { "default_value": 110 }, + "machine_height": { "default_value": 120 }, + "machine_heated_bed": { "default_value": true }, + "machine_shape": { "default_value": "elliptic" }, + "machine_center_is_zero": { "default_value": true }, + "machine_nozzle_size": { + "default_value": 0.4, + "minimum_value": 0.10, + "maximum_value": 0.80 + }, + "layer_height": { + "default_value": 0.14, + "minimum_value": 0.04 + }, + "layer_height_0": { + "default_value": 0.21, + "minimum_value": 0.07 + }, + "line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" }, + "material_print_temperature_layer_0": { "value": "material_print_temperature + 5" }, + "material_bed_temperature_layer_0": { "value": "material_bed_temperature + 5" }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_max_feedrate_x": { "default_value": 150 }, + "machine_max_feedrate_y": { "default_value": 150 }, + "machine_max_feedrate_z": { "default_value": 150 }, + "machine_max_feedrate_e": { "default_value": 50 }, + "machine_max_acceleration_x": { "default_value": 800 }, + "machine_max_acceleration_y": { "default_value": 800 }, + "machine_max_acceleration_z": { "default_value": 800 }, + "machine_max_acceleration_e": { "default_value": 10000 }, + "machine_acceleration": { "default_value": 3000 }, + "machine_max_jerk_xy": { "default_value": 20 }, + "machine_max_jerk_z": { "default_value": 20 }, + "machine_max_jerk_e": { "default_value": 5}, + "retraction_amount": { "default_value": 4 }, + "retraction_speed": { "default_value": 50 }, + "retraction_hop_enabled": { "default_value": false }, + "retract_at_layer_change": { "default_value": true }, + "coasting_enable": { "default_value": true } + } +} \ No newline at end of file From 8e50f7ab95adbcb2f1284bf394b068df1f82c6c2 Mon Sep 17 00:00:00 2001 From: PurpleHullPeas <39073039+PurpleHullPeas@users.noreply.github.com> Date: Tue, 7 Jan 2020 18:29:52 -0600 Subject: [PATCH 04/33] Adding Monoprice Mini Delta Extruder File --- .../extruders/mp_mini_delta_extruder_0.def.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 resources/extruders/mp_mini_delta_extruder_0.def.json diff --git a/resources/extruders/mp_mini_delta_extruder_0.def.json b/resources/extruders/mp_mini_delta_extruder_0.def.json new file mode 100644 index 0000000000..2723aee4fd --- /dev/null +++ b/resources/extruders/mp_mini_delta_extruder_0.def.json @@ -0,0 +1,16 @@ +{ + "id": "mp_mini_delta_extruder_0", + "version": 2, + "name": "Extruder 0", + "inherits": "fdmextruder", + "metadata": { + "machine": "mp_mini_delta", + "position": "0" + }, + + "overrides": { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} From 473ff88009ce801e53d87ead4bae1e3d352b697c Mon Sep 17 00:00:00 2001 From: PurpleHullPeas <39073039+PurpleHullPeas@users.noreply.github.com> Date: Tue, 7 Jan 2020 18:31:10 -0600 Subject: [PATCH 05/33] Adding Monoprice Mini Delta platform image. --- resources/meshes/mp_mini_delta_platform.stl | Bin 0 -> 147484 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/meshes/mp_mini_delta_platform.stl diff --git a/resources/meshes/mp_mini_delta_platform.stl b/resources/meshes/mp_mini_delta_platform.stl new file mode 100644 index 0000000000000000000000000000000000000000..603cb26e22e70c111ea4381b0892c0d1733e7358 GIT binary patch literal 147484 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 literal 0 HcmV?d00001 From fa89841f5284a4de2d86a98f185d033ffdd7b872 Mon Sep 17 00:00:00 2001 From: PurpleHullPeas <39073039+PurpleHullPeas@users.noreply.github.com> Date: Tue, 7 Jan 2020 22:03:30 -0600 Subject: [PATCH 06/33] Removed gantry_height --- resources/definitions/mp_mini_delta.def.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/resources/definitions/mp_mini_delta.def.json b/resources/definitions/mp_mini_delta.def.json index 1804f30b37..aa0bc98101 100644 --- a/resources/definitions/mp_mini_delta.def.json +++ b/resources/definitions/mp_mini_delta.def.json @@ -33,9 +33,6 @@ { "default_value": "M107; \nM104 S0; turn off hotend heater \nM140 S0; turn off bed heater \nG91; Switch to use Relative Coordinates \nG1 E-2 F300; retract the filament a bit before lifting the nozzle to release some of the pressure \nG1 Z5 E-5 F4800; move nozzle up a bit and retract filament even more \nG28 X0; return to home positions so the nozzle is out of the way \nM84; turn off stepper motors \nG90; switch to absolute positioning \nM82; absolute extrusion mode" }, - "gantry_height": { - "default_value": 120 - }, "machine_width": { "default_value": 110 }, "machine_depth": { "default_value": 110 }, "machine_height": { "default_value": 120 }, @@ -77,4 +74,4 @@ "retract_at_layer_change": { "default_value": true }, "coasting_enable": { "default_value": true } } -} \ No newline at end of file +} From 1c54f4c592e7f42f310c1baeb4a88db7c8e6acc1 Mon Sep 17 00:00:00 2001 From: PurpleHullPeas <39073039+PurpleHullPeas@users.noreply.github.com> Date: Tue, 7 Jan 2020 22:17:54 -0600 Subject: [PATCH 07/33] Removing id field. --- resources/definitions/mp_mini_delta.def.json | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/definitions/mp_mini_delta.def.json b/resources/definitions/mp_mini_delta.def.json index aa0bc98101..57e2d7c04c 100644 --- a/resources/definitions/mp_mini_delta.def.json +++ b/resources/definitions/mp_mini_delta.def.json @@ -1,5 +1,4 @@ { - "id": "mp_mini_delta", "version": 2, "name": "MP Mini Delta", "inherits": "fdmprinter", From b2424a4f2cae800e877f3cea23d2a88ca3ec0bd1 Mon Sep 17 00:00:00 2001 From: PurpleHullPeas <39073039+PurpleHullPeas@users.noreply.github.com> Date: Tue, 7 Jan 2020 22:27:03 -0600 Subject: [PATCH 08/33] Removing id from MPMD extruder def. --- resources/extruders/mp_mini_delta_extruder_0.def.json | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/extruders/mp_mini_delta_extruder_0.def.json b/resources/extruders/mp_mini_delta_extruder_0.def.json index 2723aee4fd..b4eab5c7a2 100644 --- a/resources/extruders/mp_mini_delta_extruder_0.def.json +++ b/resources/extruders/mp_mini_delta_extruder_0.def.json @@ -1,5 +1,4 @@ { - "id": "mp_mini_delta_extruder_0", "version": 2, "name": "Extruder 0", "inherits": "fdmextruder", From 414b696b32cb9e3f5e5a326bdb33b7f7ac573dd8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jan 2020 11:04:42 +0100 Subject: [PATCH 09/33] Use the resetWorkspace for new project This makes it a bit cleaner as we don't have to copy functionality CURA-6627 --- resources/qml/MainWindow/ApplicationMenu.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/MainWindow/ApplicationMenu.qml b/resources/qml/MainWindow/ApplicationMenu.qml index 30e44d7d3b..72a371e2f5 100644 --- a/resources/qml/MainWindow/ApplicationMenu.qml +++ b/resources/qml/MainWindow/ApplicationMenu.qml @@ -127,8 +127,8 @@ Item icon: StandardIcon.Question onYes: { - CuraApplication.deleteAll(); - Cura.Actions.resetProfile.trigger(); + CuraApplication.resetWorkspace() + Cura.Actions.resetProfile.trigger() UM.Controller.setActiveStage("PrepareStage") } } From b6d1429eb779b4724ea186d7d9eaee75e99054c3 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jan 2020 15:27:30 +0100 Subject: [PATCH 10/33] Ensure that 3mf workspace reader loads aditional metadata CURA-6627 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 2e395c9efa..953980d0b7 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -4,7 +4,7 @@ from configparser import ConfigParser import zipfile import os -from typing import cast, Dict, List, Optional, Tuple +from typing import cast, Dict, List, Optional, Tuple, Any import xml.etree.ElementTree as ET @@ -732,7 +732,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader): base_file_name = os.path.basename(file_name) self.setWorkspaceName(base_file_name) - return nodes + + return nodes, self._loadMetadata(file_name) + + def _loadMetadata(self, file_name) -> Dict[str, Dict[str, Any]]: + archive = zipfile.ZipFile(file_name, "r") + import json + try: + return json.loads(archive.open("Cura/plugin_metadata.json").read().decode("utf-8")) + except KeyError: + # The plugin_metadata.json file doesn't exist + return dict() def _processQualityChanges(self, global_stack): if self._machine_info.quality_changes_info is None: From 6ae3172287a20cd2e54b22a95997aa10c7bfe29a Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Tue, 14 Jan 2020 15:40:38 +0100 Subject: [PATCH 11/33] Store metadata when writing the workspace --- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 33df0bfe90..953b0a57e6 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -73,11 +73,22 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): version_config_parser.write(version_file_string) archive.writestr(version_file, version_file_string.getvalue()) + self._writePluginMetadataToArchive(archive) + # Close the archive & reset states. archive.close() mesh_writer.setStoreArchive(False) return True + def _writePluginMetadataToArchive(self, archive): + file_name = "Cura/plugin_metadata.json" + + file_in_archive = zipfile.ZipInfo(file_name) + # For some reason we have to set the compress type of each file as well (it doesn't keep the type of the entire archive) + file_in_archive.compress_type = zipfile.ZIP_DEFLATED + import json + archive.writestr(file_in_archive, json.dumps(Application.getInstance()._workspace_metadata_storage.getAllData())) + ## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive. # \param container That follows the \type{ContainerInterface} to archive. # \param archive The archive to write to. From b8dbc1d1600c72f49ac7e9f781b0f5236afee201 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jan 2020 09:50:45 +0100 Subject: [PATCH 12/33] Store the data from each plugin in it's own folder CURA-6627 --- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 953b0a57e6..cd56e77ebd 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -81,13 +81,15 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): return True def _writePluginMetadataToArchive(self, archive): - file_name = "Cura/plugin_metadata.json" + file_name_template = "%s/plugin_metadata.json" - file_in_archive = zipfile.ZipInfo(file_name) - # For some reason we have to set the compress type of each file as well (it doesn't keep the type of the entire archive) - file_in_archive.compress_type = zipfile.ZIP_DEFLATED - import json - archive.writestr(file_in_archive, json.dumps(Application.getInstance()._workspace_metadata_storage.getAllData())) + for plugin_id, metadata in Application.getInstance()._workspace_metadata_storage.getAllData().items(): + file_name = file_name_template % plugin_id + file_in_archive = zipfile.ZipInfo(file_name) + # We have to set the compress type of each file as well (it doesn't keep the type of the entire archive) + file_in_archive.compress_type = zipfile.ZIP_DEFLATED + import json + archive.writestr(file_in_archive, json.dumps(metadata, separators = (", ", ": "), indent = 4)) ## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive. # \param container That follows the \type{ContainerInterface} to archive. From 4e0e0c033953e97325e37495cbe717866cfb6f95 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jan 2020 11:19:29 +0100 Subject: [PATCH 13/33] Add skipkeys flag to writing plugin metadata to workspace CURA-6627 --- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index cd56e77ebd..45d9db77f6 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -89,7 +89,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): # We have to set the compress type of each file as well (it doesn't keep the type of the entire archive) file_in_archive.compress_type = zipfile.ZIP_DEFLATED import json - archive.writestr(file_in_archive, json.dumps(metadata, separators = (", ", ": "), indent = 4)) + archive.writestr(file_in_archive, json.dumps(metadata, separators = (", ", ": "), indent = 4, skipkeys = True)) ## Helper function that writes ContainerStacks, InstanceContainers and DefinitionContainers to the archive. # \param container That follows the \type{ContainerInterface} to archive. From 46050f9618afffce36701530010cd663c03ce41b Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jan 2020 11:25:30 +0100 Subject: [PATCH 14/33] Use getter instead of protected attribute CURA-6627 --- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 45d9db77f6..08918e1c12 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -83,7 +83,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): def _writePluginMetadataToArchive(self, archive): file_name_template = "%s/plugin_metadata.json" - for plugin_id, metadata in Application.getInstance()._workspace_metadata_storage.getAllData().items(): + for plugin_id, metadata in Application.getInstance().getWorkspaceMetadataStorage().getAllData().items(): file_name = file_name_template % plugin_id file_in_archive = zipfile.ZipInfo(file_name) # We have to set the compress type of each file as well (it doesn't keep the type of the entire archive) From 712cebcdd2d11310199b62e5e2aa0a2ded712515 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 16 Jan 2020 11:43:07 +0100 Subject: [PATCH 15/33] Let 3mf workspace reader read from files per plugin CURA-6627 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 953980d0b7..7d4efa6e74 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -737,12 +737,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader): def _loadMetadata(self, file_name) -> Dict[str, Dict[str, Any]]: archive = zipfile.ZipFile(file_name, "r") + + metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")] import json - try: - return json.loads(archive.open("Cura/plugin_metadata.json").read().decode("utf-8")) - except KeyError: - # The plugin_metadata.json file doesn't exist - return dict() + + result = dict() + + for metadata_file in metadata_files: + try: + plugin_id = metadata_file.split("/")[0] + result[plugin_id] = json.loads(archive.open("Cura/plugin_metadata.json").read().decode("utf-8")) + except Exception: + Logger.logException("w", "Unable to retrieve metadata for %s", metadata_file) + + return result def _processQualityChanges(self, global_stack): if self._machine_info.quality_changes_info is None: From 66105e8a3a8cfc54410d57a48eb0bfc81715417f Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Mon, 20 Jan 2020 09:48:23 +0100 Subject: [PATCH 16/33] Add invalid imports checker Since plugins.* is not available on the PATH for some builds, they should not be used. Relative imports are preferred --- docker/build.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docker/build.sh b/docker/build.sh index 5b035ca08a..9bff78c2a3 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -13,6 +13,19 @@ export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}" cd "${PROJECT_DIR}" +# Check for plugins.* import statements. These imports may work when running from source, +# but will fail in some build types (linux and mac) +GREP_OUTPUT=$(grep -Ern "^\s*(from plugins|import plugins)" --include \*.py "${PROJECT_DIR}" || true) +echo "$GREP_OUTPUT" + +if [ -z "$GREP_OUTPUT" ] +then + echo "invalid imports checker: OK" +else + echo "error: sources contain invalid imports. Use relative imports when referencing plugin source files" + exit 1 +fi + # # Clone Uranium and set PYTHONPATH first # From bee641da5ac08477e0956f181dd80b01a0a1be68 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 20 Jan 2020 15:30:23 +0100 Subject: [PATCH 17/33] Remove local import of json CURA-7005 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 7d4efa6e74..e92b60976b 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -4,6 +4,7 @@ from configparser import ConfigParser import zipfile import os +import json from typing import cast, Dict, List, Optional, Tuple, Any import xml.etree.ElementTree as ET @@ -739,7 +740,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): archive = zipfile.ZipFile(file_name, "r") metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")] - import json result = dict() From 52ce10639932cc9da4c471864bdb315e6f33a295 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 20 Jan 2020 15:37:57 +0100 Subject: [PATCH 18/33] Change _loadMetadata to be static CURA-6627 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index e92b60976b..ba169f3dab 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -736,7 +736,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return nodes, self._loadMetadata(file_name) - def _loadMetadata(self, file_name) -> Dict[str, Dict[str, Any]]: + @staticmethod + def _loadMetadata(file_name: str) -> Dict[str, Dict[str, Any]]: archive = zipfile.ZipFile(file_name, "r") metadata_files = [name for name in archive.namelist() if name.endswith("plugin_metadata.json")] From 06ccd882e1395909cfa2d66be6310270bcfc94bb Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Mon, 20 Jan 2020 16:03:56 +0100 Subject: [PATCH 19/33] Add missing typing CURA-6627 --- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 3 ++- plugins/3MFWriter/ThreeMFWriter.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 08918e1c12..10a21f445b 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -80,7 +80,8 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): mesh_writer.setStoreArchive(False) return True - def _writePluginMetadataToArchive(self, archive): + @staticmethod + def _writePluginMetadataToArchive(archive: zipfile.ZipFile) -> None: file_name_template = "%s/plugin_metadata.json" for plugin_id, metadata in Application.getInstance().getWorkspaceMetadataStorage().getAllData().items(): diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 640aabd30c..9860804542 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -1,5 +1,6 @@ # Copyright (c) 2015 Ultimaker B.V. # Uranium is released under the terms of the LGPLv3 or higher. +from typing import Optional from UM.Mesh.MeshWriter import MeshWriter from UM.Math.Vector import Vector @@ -40,7 +41,7 @@ class ThreeMFWriter(MeshWriter): } self._unit_matrix_string = self._convertMatrixToString(Matrix()) - self._archive = None + self._archive = None # type: Optional[zipfile.ZipFile] self._store_archive = False def _convertMatrixToString(self, matrix): From b830a6faa3f83814b795fdca41bb73b90e48ac68 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Mon, 20 Jan 2020 16:22:02 +0100 Subject: [PATCH 20/33] Rewrite invalid imports checker to Python Makes it consistent with other checkers we already have --- cmake/CuraTests.cmake | 7 ++++ docker/build.sh | 11 ------ scripts/check_invalid_imports.py | 60 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 scripts/check_invalid_imports.py diff --git a/cmake/CuraTests.cmake b/cmake/CuraTests.cmake index b1d3e0ddc4..c76019d310 100644 --- a/cmake/CuraTests.cmake +++ b/cmake/CuraTests.cmake @@ -56,6 +56,13 @@ function(cura_add_test) endif() endfunction() +#Add test for whether the shortcut alt-keys are unique in every translation. +add_test( + NAME "invalid-imports" + COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) + cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}") file(GLOB_RECURSE _plugins plugins/*/__init__.py) diff --git a/docker/build.sh b/docker/build.sh index 9bff78c2a3..a500663c64 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -13,18 +13,7 @@ export PKG_CONFIG_PATH="${CURA_BUILD_ENV_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}" cd "${PROJECT_DIR}" -# Check for plugins.* import statements. These imports may work when running from source, -# but will fail in some build types (linux and mac) -GREP_OUTPUT=$(grep -Ern "^\s*(from plugins|import plugins)" --include \*.py "${PROJECT_DIR}" || true) -echo "$GREP_OUTPUT" -if [ -z "$GREP_OUTPUT" ] -then - echo "invalid imports checker: OK" -else - echo "error: sources contain invalid imports. Use relative imports when referencing plugin source files" - exit 1 -fi # # Clone Uranium and set PYTHONPATH first diff --git a/scripts/check_invalid_imports.py b/scripts/check_invalid_imports.py new file mode 100644 index 0000000000..121184e739 --- /dev/null +++ b/scripts/check_invalid_imports.py @@ -0,0 +1,60 @@ +import os +import re +import sys +from pathlib import Path + +""" +Run this file with the Cura project root as the working directory +""" + +class InvalidImportsChecker: + # compile regex + REGEX = re.compile(r"^\s*(from plugins|import plugins)") + + def check(self): + """ Checks for invalid imports + + :return: True if checks passed, False when the test fails + """ + cwd = os.getcwd() + cura_result = checker.check_dir(os.path.join(cwd, "cura")) + plugins_result = checker.check_dir(os.path.join(cwd, "plugins")) + result = cura_result and plugins_result + if not result: + print("error: sources contain invalid imports. Use relative imports when referencing plugin source files") + + return result + + def check_dir(self, root_dir: str) -> bool: + """ Checks a directory for invalid imports + + :return: True if checks passed, False when the test fails + """ + passed = True + for path_like in Path(root_dir).rglob('*.py'): + if not self.check_file(str(path_like)): + passed = False + + return passed + + def check_file(self, file_path): + """ Checks a file for invalid imports + + :return: True if checks passed, False when the test fails + """ + passed = True + with open(file_path, 'r', encoding = "utf-8") as inputFile: + # loop through each line in file + for line_i, line in enumerate(inputFile, 1): + # check if we have a regex match + match = self.REGEX.search(line) + if match: + path = os.path.relpath(file_path) + print("{path}:{line_i}:{match}".format(path=path, line_i=line_i, match=match.group(1))) + passed = False + return passed + + +if __name__ == "__main__": + checker = InvalidImportsChecker() + sys.exit(0 if checker.check() else 1) From 2e20bf6a983dfd4fcfad56aecb3ce41f53621b31 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Mon, 20 Jan 2020 16:33:03 +0100 Subject: [PATCH 21/33] Update invalid imports checker documentation Makes it consistent with other checkers we already have --- cmake/CuraTests.cmake | 2 +- scripts/check_invalid_imports.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmake/CuraTests.cmake b/cmake/CuraTests.cmake index c76019d310..251bec5781 100644 --- a/cmake/CuraTests.cmake +++ b/cmake/CuraTests.cmake @@ -56,7 +56,7 @@ function(cura_add_test) endif() endfunction() -#Add test for whether the shortcut alt-keys are unique in every translation. +#Add test for import statements which are not compatible with all builds add_test( NAME "invalid-imports" COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py diff --git a/scripts/check_invalid_imports.py b/scripts/check_invalid_imports.py index 121184e739..ba21b9f822 100644 --- a/scripts/check_invalid_imports.py +++ b/scripts/check_invalid_imports.py @@ -5,8 +5,13 @@ from pathlib import Path """ Run this file with the Cura project root as the working directory +Checks for invalid imports. When importing from plugins, there will be no problems when running from source, +but for some build types the plugins dir is not on the path, so relative imports should be used instead. eg: +from ..UltimakerCloudScope import UltimakerCloudScope <-- OK +import plugins.Toolbox.src ... <-- NOT OK """ + class InvalidImportsChecker: # compile regex REGEX = re.compile(r"^\s*(from plugins|import plugins)") From 1287ebdc519954339ac7bc622200b847fc5341f2 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Tue, 21 Jan 2020 14:43:02 +0100 Subject: [PATCH 22/33] Change backgroundColor and margin for licenseDialog CURA-7129 --- plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml index 3e7cdc9df8..5d29e70f97 100644 --- a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml @@ -20,6 +20,8 @@ UM.Dialog minimumHeight: UM.Theme.getSize("license_window_minimum").height width: minimumWidth height: minimumHeight + backgroundColor: UM.Theme.getColor("main_background") + margin: screenScaleFactor * 10 Item { @@ -27,7 +29,6 @@ UM.Dialog UM.I18nCatalog{id: catalog; name: "cura"} - Label { id: licenseHeader From b3812a36300dc54a7201981e7665a4ce966a3127 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Tue, 21 Jan 2020 17:07:21 +0100 Subject: [PATCH 23/33] Add icon to licenseDialog (clowd flow) CURA-7129 --- .../qml/dialogs/ToolboxLicenseDialog.qml | 47 +++++++++++++++---- .../src/CloudSync/DownloadPresenter.py | 12 ++++- plugins/Toolbox/src/CloudSync/LicenseModel.py | 23 ++++++--- .../Toolbox/src/CloudSync/LicensePresenter.py | 10 ++-- .../Toolbox/src/CloudSync/SyncOrchestrator.py | 4 +- 5 files changed, 72 insertions(+), 24 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml index 5d29e70f97..dba4a5ba58 100644 --- a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml @@ -5,6 +5,7 @@ import QtQuick 2.10 import QtQuick.Dialogs 1.1 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 import QtQuick.Controls.Styles 1.4 // TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles @@ -23,7 +24,7 @@ UM.Dialog backgroundColor: UM.Theme.getColor("main_background") margin: screenScaleFactor * 10 - Item + ColumnLayout { anchors.fill: parent @@ -32,20 +33,48 @@ UM.Dialog Label { id: licenseHeader - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - text: licenseModel.headerText + Layout.fillWidth: true + text: catalog.i18nc("@label", "You need to accept the license to install the package") wrapMode: Text.Wrap renderType: Text.NativeRendering } + + Row { + id: packageRow + + anchors.left: parent.left + anchors.right: parent.right + height: childrenRect.height + + + Image + { + id: icon + width: 30 * screenScaleFactor + height: width + fillMode: Image.PreserveAspectFit + source: licenseModel.iconUrl || "../../images/logobot.svg" + mipmap: true + } + + Label + { + id: packageName + text: licenseModel.packageName + anchors.verticalCenter: icon.verticalCenter + height: contentHeight + wrapMode: Text.Wrap + renderType: Text.NativeRendering + } + + + } + TextArea { id: licenseText - anchors.top: licenseHeader.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true + Layout.fillHeight: true anchors.topMargin: UM.Theme.getSize("default_margin").height readOnly: true text: licenseModel.licenseText diff --git a/plugins/Toolbox/src/CloudSync/DownloadPresenter.py b/plugins/Toolbox/src/CloudSync/DownloadPresenter.py index f19cac047a..d79d031949 100644 --- a/plugins/Toolbox/src/CloudSync/DownloadPresenter.py +++ b/plugins/Toolbox/src/CloudSync/DownloadPresenter.py @@ -62,7 +62,8 @@ class DownloadPresenter: "received": 0, "total": 1, # make sure this is not considered done yet. Also divByZero-safe "file_written": None, - "request_data": request_data + "request_data": request_data, + "package_model": item } self._started = True @@ -128,7 +129,14 @@ class DownloadPresenter: if not item["file_written"]: return False - success_items = {package_id : value["file_written"] for package_id, value in self._progress.items()} + success_items = { + package_id: + { + "package_path": value["file_written"], + "icon_url": value["package_model"]["icon_url"] + } + for package_id, value in self._progress.items() + } error_items = [package_id for package_id in self._error] self._progress_message.hide() diff --git a/plugins/Toolbox/src/CloudSync/LicenseModel.py b/plugins/Toolbox/src/CloudSync/LicenseModel.py index c3b5ee5d31..66ea6ca5da 100644 --- a/plugins/Toolbox/src/CloudSync/LicenseModel.py +++ b/plugins/Toolbox/src/CloudSync/LicenseModel.py @@ -7,8 +7,9 @@ catalog = i18nCatalog("cura") # Model for the ToolboxLicenseDialog class LicenseModel(QObject): dialogTitleChanged = pyqtSignal() - headerChanged = pyqtSignal() + packageNameChanged = pyqtSignal() licenseTextChanged = pyqtSignal() + iconChanged = pyqtSignal() def __init__(self) -> None: super().__init__() @@ -16,21 +17,29 @@ class LicenseModel(QObject): self._current_page_idx = 0 self._page_count = 1 self._dialogTitle = "" - self._header_text = "" self._license_text = "" self._package_name = "" + self._icon_url = "" @pyqtProperty(str, notify=dialogTitleChanged) def dialogTitle(self) -> str: return self._dialogTitle - @pyqtProperty(str, notify=headerChanged) - def headerText(self) -> str: - return self._header_text + @pyqtProperty(str, notify=packageNameChanged) + def packageName(self) -> str: + return self._package_name def setPackageName(self, name: str) -> None: - self._header_text = name + ": " + catalog.i18nc("@label", "This plugin contains a license.\nYou need to accept this license to install this plugin.\nDo you agree with the terms below?") - self.headerChanged.emit() + self._package_name = name + self.packageNameChanged.emit() + + @pyqtProperty(str, notify=iconChanged) + def iconUrl(self) -> str: + return self._icon_url + + def setIconUrl(self, url: str): + self._icon_url = url + self.iconChanged.emit() @pyqtProperty(str, notify=licenseTextChanged) def licenseText(self) -> str: diff --git a/plugins/Toolbox/src/CloudSync/LicensePresenter.py b/plugins/Toolbox/src/CloudSync/LicensePresenter.py index cefe6f4037..5abf4a3b48 100644 --- a/plugins/Toolbox/src/CloudSync/LicensePresenter.py +++ b/plugins/Toolbox/src/CloudSync/LicensePresenter.py @@ -34,7 +34,7 @@ class LicensePresenter(QObject): ## Show a license dialog for multiple packages where users can read a license and accept or decline them # \param plugin_path: Root directory of the Toolbox plugin # \param packages: Dict[package id, file path] - def present(self, plugin_path: str, packages: Dict[str, str]) -> None: + def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None: path = os.path.join(plugin_path, self._compatibility_dialog_path) self._initState(packages) @@ -60,14 +60,15 @@ class LicensePresenter(QObject): self._package_models[self._current_package_idx]["accepted"] = False self._checkNextPage() - def _initState(self, packages: Dict[str, str]) -> None: + def _initState(self, packages: Dict[str, Dict[str, str]]) -> None: self._package_models = [ { "package_id" : package_id, - "package_path" : package_path, + "package_path" : item["package_path"], + "icon_url" : item["icon_url"], "accepted" : None #: None: no answer yet } - for package_id, package_path in packages.items() + for package_id, item in packages.items() ] def _presentCurrentPackage(self) -> None: @@ -80,6 +81,7 @@ class LicensePresenter(QObject): self._license_model.setCurrentPageIdx(self._current_package_idx) self._license_model.setPackageName(package_model["package_id"]) + self._license_model.setIconUrl(package_model["icon_url"]) self._license_model.setLicenseText(license_content) if self._dialog: self._dialog.open() # Does nothing if already open diff --git a/plugins/Toolbox/src/CloudSync/SyncOrchestrator.py b/plugins/Toolbox/src/CloudSync/SyncOrchestrator.py index e97bdbcbc4..4b8b3cf7f9 100644 --- a/plugins/Toolbox/src/CloudSync/SyncOrchestrator.py +++ b/plugins/Toolbox/src/CloudSync/SyncOrchestrator.py @@ -63,9 +63,9 @@ class SyncOrchestrator(Extension): self._download_presenter.download(mutations) ## Called when a set of packages have finished downloading - # \param success_items: Dict[package_id, file_path] + # \param success_items: Dict[package_id, Dict[str, str]] # \param error_items: List[package_id] - def _onDownloadFinished(self, success_items: Dict[str, str], error_items: List[str]) -> None: + def _onDownloadFinished(self, success_items: Dict[str, Dict[str, str]], error_items: List[str]) -> None: if error_items: message = i18n_catalog.i18nc("@info:generic", "{} plugins failed to download".format(len(error_items))) self._showErrorMessage(message) From 2f3cf3c493698834d9ee7cbe8c7f5c220c4610e2 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Tue, 21 Jan 2020 17:22:33 +0100 Subject: [PATCH 24/33] LicenseDialog layout tweaking CURA-7129 --- .../qml/dialogs/ToolboxLicenseDialog.qml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml index dba4a5ba58..67f850ca9e 100644 --- a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml @@ -27,6 +27,7 @@ UM.Dialog ColumnLayout { anchors.fill: parent + spacing: UM.Theme.getSize("thick_margin").height UM.I18nCatalog{id: catalog; name: "cura"} @@ -45,22 +46,24 @@ UM.Dialog anchors.left: parent.left anchors.right: parent.right height: childrenRect.height - + spacing: UM.Theme.getSize("default_margin").width + leftPadding: UM.Theme.getSize("narrow_margin").width Image { - id: icon - width: 30 * screenScaleFactor - height: width - fillMode: Image.PreserveAspectFit - source: licenseModel.iconUrl || "../../images/logobot.svg" - mipmap: true + id: icon + width: 30 * screenScaleFactor + height: width + fillMode: Image.PreserveAspectFit + source: licenseModel.iconUrl || "../../images/logobot.svg" + mipmap: true } Label { id: packageName text: licenseModel.packageName + font.bold: true anchors.verticalCenter: icon.verticalCenter height: contentHeight wrapMode: Text.Wrap From 3b534ea986ae1e3b3161b39c1a037d197cc403ce Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Wed, 22 Jan 2020 10:54:05 +0100 Subject: [PATCH 25/33] Add icon to licenseDialog (Toolbox flow) CURA-7129 --- plugins/Toolbox/src/Toolbox.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index e0d04bed5b..4835651b99 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -154,10 +154,11 @@ class Toolbox(QObject, Extension): def getLicenseDialogPluginFileLocation(self) -> str: return self._license_dialog_plugin_file_location - def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str) -> None: + def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str, icon_url: str) -> None: # Set page 1/1 when opening the dialog for a single package self._license_model.setCurrentPageIdx(0) self._license_model.setPageCount(1) + self._license_model.setIconUrl(icon_url) self._license_model.setPackageName(plugin_name) self._license_model.setLicenseText(license_content) @@ -670,14 +671,17 @@ class Toolbox(QObject, Extension): return license_content = self._package_manager.getPackageLicense(file_path) + package_id = package_info["package_id"] if license_content is not None: - self.openLicenseDialog(package_info["package_id"], license_content, file_path) + # get the icon url for package_id, make sure the result is a string, never None + icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or "" + self.openLicenseDialog(package_id, license_content, file_path, icon_url) return - package_id = self.install(file_path) - if package_id != package_info["package_id"]: - Logger.error("Installed package {} does not match {}".format(package_id, package_info["package_id"])) - self.subscribe(package_id) + installed_id = self.install(file_path) + if installed_id != package_id: + Logger.error("Installed package {} does not match {}".format(installed_id, package_id)) + self.subscribe(installed_id) # Getter & Setters for Properties: # -------------------------------------------------------------------------- @@ -699,14 +703,14 @@ class Toolbox(QObject, Extension): def isDownloading(self) -> bool: return self._is_downloading - def setActivePackage(self, package: Dict[str, Any]) -> None: + def setActivePackage(self, package: QObject) -> None: if self._active_package != package: self._active_package = package self.activePackageChanged.emit() ## The active package is the package that is currently being downloaded @pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged) - def activePackage(self) -> Optional[Dict[str, Any]]: + def activePackage(self) -> Optional[QObject]: return self._active_package def setViewCategory(self, category: str = "plugin") -> None: From 95def2850d4419cf56b58edcf066ffac6397245f Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Wed, 22 Jan 2020 11:17:03 +0100 Subject: [PATCH 26/33] Make license decline button text context-dependant CURA-7129 --- .../resources/qml/dialogs/ToolboxLicenseDialog.qml | 4 ++-- plugins/Toolbox/src/CloudSync/LicenseModel.py | 14 +++++++++++++- plugins/Toolbox/src/CloudSync/LicensePresenter.py | 6 ++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml index 67f850ca9e..c37bb099d0 100644 --- a/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/ToolboxLicenseDialog.qml @@ -90,7 +90,7 @@ UM.Dialog leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width - text: catalog.i18nc("@button", "Agree") + text: licenseModel.acceptButtonText onClicked: { handler.onLicenseAccepted() } } ] @@ -100,7 +100,7 @@ UM.Dialog Cura.SecondaryButton { id: declineButton - text: catalog.i18nc("@button", "Decline and remove from account") + text: licenseModel.declineButtonText onClicked: { handler.onLicenseDeclined() } } ] diff --git a/plugins/Toolbox/src/CloudSync/LicenseModel.py b/plugins/Toolbox/src/CloudSync/LicenseModel.py index 66ea6ca5da..bb62cb8abd 100644 --- a/plugins/Toolbox/src/CloudSync/LicenseModel.py +++ b/plugins/Toolbox/src/CloudSync/LicenseModel.py @@ -6,12 +6,15 @@ catalog = i18nCatalog("cura") # Model for the ToolboxLicenseDialog class LicenseModel(QObject): + DEFAULT_DECLINE_BUTTON_TEXT = catalog.i18nc("@button", "Decline") + ACCEPT_BUTTON_TEXT = catalog.i18nc("@button", "Agree") + dialogTitleChanged = pyqtSignal() packageNameChanged = pyqtSignal() licenseTextChanged = pyqtSignal() iconChanged = pyqtSignal() - def __init__(self) -> None: + def __init__(self, decline_button_text: str = DEFAULT_DECLINE_BUTTON_TEXT) -> None: super().__init__() self._current_page_idx = 0 @@ -20,6 +23,15 @@ class LicenseModel(QObject): self._license_text = "" self._package_name = "" self._icon_url = "" + self._decline_button_text = decline_button_text + + @pyqtProperty(str, constant = True) + def acceptButtonText(self): + return self.ACCEPT_BUTTON_TEXT + + @pyqtProperty(str, constant = True) + def declineButtonText(self): + return self._decline_button_text @pyqtProperty(str, notify=dialogTitleChanged) def dialogTitle(self) -> str: diff --git a/plugins/Toolbox/src/CloudSync/LicensePresenter.py b/plugins/Toolbox/src/CloudSync/LicensePresenter.py index 5abf4a3b48..0982bb4162 100644 --- a/plugins/Toolbox/src/CloudSync/LicensePresenter.py +++ b/plugins/Toolbox/src/CloudSync/LicensePresenter.py @@ -17,6 +17,7 @@ class LicensePresenter(QObject): def __init__(self, app: CuraApplication) -> None: super().__init__() + self._catalog = i18nCatalog("cura") self._dialog = None # type: Optional[QObject] self._package_manager = app.getPackageManager() # type: PackageManager # Emits List[Dict[str, [Any]] containing for example @@ -25,7 +26,8 @@ class LicensePresenter(QObject): self._current_package_idx = 0 self._package_models = [] # type: List[Dict] - self._license_model = LicenseModel() # type: LicenseModel + decline_button_text = self._catalog.i18nc("@button", "Decline and remove from account") + self._license_model = LicenseModel(decline_button_text=decline_button_text) # type: LicenseModel self._app = app @@ -42,7 +44,7 @@ class LicensePresenter(QObject): if self._dialog is None: context_properties = { - "catalog": i18nCatalog("cura"), + "catalog": self._catalog, "licenseModel": self._license_model, "handler": self } From 1324969dc251ed06f46a3c5852423c6a125b38a9 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Wed, 22 Jan 2020 11:50:22 +0100 Subject: [PATCH 27/33] Add some documentation for HttpRequestScope --- plugins/Toolbox/src/UltimakerCloudScope.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Toolbox/src/UltimakerCloudScope.py b/plugins/Toolbox/src/UltimakerCloudScope.py index f7707957e6..c0bd0eca93 100644 --- a/plugins/Toolbox/src/UltimakerCloudScope.py +++ b/plugins/Toolbox/src/UltimakerCloudScope.py @@ -6,6 +6,8 @@ from cura.API import Account from cura.CuraApplication import CuraApplication +## Add a Authorization header to the request for Ultimaker Cloud Api requests. +# When the user is not logged in or a token is not available, a warning will be logged class UltimakerCloudScope(DefaultUserAgentScope): def __init__(self, application: CuraApplication): super().__init__(application) From b25d6360ab257a6c74ddada197e48bdd162b0f70 Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Wed, 22 Jan 2020 11:53:48 +0100 Subject: [PATCH 28/33] More documentation for UltimakerCloudScope --- plugins/Toolbox/src/UltimakerCloudScope.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Toolbox/src/UltimakerCloudScope.py b/plugins/Toolbox/src/UltimakerCloudScope.py index c0bd0eca93..14583d7d59 100644 --- a/plugins/Toolbox/src/UltimakerCloudScope.py +++ b/plugins/Toolbox/src/UltimakerCloudScope.py @@ -8,6 +8,7 @@ from cura.CuraApplication import CuraApplication ## Add a Authorization header to the request for Ultimaker Cloud Api requests. # When the user is not logged in or a token is not available, a warning will be logged +# Also add the user agent headers (see DefaultUserAgentScope) class UltimakerCloudScope(DefaultUserAgentScope): def __init__(self, application: CuraApplication): super().__init__(application) From 87fb0d7df3881b40d103323e2c4fb5769a1c03a8 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 23 Jan 2020 09:28:28 +0100 Subject: [PATCH 29/33] Fix incorrect read location for plugin metadata CURA-6627 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index ba169f3dab..be90f02d37 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -747,7 +747,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): for metadata_file in metadata_files: try: plugin_id = metadata_file.split("/")[0] - result[plugin_id] = json.loads(archive.open("Cura/plugin_metadata.json").read().decode("utf-8")) + result[plugin_id] = json.loads(archive.open("%s/plugin_metadata.json" % plugin_id).read().decode("utf-8")) except Exception: Logger.logException("w", "Unable to retrieve metadata for %s", metadata_file) From a02e8d3b587da570f8bcc805924f32ae250c7e7e Mon Sep 17 00:00:00 2001 From: Nino van Hooff Date: Fri, 24 Jan 2020 10:36:36 +0100 Subject: [PATCH 30/33] Use display_name for the package name in the license dialog Implemented for both CloudSync and Toolbox --- plugins/Toolbox/src/CloudSync/LicensePresenter.py | 3 ++- plugins/Toolbox/src/Toolbox.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/Toolbox/src/CloudSync/LicensePresenter.py b/plugins/Toolbox/src/CloudSync/LicensePresenter.py index 0982bb4162..74fc2a3099 100644 --- a/plugins/Toolbox/src/CloudSync/LicensePresenter.py +++ b/plugins/Toolbox/src/CloudSync/LicensePresenter.py @@ -75,6 +75,7 @@ class LicensePresenter(QObject): def _presentCurrentPackage(self) -> None: package_model = self._package_models[self._current_package_idx] + package_info = self._package_manager.getPackageInfo(package_model["package_path"]) license_content = self._package_manager.getPackageLicense(package_model["package_path"]) if license_content is None: # Implicitly accept when there is no license @@ -82,7 +83,7 @@ class LicensePresenter(QObject): return self._license_model.setCurrentPageIdx(self._current_package_idx) - self._license_model.setPackageName(package_model["package_id"]) + self._license_model.setPackageName(package_info["display_name"]) self._license_model.setIconUrl(package_model["icon_url"]) self._license_model.setLicenseText(license_content) if self._dialog: diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 4835651b99..72e1f59615 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -675,7 +675,7 @@ class Toolbox(QObject, Extension): if license_content is not None: # get the icon url for package_id, make sure the result is a string, never None icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or "" - self.openLicenseDialog(package_id, license_content, file_path, icon_url) + self.openLicenseDialog(package_info["display_name"], license_content, file_path, icon_url) return installed_id = self.install(file_path) From bd18a539e26aca452ec606741337a8364889cec5 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 24 Jan 2020 14:04:27 +0100 Subject: [PATCH 31/33] Only update position of label when it's visible This prevents unneeded re-rendering --- resources/qml/Toolbar.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/qml/Toolbar.qml b/resources/qml/Toolbar.qml index c2a70143c3..ab32bff619 100644 --- a/resources/qml/Toolbar.qml +++ b/resources/qml/Toolbar.qml @@ -202,8 +202,9 @@ Item // dragging a tool handle. Rectangle { - x: -base.x + base.mouseX + UM.Theme.getSize("default_margin").width - y: -base.y + base.mouseY + UM.Theme.getSize("default_margin").height + id: toolInfo + x: visible ? -base.x + base.mouseX + UM.Theme.getSize("default_margin").width: 0 + y: visible ? -base.y + base.mouseY + UM.Theme.getSize("default_margin").height: 0 width: toolHint.width + UM.Theme.getSize("default_margin").width height: toolHint.height; From c97ce6fafbfe987a875b984c7489f0b58a86a6ad Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Fri, 24 Jan 2020 14:12:03 +0100 Subject: [PATCH 32/33] Make the sentry_env a bit smarter --- cura_app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cura_app.py b/cura_app.py index cb97792662..5184a5ab8b 100755 --- a/cura_app.py +++ b/cura_app.py @@ -29,9 +29,13 @@ parser.add_argument("--debug", known_args = vars(parser.parse_known_args()[0]) if with_sentry_sdk: - sentry_env = "production" - if ApplicationMetadata.CuraVersion == "master": + sentry_env = "unknown" # Start off with a "IDK" + if hasattr(sys, "frozen"): + sentry_env = "production" # A frozen build is a "real" distribution. + elif ApplicationMetadata.CuraVersion == "master": sentry_env = "development" + elif "beta" in ApplicationMetadata.CuraVersion or "BETA" in ApplicationMetadata.CuraVersion: + sentry_env = "beta" try: if ApplicationMetadata.CuraVersion.split(".")[2] == "99": sentry_env = "nightly" From db41a83aa6d555d31a3273ae408fdc3c8ba12159 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 24 Jan 2020 16:13:15 +0100 Subject: [PATCH 33/33] Fix mix of whitespace styles: Use spaces instead of tabs Contributes to issue CURA-7140. --- resources/definitions/renkforce_rf100.def.json | 8 ++++---- resources/definitions/renkforce_rf100_v2.def.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/definitions/renkforce_rf100.def.json b/resources/definitions/renkforce_rf100.def.json index 33be020a95..8ebd9c02fe 100644 --- a/resources/definitions/renkforce_rf100.def.json +++ b/resources/definitions/renkforce_rf100.def.json @@ -77,10 +77,10 @@ "machine_head_with_fans_polygon": { "default_value": [ - [-26, -27], - [38, -27], - [38, 55], - [-26, 55] + [-26, -27], + [38, -27], + [38, 55], + [-26, 55] ] }, "gantry_height": { diff --git a/resources/definitions/renkforce_rf100_v2.def.json b/resources/definitions/renkforce_rf100_v2.def.json index c1dd9b003b..bd012eff06 100644 --- a/resources/definitions/renkforce_rf100_v2.def.json +++ b/resources/definitions/renkforce_rf100_v2.def.json @@ -77,10 +77,10 @@ "machine_head_with_fans_polygon": { "default_value": [ - [-26, -27], - [38, -27], - [38, 55], - [-26, 55] + [-26, -27], + [38, -27], + [38, 55], + [-26, 55] ] }, "gantry_height": {