From a0d2d6fb620eecc2d56b05666f40b7b1f9ce6fec Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 28 Jan 2025 09:02:10 +0100 Subject: [PATCH 01/55] Add setting for retract/unretract during travel move CURA-11978 --- resources/definitions/fdmprinter.def.json | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index c1392e57ba..2425df01b1 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4278,6 +4278,32 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "retraction_during_travel_ratio": + { + "label": "Retraction During Travel Move", + "description": "The ratio of retraction performed during the travel move, with the remainder completed while the nozzle is stationary, before traveling", + "unit": "%", + "type": "float", + "default_value": 0, + "minimum_value": 0, + "maximum_value": 100, + "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"", + "settable_per_mesh": false, + "settable_per_extruder": true + }, + "prime_during_travel_ratio": + { + "label": "Prime During Travel Move", + "description": "The ratio of priming performed during the travel move, with the remainder completed while the nozzle is stationary, after traveling", + "unit": "%", + "type": "float", + "default_value": 0, + "minimum_value": 0, + "maximum_value": 100, + "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "retraction_speed": { "label": "Retraction Speed", From 850228498792684a4b282d40dedeea52bbafbc26 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 5 Feb 2025 10:33:51 +0100 Subject: [PATCH 02/55] Disable retract during travel for printers that handle retraction CURA-11978 --- resources/definitions/fdmprinter.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 1323f93a73..cf43fde121 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4300,7 +4300,7 @@ "default_value": 0, "minimum_value": 0, "maximum_value": 100, - "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"", + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", "settable_per_mesh": false, "settable_per_extruder": true }, @@ -4313,7 +4313,7 @@ "default_value": 0, "minimum_value": 0, "maximum_value": 100, - "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"", + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", "settable_per_mesh": false, "settable_per_extruder": true }, From 3adf94cffb0450bc83ddf9aee4853153651c5910 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Fri, 14 Feb 2025 10:59:38 +0100 Subject: [PATCH 03/55] move settings to experimental category --- resources/definitions/fdmprinter.def.json | 52 +++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index cf43fde121..48342b2eb1 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4291,32 +4291,6 @@ "settable_per_mesh": false, "settable_per_extruder": true }, - "retraction_during_travel_ratio": - { - "label": "Retraction During Travel Move", - "description": "The ratio of retraction performed during the travel move, with the remainder completed while the nozzle is stationary, before traveling
  • When 0, the entire retraction is performed while stationary, before the travel begins
  • When 100, the entire retraction is performed during the travel move, bypassing the stationary phase
", - "unit": "%", - "type": "float", - "default_value": 0, - "minimum_value": 0, - "maximum_value": 100, - "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", - "settable_per_mesh": false, - "settable_per_extruder": true - }, - "prime_during_travel_ratio": - { - "label": "Prime During Travel Move", - "description": "The ratio of priming performed during the travel move, with the remainder completed while the nozzle is stationary, after traveling
  • When 0, the entire priming is performed while stationary, after the travel ends
  • When 100, the entire priming is performed during the travel move, allowing the print to start immediately
", - "unit": "%", - "type": "float", - "default_value": 0, - "minimum_value": 0, - "maximum_value": 100, - "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", - "settable_per_mesh": false, - "settable_per_extruder": true - }, "retraction_speed": { "label": "Retraction Speed", @@ -9015,6 +8989,32 @@ "default_value": true, "settable_per_mesh": true }, + "retraction_during_travel_ratio": + { + "label": "Retraction During Travel Move", + "description": "The ratio of retraction performed during the travel move, with the remainder completed while the nozzle is stationary, before traveling
  • When 0, the entire retraction is performed while stationary, before the travel begins
  • When 100, the entire retraction is performed during the travel move, bypassing the stationary phase
", + "unit": "%", + "type": "float", + "default_value": 0, + "minimum_value": 0, + "maximum_value": 100, + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", + "settable_per_mesh": false, + "settable_per_extruder": true + }, + "prime_during_travel_ratio": + { + "label": "Prime During Travel Move", + "description": "The ratio of priming performed during the travel move, with the remainder completed while the nozzle is stationary, after traveling
  • When 0, the entire priming is performed while stationary, after the travel ends
  • When 100, the entire priming is performed during the travel move, allowing the print to start immediately
", + "unit": "%", + "type": "float", + "default_value": 0, + "minimum_value": 0, + "maximum_value": 100, + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "scarf_joint_seam_length": { "label": "Scarf Seam Length", From 86777ac666dd75bfa9a9ade7a3cdf671f4ec72ed Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 26 May 2025 11:52:53 +0200 Subject: [PATCH 04/55] Add new travel types and display z-hops CURA-11978 --- cura/LayerDataBuilder.py | 10 +- cura/LayerPolygon.py | 26 ++-- plugins/CuraEngineBackend/Cura.proto | 4 +- plugins/GCodeReader/FlavorParser.py | 17 ++- plugins/SimulationView/SimulationPass.py | 4 +- plugins/SimulationView/SimulationView.py | 6 +- plugins/SimulationView/layers.shader | 14 +- plugins/SimulationView/layers3d.shader | 67 +++++++--- plugins/SimulationView/layers3d_shadow.shader | 125 +++++++++++------- plugins/SimulationView/layers_shadow.shader | 10 +- resources/themes/cura-light/theme.json | 2 + 11 files changed, 181 insertions(+), 104 deletions(-) diff --git a/cura/LayerDataBuilder.py b/cura/LayerDataBuilder.py index d8801c9e7b..ff80307223 100755 --- a/cura/LayerDataBuilder.py +++ b/cura/LayerDataBuilder.py @@ -80,9 +80,13 @@ class LayerDataBuilder(MeshBuilder): material_colors = numpy.zeros((line_dimensions.shape[0], 4), dtype=numpy.float32) for extruder_nr in range(material_color_map.shape[0]): material_colors[extruders == extruder_nr] = material_color_map[extruder_nr] - # Set material_colors with indices where line_types (also numpy array) == MoveCombingType - material_colors[line_types == LayerPolygon.MoveCombingType] = colors[line_types == LayerPolygon.MoveCombingType] - material_colors[line_types == LayerPolygon.MoveRetractionType] = colors[line_types == LayerPolygon.MoveRetractionType] + # Set material_colors with indices where line_types (also numpy array) == MoveUnretractedType + material_colors[line_types == LayerPolygon.MoveUnretractedType] = colors[line_types == LayerPolygon.MoveUnretractedType] + material_colors[line_types == LayerPolygon.MoveRetractedType] = colors[line_types == LayerPolygon.MoveRetractedType] + material_colors[line_types == LayerPolygon.MoveWhileRetractingType] = colors[ + line_types == LayerPolygon.MoveWhileRetractingType] + material_colors[line_types == LayerPolygon.MoveWhileUnretractingType] = colors[ + line_types == LayerPolygon.MoveWhileUnretractingType] attributes = { "line_dimensions": { diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index e772a8b78e..c4d57c07a0 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -19,15 +19,21 @@ class LayerPolygon: SkirtType = 5 InfillType = 6 SupportInfillType = 7 - MoveCombingType = 8 - MoveRetractionType = 9 + MoveUnretractedType = 8 + MoveRetractedType = 9 SupportInterfaceType = 10 PrimeTowerType = 11 - __number_of_types = 12 + MoveWhileRetractingType = 12 + MoveWhileUnretractingType = 13 + __number_of_types = 14 - __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, - numpy.arange(__number_of_types) == MoveCombingType), - numpy.arange(__number_of_types) == MoveRetractionType) + __jump_map = numpy.logical_or(numpy.logical_or(numpy.logical_or( + numpy.arange(__number_of_types) == NoneType, + numpy.arange(__number_of_types) == MoveUnretractedType), + numpy.logical_or( + numpy.arange(__number_of_types) == MoveRetractedType, + numpy.arange(__number_of_types) == MoveWhileRetractingType)), + numpy.arange(__number_of_types) == MoveWhileUnretractingType) def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None: @@ -269,10 +275,12 @@ class LayerPolygon: theme.getColor("layerview_skirt").getRgbF(), # SkirtType theme.getColor("layerview_infill").getRgbF(), # InfillType theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType - theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType - theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType + theme.getColor("layerview_move_combing").getRgbF(), # MoveUnretractedType + theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractedType theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType - theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType + theme.getColor("layerview_prime_tower").getRgbF(), # PrimeTowerType + theme.getColor("layerview_move_while_retracting").getRgbF(), # MoveWhileRetracting + theme.getColor("layerview_move_while_unretracting").getRgbF(), # MoveWhileUnretracting ]) return cls.__color_map diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index 238829ba64..8018c9186f 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/plugins/CuraEngineBackend/Cura.proto @@ -78,8 +78,8 @@ message Polygon { SkirtType = 5; InfillType = 6; SupportInfillType = 7; - MoveCombingType = 8; - MoveRetractionType = 9; + MoveUnretractedType = 8; + MoveRetractedType = 9; SupportInterfaceType = 10; PrimeTowerType = 11; } diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 74dbeadec0..f83a9bbb34 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -133,7 +133,10 @@ class FlavorParser: if i > 0: line_feedrates[i - 1] = point[3] line_types[i - 1] = point[5] - if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: + if point[5] in [LayerPolygon.MoveUnretractedType, + LayerPolygon.MoveRetractedType, + LayerPolygon.MoveWhileRetractingType, + LayerPolygon.MoveWhileUnretractingType]: line_widths[i - 1] = 0.1 line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines else: @@ -196,7 +199,7 @@ class FlavorParser: path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion self._previous_extrusion_value = new_extrusion_value else: - path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction + path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType]) # retraction e[self._extruder_number] = new_extrusion_value # Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions @@ -205,9 +208,9 @@ class FlavorParser: self._current_layer_thickness = z - self._previous_z # allow a tiny overlap self._previous_z = z elif self._previous_extrusion_value > e[self._extruder_number]: - path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) + path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType]) else: - path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType]) + path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveUnretractedType]) return self._position(x, y, z, f, e) @@ -419,7 +422,7 @@ class FlavorParser: self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() # Start the new layer at the end position of the last layer - current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType]) # When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior # as in ProcessSlicedLayersJob @@ -461,9 +464,9 @@ class FlavorParser: # When changing tool, store the end point of the previous path, then process the code and finally # add another point with the new position of the head. - current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType]) current_position = self.processTCode(global_stack, T, line, current_position, current_path) - current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType]) if line.startswith("M"): M = self._getInt(line, "M") diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py index 080b02bd9e..840fd7f6dc 100644 --- a/plugins/SimulationView/SimulationPass.py +++ b/plugins/SimulationView/SimulationPass.py @@ -202,9 +202,9 @@ class SimulationPass(RenderPass): self._layer_shader.setUniformValue("u_next_vertex", not_a_vector) self._layer_shader.setUniformValue("u_last_line_ratio", 1.0) - # The first line does not have a previous line: add a MoveCombingType in front for start detection + # The first line does not have a previous line: add a MoveUnretractedType in front for start detection # this way the first start of the layer can also be drawn - prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]]) + prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveUnretractedType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]]) # Remove the last element prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size] layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'} diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 10861acfd0..083fc73bf1 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -608,8 +608,10 @@ class SimulationView(CuraView): visible_line_types.append(LayerPolygon.SupportInterfaceType) visible_line_types_with_extrusion = visible_line_types.copy() # Copy before travel moves are added if self.getShowTravelMoves(): - visible_line_types.append(LayerPolygon.MoveCombingType) - visible_line_types.append(LayerPolygon.MoveRetractionType) + visible_line_types.append(LayerPolygon.MoveUnretractedType) + visible_line_types.append(LayerPolygon.MoveRetractedType) + visible_line_types.append(LayerPolygon.MoveWhileRetractingType) + visible_line_types.append(LayerPolygon.MoveWhileUnretractingType) for node in DepthFirstIterator(self.getController().getScene().getRoot()): layer_data = node.callDecoration("getLayerData") diff --git a/plugins/SimulationView/layers.shader b/plugins/SimulationView/layers.shader index e6210c2b65..d5079fd82b 100644 --- a/plugins/SimulationView/layers.shader +++ b/plugins/SimulationView/layers.shader @@ -22,8 +22,8 @@ vertex = gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; // shade the color depending on the extruder index v_color = a_color; - // 8 and 9 are travel moves - if ((a_line_type != 8.0) && (a_line_type != 9.0)) { + // 8, 9, 12 and 13 are travel moves + if ((a_line_type != 8.0) && (a_line_type != 9.0) && (a_line_type != 12.0) && (a_line_type != 13.0)) { v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); } @@ -48,7 +48,9 @@ fragment = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { // discard movements discard; } @@ -100,7 +102,7 @@ vertex41core = { gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex; v_color = a_color; - if ((a_line_type != 8) && (a_line_type != 9)) { + if ((a_line_type != 8) && (a_line_type != 9) && (a_line_type != 12) && (a_line_type != 13)) { v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a); } @@ -120,7 +122,9 @@ fragment41core = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { // discard movements discard; } diff --git a/plugins/SimulationView/layers3d.shader b/plugins/SimulationView/layers3d.shader index 494a07083d..e2f57823f3 100644 --- a/plugins/SimulationView/layers3d.shader +++ b/plugins/SimulationView/layers3d.shader @@ -228,22 +228,26 @@ geometry41core = { highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix; - vec4 g_vertex_delta; - vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers - vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position + // Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position + vec3 g_vertex_delta; + vec3 g_vertex_normal_horz; + vec4 g_vertex_offset_horz; vec3 g_vertex_normal_vert; vec4 g_vertex_offset_vert; vec3 g_vertex_normal_horz_head; vec4 g_vertex_offset_horz_head; + vec3 g_axial_plan_vector; + vec3 g_radial_plan_vector; float size_x; float size_y; - if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) { + if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && + (v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) { return; } - // See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType - if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) { + // See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType + if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) { return; } if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10) || v_line_type[0] == 11)) { @@ -256,7 +260,7 @@ geometry41core = return; } - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { // fixed size for movements size_x = 0.05; } else { @@ -264,26 +268,47 @@ geometry41core = } size_y = v_line_dim[1].y / 2 + 0.01; - g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; //Actual movement exhibited by the line. - g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); //Lengthwise normal vector pointing backwards. - g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector pointing backwards. + g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line. - g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); //Normal vector pointing right. + if (g_vertex_delta == vec3(0.0)) { + return; + } + + if (g_vertex_delta.y == 0.0) + { + // vector is in the horizontal plan, radial vector is a simple rotation around Y axis + g_radial_plan_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x); + } + else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0) + { + // delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views + g_radial_plan_vector = vec3(1.0, 0.0, -1.0); + } + else + { + // delta vector is completely 3D + g_axial_plan_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plan + g_radial_plan_vector = cross(g_vertex_delta, g_axial_plan_vector); // Radial vector in the horizontal plan, pointing right. + } + + g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector + g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector + + g_vertex_normal_horz = normalize(g_radial_plan_vector); //Normal vector pointing right. g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right. g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector. g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness. - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { //Travel or retraction moves. - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { //Travel or retraction moves. + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); // Travels: flat plane with pointy ends - myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); @@ -308,8 +333,8 @@ geometry41core = vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex. vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex. vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex. - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head); //Line start, tip. - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head); //Line end, tip. + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip. + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip. // All normal lines are rendered as 3d tubes. myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); @@ -328,14 +353,14 @@ geometry41core = // left side myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert); - myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); EndPrimitive(); myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert); - myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); EndPrimitive(); @@ -343,14 +368,14 @@ geometry41core = // right side myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); EndPrimitive(); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); EndPrimitive(); diff --git a/plugins/SimulationView/layers3d_shadow.shader b/plugins/SimulationView/layers3d_shadow.shader index 88268938c9..47637cfb20 100644 --- a/plugins/SimulationView/layers3d_shadow.shader +++ b/plugins/SimulationView/layers3d_shadow.shader @@ -95,22 +95,26 @@ geometry41core = { highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix; - vec4 g_vertex_delta; - vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers - vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position + // Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position + vec3 g_vertex_delta; + vec3 g_vertex_normal_horz; + vec4 g_vertex_offset_horz; vec3 g_vertex_normal_vert; vec4 g_vertex_offset_vert; vec3 g_vertex_normal_horz_head; vec4 g_vertex_offset_horz_head; + vec3 g_axial_plan_vector; + vec3 g_radial_plan_vector; float size_x; float size_y; - if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) { + if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && + (v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) { return; } - // See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType - if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) { + // See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType + if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) { return; } if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) { @@ -123,7 +127,7 @@ geometry41core = return; } - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { // fixed size for movements size_x = 0.05; } else { @@ -131,93 +135,114 @@ geometry41core = } size_y = v_line_dim[1].y / 2 + 0.01; - g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; - g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); - g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); + g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line. - g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); + if (g_vertex_delta == vec3(0.0)) { + return; + } - g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz; - g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); - g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); + if (g_vertex_delta.y == 0.0) + { + // vector is in the horizontal plan, radial vector is a simple rotation around Y axis + g_radial_plan_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x); + } + else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0) + { + // delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views + g_radial_plan_vector = vec3(1.0, 0.0, -1.0); + } + else + { + // delta vector is completely 3D + g_axial_plan_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plan + g_radial_plan_vector = cross(g_vertex_delta, g_axial_plan_vector); // Radial vector in the horizontal plan, pointing right. + } - if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); + g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector + g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector + + g_vertex_normal_horz = normalize(g_radial_plan_vector); //Normal vector pointing right. + g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right. + + g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector. + g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness. + + if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert); + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert); vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert); vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert); // Travels: flat plane with pointy ends - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head); //And reverse so that the line is also visible from the back side. myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up); EndPrimitive(); } else { - vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz); - vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz); - vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert); - vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert); - vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz); - vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); - vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); - vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); - vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head); - vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head); + vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz); //Line start, left vertex. + vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz); //Line end, left vertex. + vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert); //Line start, top vertex. + vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert); //Line end, top vertex. + vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz); //Line start, right vertex. + vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex. + vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex. + vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex. + vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip. + vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip. // All normal lines are rendered as 3d tubes. - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); EndPrimitive(); // left side - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); EndPrimitive(); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert); - myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head); - myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz); + myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head); + myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz); EndPrimitive(); // right side myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); EndPrimitive(); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert); - myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head); + myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz); EndPrimitive(); diff --git a/plugins/SimulationView/layers_shadow.shader b/plugins/SimulationView/layers_shadow.shader index 4bc2de3d0b..73278914b7 100644 --- a/plugins/SimulationView/layers_shadow.shader +++ b/plugins/SimulationView/layers_shadow.shader @@ -48,8 +48,10 @@ fragment = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) - { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { + { // discard movements discard; } @@ -124,7 +126,9 @@ fragment41core = void main() { - if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9 + // travel moves: 8, 9, 12, 13 + if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) || + ((v_line_type >= 11.5) && (v_line_type <= 13.5)))) { // discard movements discard; } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 1ae316f96c..377e70f5b6 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -463,6 +463,8 @@ "layerview_support_infill": [0, 230, 230, 127], "layerview_move_combing": [0, 0, 255, 255], "layerview_move_retraction": [128, 127, 255, 255], + "layerview_move_while_retracting": [127, 255, 255, 255], + "layerview_move_while_unretracting": [255, 127, 255, 255], "layerview_support_interface": [63, 127, 255, 127], "layerview_prime_tower": [0, 255, 255, 255], "layerview_nozzle": [224, 192, 16, 64], From e1d579c6c8f7720deb05dd21e3892b1f914c6e34 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 26 May 2025 16:53:34 +0200 Subject: [PATCH 05/55] Display legend tooltip for travel types CURA-11978 --- .../SimulationViewMenuComponent.qml | 87 +++++++++++++++++-- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/plugins/SimulationView/SimulationViewMenuComponent.qml b/plugins/SimulationView/SimulationViewMenuComponent.qml index d434d883eb..dcd2c7d178 100644 --- a/plugins/SimulationView/SimulationViewMenuComponent.qml +++ b/plugins/SimulationView/SimulationViewMenuComponent.qml @@ -227,29 +227,52 @@ Cura.ExpandableComponent id: typesLegendModel Component.onCompleted: { + const travelsTypesModel = [ + { + label: catalog.i18nc("@label", "Unretracted"), + colorId: "layerview_move_combing" + }, + { + label: catalog.i18nc("@label", "Retracted"), + colorId: "layerview_move_retraction" + }, + { + label: catalog.i18nc("@label", "Retracting"), + colorId: "layerview_move_while_retracting" + }, + { + label: catalog.i18nc("@label", "Unretracting"), + colorId: "layerview_move_while_unretracting" + } + ]; + typesLegendModel.append({ label: catalog.i18nc("@label", "Travels"), initialValue: viewSettings.show_travel_moves, preference: "layerview/show_travel_moves", - colorId: "layerview_move_combing" + colorId: "layerview_move_combing", + subTypesModel: travelsTypesModel }); typesLegendModel.append({ label: catalog.i18nc("@label", "Helpers"), initialValue: viewSettings.show_helpers, preference: "layerview/show_helpers", - colorId: "layerview_support" + colorId: "layerview_support", + subTypesModel: [] }); typesLegendModel.append({ label: catalog.i18nc("@label", "Shell"), initialValue: viewSettings.show_skin, preference: "layerview/show_skin", - colorId: "layerview_inset_0" + colorId: "layerview_inset_0", + subTypesModel: [] }); typesLegendModel.append({ label: catalog.i18nc("@label", "Infill"), initialValue: viewSettings.show_infill, preference: "layerview/show_infill", - colorId: "layerview_infill" + colorId: "layerview_infill", + subTypesModel: [] }); if (! UM.SimulationView.compatibilityMode) { @@ -257,7 +280,8 @@ Cura.ExpandableComponent label: catalog.i18nc("@label", "Starts"), initialValue: viewSettings.show_starts, preference: "layerview/show_starts", - colorId: "layerview_starts" + colorId: "layerview_starts", + subTypesModel: [] }); } } @@ -273,6 +297,7 @@ Cura.ExpandableComponent Rectangle { + id: rectangleColor anchors.verticalCenter: parent.verticalCenter anchors.right: legendModelCheckBox.right width: UM.Theme.getSize("layerview_legend_size").width @@ -281,6 +306,58 @@ Cura.ExpandableComponent border.width: UM.Theme.getSize("default_lining").width border.color: UM.Theme.getColor("lining") visible: viewSettings.show_legend + + MouseArea + { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + enabled: subTypesModel.count > 0 + + onEntered: tooltip.show() + onExited: tooltip.hide() + + UM.ToolTip + { + id: tooltip + delay: 0 + width: subTypesColumn.implicitWidth + 2 * UM.Theme.getSize("thin_margin").width + height: subTypesColumn.implicitHeight + 2 * UM.Theme.getSize("thin_margin").width + + contentItem: Column + { + id: subTypesColumn + padding: 0 + spacing: UM.Theme.getSize("layerview_row_spacing").height + + Repeater + { + model: subTypesModel + UM.Label + { + text: label + + height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height + width: UM.Theme.getSize("layerview_menu_size").width + color: UM.Theme.getColor("setting_control_text") + Rectangle + { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + + width: UM.Theme.getSize("layerview_legend_size").width + height: UM.Theme.getSize("layerview_legend_size").height + + color: UM.Theme.getColor(model.colorId) + + border.width: UM.Theme.getSize("default_lining").width + border.color: UM.Theme.getColor("lining") + } + } + } + } + } + } } UM.Label From b940179c54f224dfa6420a65a8f91f615f7478b6 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 4 Jun 2025 11:01:51 +0200 Subject: [PATCH 06/55] Set proper tooltip text color CURA-11978 --- plugins/SimulationView/SimulationViewMenuComponent.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SimulationView/SimulationViewMenuComponent.qml b/plugins/SimulationView/SimulationViewMenuComponent.qml index dcd2c7d178..0e254a005b 100644 --- a/plugins/SimulationView/SimulationViewMenuComponent.qml +++ b/plugins/SimulationView/SimulationViewMenuComponent.qml @@ -339,7 +339,7 @@ Cura.ExpandableComponent height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height width: UM.Theme.getSize("layerview_menu_size").width - color: UM.Theme.getColor("setting_control_text") + color: UM.Theme.getColor("tooltip_text") Rectangle { anchors.verticalCenter: parent.verticalCenter From f84923185ded2dde8f614a7ab6f0e50d6290b343 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 10 Jun 2025 08:30:43 +0200 Subject: [PATCH 07/55] Use proper English word for "plane" CURA-12544 The word in French to describe a geometric flat surface if "plan", which is a valid word in English but it doesn't have the same meaning, this got me confused. So replacing "plan" by "plane" because we are actually dealing with a geometrical "plane" (although it doesn't fly). --- plugins/SimulationView/layers3d_shadow.shader | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/SimulationView/layers3d_shadow.shader b/plugins/SimulationView/layers3d_shadow.shader index 47637cfb20..0cf3e4f75a 100644 --- a/plugins/SimulationView/layers3d_shadow.shader +++ b/plugins/SimulationView/layers3d_shadow.shader @@ -103,8 +103,8 @@ geometry41core = vec4 g_vertex_offset_vert; vec3 g_vertex_normal_horz_head; vec4 g_vertex_offset_horz_head; - vec3 g_axial_plan_vector; - vec3 g_radial_plan_vector; + vec3 g_axial_plane_vector; + vec3 g_radial_plane_vector; float size_x; float size_y; @@ -143,25 +143,25 @@ geometry41core = if (g_vertex_delta.y == 0.0) { - // vector is in the horizontal plan, radial vector is a simple rotation around Y axis - g_radial_plan_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x); + // vector is in the horizontal plane, radial vector is a simple rotation around Y axis + g_radial_plane_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x); } else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0) { // delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views - g_radial_plan_vector = vec3(1.0, 0.0, -1.0); + g_radial_plane_vector = vec3(1.0, 0.0, -1.0); } else { // delta vector is completely 3D - g_axial_plan_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plan - g_radial_plan_vector = cross(g_vertex_delta, g_axial_plan_vector); // Radial vector in the horizontal plan, pointing right. + g_axial_plane_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plane + g_radial_plane_vector = cross(g_vertex_delta, g_axial_plane_vector); // Radial vector in the horizontal plane, pointing right. } g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector - g_vertex_normal_horz = normalize(g_radial_plan_vector); //Normal vector pointing right. + g_vertex_normal_horz = normalize(g_radial_plane_vector); //Normal vector pointing right. g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right. g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector. From 65b0e4f080d0dceb37aeddc8ca9f4d8cc52e6883 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 11 Jun 2025 10:51:47 +0200 Subject: [PATCH 08/55] Add specific message when sending a print to an inactive printer CURA-12557 --- .../src/Cloud/CloudApiClient.py | 22 +++++++++++++++++-- .../src/Cloud/CloudOutputDevice.py | 16 +++++++++----- .../PrintJobUploadPrinterInactiveMessage.py | 20 +++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadPrinterInactiveMessage.py diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py index 3c8e53b2e9..0831ceebd3 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py @@ -163,7 +163,7 @@ class CloudApiClient: scope=self._scope, data=b"", callback=self._parseCallback(on_finished, CloudPrintResponse), - error_callback=on_error, + error_callback=self._parseError(on_error), timeout=self.DEFAULT_REQUEST_TIMEOUT) def doPrintJobAction(self, cluster_id: str, cluster_job_id: str, action: str, @@ -256,7 +256,6 @@ class CloudApiClient: """Creates a callback function so that it includes the parsing of the response into the correct model. The callback is added to the 'finished' signal of the reply. - :param reply: The reply that should be listened to. :param on_finished: The callback in case the response is successful. Depending on the endpoint it will be either a list or a single item. :param model: The type of the model to convert the response to. @@ -281,6 +280,25 @@ class CloudApiClient: self._anti_gc_callbacks.append(parse) return parse + def _parseError(self, + on_error: Callable[[CloudError, "QNetworkReply.NetworkError", int], None]) -> Callable[[QNetworkReply, "QNetworkReply.NetworkError"], None]: + + """Creates a callback function so that it includes the parsing of an explicit error response into the correct model. + + :param on_error: The callback in case the response gives an explicit error + """ + + def parse(reply: QNetworkReply, error: "QNetworkReply.NetworkError") -> None: + + self._anti_gc_callbacks.remove(parse) + + http_code, response = self._parseReply(reply) + result = CloudError(**response["errors"][0]) + on_error(result, error, http_code) + + self._anti_gc_callbacks.append(parse) + return parse + @classmethod def getMachineIDMap(cls) -> Dict[str, str]: if cls._machine_id_to_name is None: diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index 090355a3c0..df486822b7 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -27,9 +27,11 @@ from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOut from ..Messages.PrintJobUploadBlockedMessage import PrintJobUploadBlockedMessage from ..Messages.PrintJobUploadErrorMessage import PrintJobUploadErrorMessage from ..Messages.PrintJobUploadQueueFullMessage import PrintJobUploadQueueFullMessage +from ..Messages.PrintJobUploadPrinterInactiveMessage import PrintJobUploadPrinterInactiveMessage from ..Messages.PrintJobUploadSuccessMessage import PrintJobUploadSuccessMessage from ..Models.Http.CloudClusterResponse import CloudClusterResponse from ..Models.Http.CloudClusterStatus import CloudClusterStatus +from ..Models.Http.CloudError import CloudError from ..Models.Http.CloudPrintJobUploadRequest import CloudPrintJobUploadRequest from ..Models.Http.CloudPrintResponse import CloudPrintResponse from ..Models.Http.CloudPrintJobResponse import CloudPrintJobResponse @@ -291,19 +293,21 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self.writeFinished.emit() - def _onPrintUploadSpecificError(self, reply: "QNetworkReply", _: "QNetworkReply.NetworkError"): + def _onPrintUploadSpecificError(self, error: CloudError, _: "QNetworkReply.NetworkError", http_error: int): """ Displays a message when an error occurs specific to uploading print job (i.e. queue is full). """ - error_code = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) - if error_code == 409: - PrintJobUploadQueueFullMessage().show() + if http_error == 409: + if error.code == "printerInactive": + PrintJobUploadPrinterInactiveMessage().show() + else: + PrintJobUploadQueueFullMessage().show() else: PrintJobUploadErrorMessage(I18N_CATALOG.i18nc("@error:send", "Unknown error code when uploading print job: {0}", - error_code)).show() + http_error)).show() - Logger.log("w", "Upload of print job failed specifically with error code {}".format(error_code)) + Logger.log("w", "Upload of print job failed specifically with error code {}".format(http_error)) self._progress.hide() self._pre_upload_print_job = None diff --git a/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadPrinterInactiveMessage.py b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadPrinterInactiveMessage.py new file mode 100644 index 0000000000..324259eea4 --- /dev/null +++ b/plugins/UM3NetworkPrinting/src/Messages/PrintJobUploadPrinterInactiveMessage.py @@ -0,0 +1,20 @@ +# Copyright (c) 2020 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM import i18nCatalog +from UM.Message import Message + + +I18N_CATALOG = i18nCatalog("cura") + + +class PrintJobUploadPrinterInactiveMessage(Message): + """Message shown when uploading a print job to a cluster and the printer is inactive.""" + + def __init__(self) -> None: + super().__init__( + text = I18N_CATALOG.i18nc("@info:status", "The printer is inactive and cannot accept a new print job."), + title = I18N_CATALOG.i18nc("@info:title", "Printer inactive"), + lifetime = 10, + message_type=Message.MessageType.ERROR + ) From 2e9999ed2dd36d0e1e2f5da474e18ef1f5018a77 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 11 Jun 2025 13:51:45 +0200 Subject: [PATCH 09/55] Display the printer activation status CURA-12557 --- cura/Settings/MachineManager.py | 14 +++++++ .../src/Cloud/CloudOutputDevice.py | 13 ++++++ .../src/Models/Http/CloudClusterResponse.py | 6 ++- .../src/Models/Http/CloudClusterStatus.py | 2 + .../qml/PrinterSelector/MachineSelector.qml | 40 ++++++++++++++++--- resources/themes/cura-light/theme.json | 1 + 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 986608cd49..1bdb32f4ac 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -183,10 +183,16 @@ class MachineManager(QObject): self.setActiveMachine(active_machine_id) def _onOutputDevicesChanged(self) -> None: + for printer_output_device in self._printer_output_devices: + if hasattr(printer_output_device, "cloudActiveChanged"): + printer_output_device.cloudActiveChanged.disconnect(self.printerConnectedStatusChanged) + self._printer_output_devices = [] for printer_output_device in self._application.getOutputDeviceManager().getOutputDevices(): if isinstance(printer_output_device, PrinterOutputDevice): self._printer_output_devices.append(printer_output_device) + if hasattr(printer_output_device, "cloudActiveChanged"): + printer_output_device.cloudActiveChanged.connect(self.printerConnectedStatusChanged) self.outputDevicesChanged.emit() @@ -569,6 +575,14 @@ class MachineManager(QObject): def activeMachineIsUsingCloudConnection(self) -> bool: return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection + @pyqtProperty(bool, notify = printerConnectedStatusChanged) + def activeMachineIsCloudActive(self) -> bool: + if not self._printer_output_devices: + return True + + first_printer = self._printer_output_devices[0] + return True if not hasattr(first_printer, 'cloudActive') else first_printer.cloudActive + def activeMachineNetworkKey(self) -> str: if self._global_container_stack: return self._global_container_stack.getMetaDataEntry("um_network_key", "") diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index df486822b7..020cafacd8 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -65,6 +65,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): # Therefore, we create a private signal used to trigger the printersChanged signal. _cloudClusterPrintersChanged = pyqtSignal() + cloudActiveChanged = pyqtSignal() + def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None: """Creates a new cloud output device @@ -113,6 +115,9 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._pre_upload_print_job = None # type: Optional[CloudPrintJobResponse] self._uploaded_print_job = None # type: Optional[CloudPrintJobResponse] + # Whether the printer is active, i.e. authorized for use i.r.t to workspace limitations + self._active = cluster.display_status != "inactive" + CuraApplication.getInstance().getBackend().backendDone.connect(self._resetPrintJob) CuraApplication.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) @@ -192,6 +197,10 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._received_print_jobs = status.print_jobs self._updatePrintJobs(status.print_jobs) + if status.active != self._active: + self._active = status.active + self.cloudActiveChanged.emit() + def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, filter_by_machine: bool = False, **kwargs) -> None: @@ -436,6 +445,10 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): root_url_prefix = "-staging" if self._account.is_staging else "" return f"https://digitalfactory{root_url_prefix}.ultimaker.com/app/jobs/{self.clusterData.cluster_id}" + @pyqtProperty(bool, notify = cloudActiveChanged) + def cloudActive(self) -> bool: + return self._active + def __del__(self): CuraApplication.getInstance().getBackend().backendDone.disconnect(self._resetPrintJob) CuraApplication.getInstance().getController().getScene().sceneChanged.disconnect(self._onSceneChanged) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py index 713582b8ad..a1f22f7b36 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterResponse.py @@ -10,7 +10,7 @@ class CloudClusterResponse(BaseModel): """Class representing a cloud connected cluster.""" def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, - host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, + display_status: str, host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1, capabilities: Optional[List[str]] = None, **kwargs) -> None: """Creates a new cluster response object. @@ -20,6 +20,7 @@ class CloudClusterResponse(BaseModel): :param host_name: The name of the printer as configured during the Wi-Fi setup. Used as identifier for end users. :param is_online: Whether this cluster is currently connected to the cloud. :param status: The status of the cluster authentication (active or inactive). + :param display_status: The display status of the cluster. :param host_version: The firmware version of the cluster host. This is where the Stardust client is running on. :param host_internal_ip: The internal IP address of the host printer. :param friendly_name: The human readable name of the host printer. @@ -31,6 +32,7 @@ class CloudClusterResponse(BaseModel): self.host_guid = host_guid self.host_name = host_name self.status = status + self.display_status = display_status self.is_online = is_online self.host_version = host_version self.host_internal_ip = host_internal_ip @@ -51,5 +53,5 @@ class CloudClusterResponse(BaseModel): Convenience function for printing when debugging. :return: A human-readable representation of the data in this object. """ - return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}}) + return str({k: v for k, v in self.__dict__.items() if k in {"cluster_id", "host_guid", "host_name", "status", "display_status", "is_online", "host_version", "host_internal_ip", "friendly_name", "printer_type", "printer_count", "capabilities"}}) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py index 5cd151d8ef..34249dc67a 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/CloudClusterStatus.py @@ -14,6 +14,7 @@ class CloudClusterStatus(BaseModel): def __init__(self, printers: List[Union[ClusterPrinterStatus, Dict[str, Any]]], print_jobs: List[Union[ClusterPrintJobStatus, Dict[str, Any]]], generated_time: Union[str, datetime], + unavailable: bool = False, **kwargs) -> None: """Creates a new cluster status model object. @@ -23,6 +24,7 @@ class CloudClusterStatus(BaseModel): """ self.generated_time = self.parseDate(generated_time) + self.active = not unavailable self.printers = self.parseModels(ClusterPrinterStatus, printers) self.print_jobs = self.parseModels(ClusterPrintJobStatus, print_jobs) super().__init__(**kwargs) diff --git a/resources/qml/PrinterSelector/MachineSelector.qml b/resources/qml/PrinterSelector/MachineSelector.qml index 1bad1d70bc..7acdd9573b 100644 --- a/resources/qml/PrinterSelector/MachineSelector.qml +++ b/resources/qml/PrinterSelector/MachineSelector.qml @@ -16,6 +16,7 @@ Cura.ExpandablePopup property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration property bool isGroup: machineManager.activeMachineIsGroup + property bool isCloudActive: machineManager.activeMachineIsCloudActive property string machineName: { if (isNetworkPrinter && machineManager.activeMachineNetworkGroupName != "") { @@ -40,7 +41,14 @@ Cura.ExpandablePopup } else if (isConnectedCloudPrinter && Cura.API.connectionStatus.isInternetReachable) { - return "printer_cloud_connected" + if (isCloudActive) + { + return "printer_cloud_connected" + } + else + { + return "printer_cloud_inactive" + } } else if (isCloudRegistered) { @@ -53,7 +61,7 @@ Cura.ExpandablePopup } function getConnectionStatusMessage() { - if (connectionStatus == "printer_cloud_not_available") + if (connectionStatus === "printer_cloud_not_available") { if(Cura.API.connectionStatus.isInternetReachable) { @@ -78,6 +86,10 @@ Cura.ExpandablePopup return catalog.i18nc("@status", "The cloud connection is currently unavailable. Please check your internet connection.") } } + else if(connectionStatus === "printer_cloud_inactive") + { + return catalog.i18nc("@status", "This printer is deactivated and can not accept commands or jobs.") + } else { return "" @@ -130,14 +142,18 @@ Cura.ExpandablePopup source: { - if (connectionStatus == "printer_connected") + if (connectionStatus === "printer_connected") { return UM.Theme.getIcon("CheckBlueBG", "low") } - else if (connectionStatus == "printer_cloud_connected" || connectionStatus == "printer_cloud_not_available") + else if (connectionStatus === "printer_cloud_connected" || connectionStatus === "printer_cloud_not_available") { return UM.Theme.getIcon("CloudBadge", "low") } + else if (connectionStatus === "printer_cloud_inactive") + { + return UM.Theme.getIcon("WarningBadge", "low") + } else { return "" @@ -147,7 +163,21 @@ Cura.ExpandablePopup width: UM.Theme.getSize("printer_status_icon").width height: UM.Theme.getSize("printer_status_icon").height - color: connectionStatus == "printer_cloud_not_available" ? UM.Theme.getColor("cloud_unavailable") : UM.Theme.getColor("primary") + color: + { + if (connectionStatus === "printer_cloud_not_available") + { + return UM.Theme.getColor("cloud_unavailable") + } + else if(connectionStatus === "printer_cloud_inactive") + { + return UM.Theme.getColor("cloud_inactive") + } + else + { + return UM.Theme.getColor("primary") + } + } visible: (isNetworkPrinter || isCloudRegistered) && source != "" diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 1ae316f96c..8d09d7837d 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -496,6 +496,7 @@ "monitor_carousel_dot_current": [119, 119, 119, 255], "cloud_unavailable": [153, 153, 153, 255], + "cloud_inactive": "warning", "connection_badge_background": [255, 255, 255, 255], "warning_badge_background": [0, 0, 0, 255], "error_badge_background": [255, 255, 255, 255], From a739fd21f5d705ec639a538a9c1891e03658087a Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 11 Jun 2025 15:48:09 +0200 Subject: [PATCH 10/55] Display inactive DL projects as disabled CURA-12557 --- .../resources/qml/ProjectSummaryCard.qml | 36 ++++++++++++++----- .../resources/qml/SelectProjectPage.qml | 29 ++++++++++----- .../src/DigitalFactoryProjectModel.py | 3 ++ .../src/DigitalFactoryProjectResponse.py | 2 ++ resources/themes/cura-light/theme.json | 2 +- 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml index ca836ee21d..133cc0edde 100644 --- a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml +++ b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml @@ -11,10 +11,10 @@ Cura.RoundedRectangle width: parent.width height: projectImage.height + 2 * UM.Theme.getSize("default_margin").width cornerSide: Cura.RoundedRectangle.Direction.All - border.color: UM.Theme.getColor("lining") + border.color: enabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("action_button_disabled_border") border.width: UM.Theme.getSize("default_lining").width radius: UM.Theme.getSize("default_radius").width - color: UM.Theme.getColor("main_background") + color: getBackgoundColor() signal clicked() property alias imageSource: projectImage.source property alias projectNameText: displayNameLabel.text @@ -22,17 +22,18 @@ Cura.RoundedRectangle property alias projectLastUpdatedText: lastUpdatedLabel.text property alias cardMouseAreaEnabled: cardMouseArea.enabled - onVisibleChanged: color = UM.Theme.getColor("main_background") + onVisibleChanged: color = getBackgroundColor() MouseArea { id: cardMouseArea anchors.fill: parent - hoverEnabled: true - onEntered: base.color = UM.Theme.getColor("action_button_hovered") - onExited: base.color = UM.Theme.getColor("main_background") + hoverEnabled: base.enabled + onEntered: color = getBackgroundColor() + onExited: color = getBackgroundColor() onClicked: base.clicked() } + Row { id: projectInformationRow @@ -73,7 +74,7 @@ Cura.RoundedRectangle width: parent.width height: Math.round(parent.height / 3) elide: Text.ElideRight - color: UM.Theme.getColor("small_button_text") + color: base.enabled ? UM.Theme.getColor("small_button_text") : UM.Theme.getColor("text_disabled") } UM.Label @@ -82,8 +83,27 @@ Cura.RoundedRectangle width: parent.width height: Math.round(parent.height / 3) elide: Text.ElideRight - color: UM.Theme.getColor("small_button_text") + color: base.enabled ? UM.Theme.getColor("small_button_text") : UM.Theme.getColor("text_disabled") } } } + + function getBackgroundColor() + { + if(enabled) + { + if(cardMouseArea.containsMouse) + { + return UM.Theme.getColor("action_button_hovered") + } + else + { + return UM.Theme.getColor("main_background") + } + } + else + { + return UM.Theme.getColor("action_button_disabled") + } + } } \ No newline at end of file diff --git a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml index faceb4df23..2d0bd30f2b 100644 --- a/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml +++ b/plugins/DigitalLibrary/resources/qml/SelectProjectPage.qml @@ -159,17 +159,30 @@ Item Repeater { model: manager.digitalFactoryProjectModel - delegate: ProjectSummaryCard + delegate: Item { - id: projectSummaryCard - imageSource: model.thumbnailUrl || "../images/placeholder.svg" - projectNameText: model.displayName - projectUsernameText: model.username - projectLastUpdatedText: "Last updated: " + model.lastUpdated + width: parent.width + height: projectSummaryCard.height - onClicked: + UM.TooltipArea { - manager.selectedProjectIndex = index + anchors.fill: parent + text: "This project is inactive and cannot be used." + enabled: !model.active + } + + ProjectSummaryCard + { + id: projectSummaryCard + imageSource: model.thumbnailUrl || "../images/placeholder.svg" + projectNameText: model.displayName + projectUsernameText: model.username + projectLastUpdatedText: "Last updated: " + model.lastUpdated + enabled: model.active + + onClicked: { + manager.selectedProjectIndex = index + } } } } diff --git a/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py b/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py index bd12a4ca12..7140657508 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryProjectModel.py @@ -17,6 +17,7 @@ class DigitalFactoryProjectModel(ListModel): ThumbnailUrlRole = Qt.ItemDataRole.UserRole + 5 UsernameRole = Qt.ItemDataRole.UserRole + 6 LastUpdatedRole = Qt.ItemDataRole.UserRole + 7 + ActiveRole = Qt.ItemDataRole.UserRole + 8 dfProjectModelChanged = pyqtSignal() @@ -28,6 +29,7 @@ class DigitalFactoryProjectModel(ListModel): self.addRoleName(self.ThumbnailUrlRole, "thumbnailUrl") self.addRoleName(self.UsernameRole, "username") self.addRoleName(self.LastUpdatedRole, "lastUpdated") + self.addRoleName(self.ActiveRole, "active") self._projects = [] # type: List[DigitalFactoryProjectResponse] def setProjects(self, df_projects: List[DigitalFactoryProjectResponse]) -> None: @@ -59,5 +61,6 @@ class DigitalFactoryProjectModel(ListModel): "thumbnailUrl": project.thumbnail_url, "username": project.username, "lastUpdated": project.last_updated.strftime(PROJECT_UPDATED_AT_DATETIME_FORMAT) if project.last_updated else "", + "active": project.active, }) self.dfProjectModelChanged.emit() diff --git a/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py b/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py index bef90e5125..303271f211 100644 --- a/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py +++ b/plugins/DigitalLibrary/src/DigitalFactoryProjectResponse.py @@ -28,6 +28,7 @@ class DigitalFactoryProjectResponse(BaseModel): team_ids: Optional[List[str]] = None, status: Optional[str] = None, technical_requirements: Optional[Dict[str, Any]] = None, + is_inactive: bool = False, **kwargs) -> None: """ Creates a new digital factory project response object @@ -56,6 +57,7 @@ class DigitalFactoryProjectResponse(BaseModel): self.last_updated = datetime.strptime(last_updated, DIGITAL_FACTORY_RESPONSE_DATETIME_FORMAT) if last_updated else None self.status = status self.technical_requirements = technical_requirements + self.active = not is_inactive super().__init__(**kwargs) def __str__(self) -> str: diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 8d09d7837d..f8686aafcf 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -496,7 +496,7 @@ "monitor_carousel_dot_current": [119, 119, 119, 255], "cloud_unavailable": [153, 153, 153, 255], - "cloud_inactive": "warning", + "cloud_inactive": [253, 209, 58, 255], "connection_badge_background": [255, 255, 255, 255], "warning_badge_background": [0, 0, 0, 255], "error_badge_background": [255, 255, 255, 255], From b6b2da0c148af19614cf90537dd3599263fbc316 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 12 Jun 2025 08:39:59 +0200 Subject: [PATCH 11/55] Change wording as suggested CURA-11978 "Unretraction" is a barbaric word, better use "Priming" instead --- plugins/SimulationView/SimulationViewMenuComponent.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/SimulationView/SimulationViewMenuComponent.qml b/plugins/SimulationView/SimulationViewMenuComponent.qml index 0e254a005b..78b0b2b74f 100644 --- a/plugins/SimulationView/SimulationViewMenuComponent.qml +++ b/plugins/SimulationView/SimulationViewMenuComponent.qml @@ -229,7 +229,7 @@ Cura.ExpandableComponent { const travelsTypesModel = [ { - label: catalog.i18nc("@label", "Unretracted"), + label: catalog.i18nc("@label", "Not retracted"), colorId: "layerview_move_combing" }, { @@ -241,7 +241,7 @@ Cura.ExpandableComponent colorId: "layerview_move_while_retracting" }, { - label: catalog.i18nc("@label", "Unretracting"), + label: catalog.i18nc("@label", "Priming"), colorId: "layerview_move_while_unretracting" } ]; From 53b91a7b4841456f37021ff80c766699206ac234 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 12 Jun 2025 16:40:28 +0200 Subject: [PATCH 12/55] Add setting to keep retracting during travel CURA-11978 --- resources/definitions/fdmprinter.def.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 3ad11933e6..6747bf9ceb 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -9324,6 +9324,16 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "keep_retracting_during_travel": + { + "label": "Keep Retracting During Travel", + "description": "When retraction during travel is enabled, and there is more than enough time to perform a full retract during a travel move, spread the retraction over the whole travel move with a lower retraction speed, so that we do not travel with a non-retracting nozzle. This can help reducing oozing.", + "type": "bool", + "default_value": false, + "enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\" and retraction_during_travel_ratio > 0", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "prime_during_travel_ratio": { "label": "Prime During Travel Move", From d0947c5fb2634e9b7a046dfd18050bbaadfdacff Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 13 Jun 2025 16:17:58 +0200 Subject: [PATCH 13/55] Include new print feature type CURA-11978 --- cura/LayerPolygon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index c4d57c07a0..cd4642d719 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -25,7 +25,8 @@ class LayerPolygon: PrimeTowerType = 11 MoveWhileRetractingType = 12 MoveWhileUnretractingType = 13 - __number_of_types = 14 + StationaryRetractUnretract = 14 + __number_of_types = 15 __jump_map = numpy.logical_or(numpy.logical_or(numpy.logical_or( numpy.arange(__number_of_types) == NoneType, @@ -281,6 +282,7 @@ class LayerPolygon: theme.getColor("layerview_prime_tower").getRgbF(), # PrimeTowerType theme.getColor("layerview_move_while_retracting").getRgbF(), # MoveWhileRetracting theme.getColor("layerview_move_while_unretracting").getRgbF(), # MoveWhileUnretracting + theme.getColor("layerview_move_retraction").getRgbF(), # StationaryRetractUnretract ]) return cls.__color_map From 265599a52ded3724c4d0656276156841ed200daa Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 01:31:17 +0800 Subject: [PATCH 14/55] add Anycubic kobra 3 v2 (and ace pro) profiles --- .../definitions/anycubic_kobra3v2.def.json | 52 +++++++++++++ .../anycubic_kobra3v2_ACE_PRO.def.json | 69 ++++++++++++++++++ ...ycubic_kobra3v2_ACEPRO_extruder_0.def.json | 16 ++++ ...ycubic_kobra3v2_ACEPRO_extruder_1.def.json | 16 ++++ ...ycubic_kobra3v2_ACEPRO_extruder_2.def.json | 16 ++++ ...ycubic_kobra3v2_ACEPRO_extruder_3.def.json | 16 ++++ .../anycubic_kobra3v2_extruder_0.def.json | 16 ++++ .../meshes/anycubic_kobra3v2_buildplate.stl | Bin 0 -> 4284 bytes 8 files changed, 201 insertions(+) create mode 100644 resources/definitions/anycubic_kobra3v2.def.json create mode 100644 resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json create mode 100644 resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json create mode 100644 resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json create mode 100644 resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json create mode 100644 resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json create mode 100644 resources/extruders/anycubic_kobra3v2_extruder_0.def.json create mode 100644 resources/meshes/anycubic_kobra3v2_buildplate.stl diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json new file mode 100644 index 0000000000..59afb1c660 --- /dev/null +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -0,0 +1,52 @@ +{ + "version": 2, + "name": "Anycubic Kobra 3 v2", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "Sam Bonnekamp", + "manufacturer": "Anycubic", + "file_formats": "text/x-gcode", + "platform": "anycubic_kobra3v2_buildplate.STL", + "machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" } + }, + "overrides": + { + "machine_height": { "default_value": 260 }, + "machine_width": { "default_value": 250 }, + "machine_depth": { "default_value": 250 }, + "machine_name": + { + "description": "Anycubic Kobra 3 v2", + "default_value": "Anycubic Kobra 3 v2" + }, + "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, + "machine_heated_bed": { "default_value": true}, + "material_bed_temperature": + { + "default_value": 60, + "maximum_value": "110", + "maximum_value_warning": "90" + }, + "material_print_temp_wait": { "value": true }, + "machine_center_is_zero": { "default_value": false }, + "layer_height": { "default_value": 0.2 }, + "speed_slowdown_layers": { "value": 2 }, + "material_diameter": { "default_value": 1.75 }, + "material_initial_print_temperature": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "material_print_temperature": { "maximum_value_warning": 250 }, + "material_print_temperature_layer_0": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "material_print_temp_prep": { "default_value": false }, + "machine_start_gcode_first": { "default_value": true }, + "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\nTYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" } + } +} diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json new file mode 100644 index 0000000000..ab2f9a3b95 --- /dev/null +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -0,0 +1,69 @@ +{ + "version": 2, + "name": "Anycubic Kobra 3 v2 ACE PRO", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "Sam Bonnekamp", + "manufacturer": "Anycubic", + "file_formats": "text/x-gcode", + "platform": "anycubic_kobra3v2_buildplate.STL", + "machine_extruder_trains": { + "0": "anycubic_kobra3v2_ACEPRO_extruder_0", + "1": "anycubic_kobra3v2_ACEPRO_extruder_1", + "2": "anycubic_kobra3v2_ACEPRO_extruder_2", + "3": "anycubic_kobra3v2_ACEPRO_extruder_3" + } + }, + "overrides": + { + "machine_height": { "default_value": 260 }, + "machine_width": { "default_value": 250 }, + "machine_depth": { "default_value": 250 }, + "machine_name": + { + "description": "Anycubic Kobra 3 v2", + "default_value": "Anycubic Kobra 3 v2" + }, + "machine_extruder_count": { "default_value": 4 }, + "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, + "machine_heated_bed": { "default_value": true}, + "material_bed_temperature": + { + "default_value": 60, + "maximum_value": "110", + "maximum_value_warning": "90" + }, + "material_print_temp_wait": { "value": true }, + "machine_center_is_zero": { "default_value": false }, + "layer_height": { "default_value": 0.2 }, + "speed_slowdown_layers": { "value": 2 }, + "material_diameter": { "default_value": 1.75 }, + "material_initial_print_temperature": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "default_material_print_temperature": + { + "maximum_value": 300, + "default_value": 200 + }, + "material_print_temperature": + { + "maximum_value": 300, + "default_value": 200 + }, + "material_standby_temperature" : {"default_value": "material_print_temperature"}, + "material_print_temperature_layer_0": + { + "maximum_value_warning": 295, + "value": "material_print_temperature + 5" + }, + "material_print_temp_prep": { "default_value": false }, + "machine_start_gcode_first": { "default_value": true }, + "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\nTYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } + } +} diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json new file mode 100644 index 0000000000..93c8618bf4 --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Colour 1", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json new file mode 100644 index 0000000000..ed41ca1946 --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Colour 2", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "1" + }, + "overrides": + { + "extruder_nr": { "default_value": 1 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json new file mode 100644 index 0000000000..2fc2705ebb --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Colour 3", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "1" + }, + "overrides": + { + "extruder_nr": { "default_value": 2 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json new file mode 100644 index 0000000000..f5ef25efc5 --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "ACE Pro Colour 4", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2_ACE_PRO", + "position": "3" + }, + "overrides": + { + "extruder_nr": { "default_value": 3 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json new file mode 100644 index 0000000000..dba5e6e559 --- /dev/null +++ b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json @@ -0,0 +1,16 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": + { + "machine": "anycubic_kobra3v2", + "position": "0" + }, + "overrides": + { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/meshes/anycubic_kobra3v2_buildplate.stl b/resources/meshes/anycubic_kobra3v2_buildplate.stl new file mode 100644 index 0000000000000000000000000000000000000000..9d526d0edada625da668ad41379a2d299965b68b GIT binary patch literal 4284 zcmbW4KWmgh5XKihvCvA;I-OXG1nl1P{03q#F|h~+Jx~j+_G{Qks^AAmeRqkCpFu$o zY;24m_$Q6d^Ugju&+gqT;=<+TotfXx&g|^ImnV<+c6TB#zWwbf}<$2iC)MBt!q!EMqVFDHY57x#m7U#p@&J*WudNx-56W$1k{k3-vU z#vqLt)YCumP>T|jL0C`COJCX3%wljqNFxUI!vri!P=?+I^FR)a!FdsbdiqBmYEgo+ z%wJ#-=iihwDDkKlBD<^>#v54G+=+`#49270z3?|T1ayqY7@#qJ*nWJqk3YRd{~UWB zoEHT3FAjzXSd^d)y$`Mm^D_qLMGWfcA9<)n3Ci{ScmlyaC(Rhp`{-AcT_%C2mcVm8 zKr;Gx@oN9WK;41@!J5>><(C&G*DsAq_tXS$nSSjP_SpN6S53yMppStVvCntj-CWK) zOsJ(LuD$+U$H>?dB_PvlI1jZbvGe)La)prvVkl92MGb30tE+_OG)&Z9QNxyCm!R%5 ztIfI|3#Q)>^Wsh^!FSY}9TY;nqTeEdpAoevq1_Wq-Mji$tNU1q+7k0>FD(7Duc~^J z+4*3tf?g6PU{z}2dkOX7`(w3MN~qUgc5uQ|RL{%z$7&3nOTD0HPDsE$Hh2bUQDRmT z4)nju*ws)mUP{rG#Z?Gq3l(eC%SXPd;#$6P7bhyEI3Fp+4z`3^TEfq~5ZN;CbnPyq z3K-1XpZFJqT39RCqYCuKcN;?qtw=B}pq#n1~Lze z5^*cJr@OaA?WGk73NgZ>gr0)%_ZUIcUjNd&Ns^O6kP@H~3lp$I-Q~ zuN7{%^~89gM9Ae%%(kLAmZfXgc%hG5v?t`2(ceBP1q}kfL%)|iB6@vCg?^YAFJdK1 zgZO0gpFQWlceSG0yP+Q@#tS7vF2uaV$huQ8uiotqA-BHg!OjR8gpKSSQs3tquU=Kp O^gX4-c%ekdi17zYNr Date: Wed, 18 Jun 2025 02:39:22 +0800 Subject: [PATCH 15/55] removed redudant fields --- .../definitions/anycubic_kobra3v2.def.json | 18 +++++++++++++----- .../anycubic_kobra3v2_ACE_PRO.def.json | 6 ++---- ...nycubic_kobra3v2_ACEPRO_extruder_2.def.json | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index 59afb1c660..ec37f3174a 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -20,15 +20,24 @@ { "description": "Anycubic Kobra 3 v2", "default_value": "Anycubic Kobra 3 v2" - }, + }, "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, "machine_heated_bed": { "default_value": true}, + "material_bed_temperature": - { - "default_value": 60, - "maximum_value": "110", + { "maximum_value": "110", "maximum_value_warning": "90" }, + "default_material_print_temperature": + { + "maximum_value": 300, + "default_value": 210 + }, + "material_print_temperature": + { + "maximum_value": 300, + "default_value": 210 + }, "material_print_temp_wait": { "value": true }, "machine_center_is_zero": { "default_value": false }, "layer_height": { "default_value": 0.2 }, @@ -45,7 +54,6 @@ "maximum_value_warning": 295, "value": "material_print_temperature + 5" }, - "material_print_temp_prep": { "default_value": false }, "machine_start_gcode_first": { "default_value": true }, "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\nTYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" } } diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index ab2f9a3b95..a201ab32f4 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -31,7 +31,6 @@ "machine_heated_bed": { "default_value": true}, "material_bed_temperature": { - "default_value": 60, "maximum_value": "110", "maximum_value_warning": "90" }, @@ -48,12 +47,12 @@ "default_material_print_temperature": { "maximum_value": 300, - "default_value": 200 + "default_value": 210 }, "material_print_temperature": { "maximum_value": 300, - "default_value": 200 + "default_value": 210 }, "material_standby_temperature" : {"default_value": "material_print_temperature"}, "material_print_temperature_layer_0": @@ -61,7 +60,6 @@ "maximum_value_warning": 295, "value": "material_print_temperature + 5" }, - "material_print_temp_prep": { "default_value": false }, "machine_start_gcode_first": { "default_value": true }, "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\nTYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json index 2fc2705ebb..2761e3d08e 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json @@ -5,7 +5,7 @@ "metadata": { "machine": "anycubic_kobra3v2_ACE_PRO", - "position": "1" + "position": "2" }, "overrides": { From c5550695d664678ab6cafb8f85f4423a147e2b9a Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 12:39:29 +0800 Subject: [PATCH 16/55] removed redundant directives, removed colonialism --- resources/definitions/anycubic_kobra3v2.def.json | 15 ++------------- .../anycubic_kobra3v2_ACE_PRO.def.json | 16 +++------------- .../anycubic_kobra3v2_ACEPRO_extruder_0.def.json | 4 ++-- .../anycubic_kobra3v2_ACEPRO_extruder_1.def.json | 4 ++-- .../anycubic_kobra3v2_ACEPRO_extruder_2.def.json | 2 +- .../anycubic_kobra3v2_ACEPRO_extruder_3.def.json | 4 ++-- .../anycubic_kobra3v2_extruder_0.def.json | 2 +- 7 files changed, 13 insertions(+), 34 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index ec37f3174a..8c24b1391c 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -8,7 +8,7 @@ "author": "Sam Bonnekamp", "manufacturer": "Anycubic", "file_formats": "text/x-gcode", - "platform": "anycubic_kobra3v2_buildplate.STL", + "platform": "anycubic_kobra3v2_buildplate.stl", "machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" } }, "overrides": @@ -27,21 +27,10 @@ "material_bed_temperature": { "maximum_value": "110", "maximum_value_warning": "90" - }, - "default_material_print_temperature": - { - "maximum_value": 300, - "default_value": 210 }, - "material_print_temperature": - { - "maximum_value": 300, - "default_value": 210 - }, - "material_print_temp_wait": { "value": true }, + "material_print_temperature":{ "maximum_value": 300 }, "machine_center_is_zero": { "default_value": false }, "layer_height": { "default_value": 0.2 }, - "speed_slowdown_layers": { "value": 2 }, "material_diameter": { "default_value": 1.75 }, "material_initial_print_temperature": { diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index a201ab32f4..6b9c65e59a 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -8,7 +8,7 @@ "author": "Sam Bonnekamp", "manufacturer": "Anycubic", "file_formats": "text/x-gcode", - "platform": "anycubic_kobra3v2_buildplate.STL", + "platform": "anycubic_kobra3v2_buildplate.stl", "machine_extruder_trains": { "0": "anycubic_kobra3v2_ACEPRO_extruder_0", "1": "anycubic_kobra3v2_ACEPRO_extruder_1", @@ -37,23 +37,13 @@ "material_print_temp_wait": { "value": true }, "machine_center_is_zero": { "default_value": false }, "layer_height": { "default_value": 0.2 }, - "speed_slowdown_layers": { "value": 2 }, "material_diameter": { "default_value": 1.75 }, "material_initial_print_temperature": { "maximum_value_warning": 295, "value": "material_print_temperature + 5" - }, - "default_material_print_temperature": - { - "maximum_value": 300, - "default_value": 210 - }, - "material_print_temperature": - { - "maximum_value": 300, - "default_value": 210 - }, + }, + "material_print_temperature": { "maximum_value": 300 }, "material_standby_temperature" : {"default_value": "material_print_temperature"}, "material_print_temperature_layer_0": { diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json index 93c8618bf4..104d6bb0f1 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json @@ -1,10 +1,10 @@ { "version": 2, - "name": "ACE Pro Colour 1", + "name": "ACE Pro Color 1", "inherits": "fdmextruder", "metadata": { - "machine": "anycubic_kobra3v2_ACE_PRO", + "machine": "kobra3v2_ACE_PRO", "position": "0" }, "overrides": diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json index ed41ca1946..2c3d072215 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json @@ -1,10 +1,10 @@ { "version": 2, - "name": "ACE Pro Colour 2", + "name": "ACE Pro Color 2", "inherits": "fdmextruder", "metadata": { - "machine": "anycubic_kobra3v2_ACE_PRO", + "machine": "kobra3v2_ACE_PRO", "position": "1" }, "overrides": diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json index 2761e3d08e..fb732d5fd4 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json @@ -1,6 +1,6 @@ { "version": 2, - "name": "ACE Pro Colour 3", + "name": "ACE Pro Color 3", "inherits": "fdmextruder", "metadata": { diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json index f5ef25efc5..fb64b590fc 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json @@ -1,10 +1,10 @@ { "version": 2, - "name": "ACE Pro Colour 4", + "name": "ACE Pro Color 4", "inherits": "fdmextruder", "metadata": { - "machine": "anycubic_kobra3v2_ACE_PRO", + "machine": "kobra3v2_ACE_PRO", "position": "3" }, "overrides": diff --git a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json index dba5e6e559..d96e91ce07 100644 --- a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json +++ b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json @@ -4,7 +4,7 @@ "inherits": "fdmextruder", "metadata": { - "machine": "anycubic_kobra3v2", + "machine": "kobra3v2_ACE_PRO", "position": "0" }, "overrides": From bbe0b7f9f5a30adeb546f7501f5c2aba8e32ccc9 Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 13:14:53 +0800 Subject: [PATCH 17/55] missing comment semicolon --- resources/definitions/anycubic_kobra3v2.def.json | 2 +- resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index 8c24b1391c..8ca9800fde 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -44,6 +44,6 @@ "value": "material_print_temperature + 5" }, "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\nTYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" } + "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" } } } diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index 6b9c65e59a..9686c4c192 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -51,7 +51,7 @@ "value": "material_print_temperature + 5" }, "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\nTYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } } } From bfbc6e4dc8269ca51e23e84af3072417f9e9015b Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 13:27:02 +0800 Subject: [PATCH 18/55] update printer names --- .../extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json | 2 +- .../extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json | 2 +- .../extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json | 2 +- resources/extruders/anycubic_kobra3v2_extruder_0.def.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json index 104d6bb0f1..ab4a7d1a68 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json @@ -4,7 +4,7 @@ "inherits": "fdmextruder", "metadata": { - "machine": "kobra3v2_ACE_PRO", + "machine": "anycubic_kobra3v2_ACE_PRO", "position": "0" }, "overrides": diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json index 2c3d072215..029e7ad2bf 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json @@ -4,7 +4,7 @@ "inherits": "fdmextruder", "metadata": { - "machine": "kobra3v2_ACE_PRO", + "machine": "anycubic_kobra3v2_ACE_PRO", "position": "1" }, "overrides": diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json index fb64b590fc..2f64b0532a 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json @@ -4,7 +4,7 @@ "inherits": "fdmextruder", "metadata": { - "machine": "kobra3v2_ACE_PRO", + "machine": "anycubic_kobra3v2_ACE_PRO", "position": "3" }, "overrides": diff --git a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json index d96e91ce07..dba5e6e559 100644 --- a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json +++ b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json @@ -4,7 +4,7 @@ "inherits": "fdmextruder", "metadata": { - "machine": "kobra3v2_ACE_PRO", + "machine": "anycubic_kobra3v2", "position": "0" }, "overrides": From 4f52a3d9389e192a7be2e32d19f1e5172b1e2580 Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 17:21:03 +0800 Subject: [PATCH 19/55] machine uses relative extrusion --- .../definitions/anycubic_kobra3v2.def.json | 21 ++++++++++++------- .../anycubic_kobra3v2_ACE_PRO.def.json | 19 ++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index 8ca9800fde..a6d1c26d33 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -8,8 +8,9 @@ "author": "Sam Bonnekamp", "manufacturer": "Anycubic", "file_formats": "text/x-gcode", + "has_textured_buildplate": true, "platform": "anycubic_kobra3v2_buildplate.stl", - "machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" } + "machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" } }, "overrides": { @@ -20,17 +21,19 @@ { "description": "Anycubic Kobra 3 v2", "default_value": "Anycubic Kobra 3 v2" - }, + }, "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, "machine_heated_bed": { "default_value": true}, - + "machine_center_is_zero": { "default_value": false }, + "machine_start_gcode_first": { "default_value": true }, + "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } + "material_bed_temperature": { "maximum_value": "110", - "maximum_value_warning": "90" + "maximum_value_warning": "90" }, "material_print_temperature":{ "maximum_value": 300 }, - "machine_center_is_zero": { "default_value": false }, - "layer_height": { "default_value": 0.2 }, "material_diameter": { "default_value": 1.75 }, "material_initial_print_temperature": { @@ -43,7 +46,9 @@ "maximum_value_warning": 295, "value": "material_print_temperature + 5" }, - "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" } + + "layer_height": { "default_value": 0.2 }, + "adhesion_type": { "value": "'skirt'" } + "relative_extrusion" : {"default_value": true} } } diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index 9686c4c192..bc76df7a99 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -8,6 +8,7 @@ "author": "Sam Bonnekamp", "manufacturer": "Anycubic", "file_formats": "text/x-gcode", + "has_textured_buildplate": true, "platform": "anycubic_kobra3v2_buildplate.stl", "machine_extruder_trains": { "0": "anycubic_kobra3v2_ACEPRO_extruder_0", @@ -36,22 +37,26 @@ }, "material_print_temp_wait": { "value": true }, "machine_center_is_zero": { "default_value": false }, - "layer_height": { "default_value": 0.2 }, + "machine_start_gcode_first": { "default_value": true }, + "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } + "material_diameter": { "default_value": 1.75 }, "material_initial_print_temperature": - { + { "maximum_value_warning": 295, "value": "material_print_temperature + 5" - }, - "material_print_temperature": { "maximum_value": 300 }, + }, + "material_print_temperature": { "maximum_value": 300 }, "material_standby_temperature" : {"default_value": "material_print_temperature"}, "material_print_temperature_layer_0": { "maximum_value_warning": 295, "value": "material_print_temperature + 5" }, - "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, - "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } + + "layer_height": { "default_value": 0.2 }, + "adhesion_type": { "value": "'skirt'" }, + "relative_extrusion" : {"default_value": true} } } From 0ed4be3e94bf80effae215594558dd1bca35a84e Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 17:32:45 +0800 Subject: [PATCH 20/55] machine end gcode matches anycubic --- resources/definitions/anycubic_kobra3v2.def.json | 2 +- resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index a6d1c26d33..597c3578ed 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -27,7 +27,7 @@ "machine_center_is_zero": { "default_value": false }, "machine_start_gcode_first": { "default_value": true }, "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, - "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } + "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, "material_bed_temperature": { "maximum_value": "110", diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index bc76df7a99..c1738ddacd 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -39,7 +39,7 @@ "machine_center_is_zero": { "default_value": false }, "machine_start_gcode_first": { "default_value": true }, "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, - "machine_end_gcode": { "default_value": "M104 S0\nM140 S0\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG27 P2 ;Park toolhead\nM84" } + "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, "material_diameter": { "default_value": 1.75 }, "material_initial_print_temperature": From 350c95a110ee4074be8b80b9fe029bc58a7eb865 Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 17:36:32 +0800 Subject: [PATCH 21/55] missing semicolon --- resources/definitions/anycubic_kobra3v2.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index 597c3578ed..0f87ea130d 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -48,7 +48,7 @@ }, "layer_height": { "default_value": 0.2 }, - "adhesion_type": { "value": "'skirt'" } + "adhesion_type": { "value": "'skirt'" }, "relative_extrusion" : {"default_value": true} } } From f7dc928d3283878e80ad8694ebc6d24a78cc9fea Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Wed, 18 Jun 2025 19:03:55 +0800 Subject: [PATCH 22/55] fixed unecessary default --- resources/definitions/anycubic_kobra3v2.def.json | 2 +- resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index 0f87ea130d..0f32de2604 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -49,6 +49,6 @@ "layer_height": { "default_value": 0.2 }, "adhesion_type": { "value": "'skirt'" }, - "relative_extrusion" : {"default_value": true} + "relative_extrusion" : {"value": true} } } diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index c1738ddacd..b85fb3c0ed 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -57,6 +57,6 @@ "layer_height": { "default_value": 0.2 }, "adhesion_type": { "value": "'skirt'" }, - "relative_extrusion" : {"default_value": true} + "relative_extrusion" : {"value": true} } } From 425a167391252c41b920955a77ecf38a6f94f4bd Mon Sep 17 00:00:00 2001 From: Sam Bonnekamp Date: Thu, 19 Jun 2025 15:28:49 +0800 Subject: [PATCH 23/55] added 32px cura thumbnail to start gcode --- resources/definitions/anycubic_kobra3v2.def.json | 2 +- resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index 0f32de2604..143d08b92b 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -26,7 +26,7 @@ "machine_heated_bed": { "default_value": true}, "machine_center_is_zero": { "default_value": false }, "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, "material_bed_temperature": diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index b85fb3c0ed..6e6fb31e2a 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -38,7 +38,7 @@ "material_print_temp_wait": { "value": true }, "machine_center_is_zero": { "default_value": false }, "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, "material_diameter": { "default_value": 1.75 }, From 95f35275be464edf537a4abb76bd16c04f95cd60 Mon Sep 17 00:00:00 2001 From: HellAholic <28710690+HellAholic@users.noreply.github.com> Date: Fri, 20 Jun 2025 07:22:21 +0000 Subject: [PATCH 24/55] Apply printer-linter format --- .../definitions/anycubic_kobra3v2.def.json | 52 ++++++------- .../anycubic_kobra3v2_ACE_PRO.def.json | 77 +++++++++---------- ...ycubic_kobra3v2_ACEPRO_extruder_0.def.json | 2 +- ...ycubic_kobra3v2_ACEPRO_extruder_1.def.json | 2 +- ...ycubic_kobra3v2_ACEPRO_extruder_2.def.json | 2 +- ...ycubic_kobra3v2_ACEPRO_extruder_3.def.json | 2 +- .../anycubic_kobra3v2_extruder_0.def.json | 2 +- 7 files changed, 68 insertions(+), 71 deletions(-) diff --git a/resources/definitions/anycubic_kobra3v2.def.json b/resources/definitions/anycubic_kobra3v2.def.json index 143d08b92b..6b8df0cc4b 100644 --- a/resources/definitions/anycubic_kobra3v2.def.json +++ b/resources/definitions/anycubic_kobra3v2.def.json @@ -8,34 +8,35 @@ "author": "Sam Bonnekamp", "manufacturer": "Anycubic", "file_formats": "text/x-gcode", - "has_textured_buildplate": true, "platform": "anycubic_kobra3v2_buildplate.stl", - "machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" } + "has_textured_buildplate": true, + "machine_extruder_trains": { "0": "anycubic_kobra3v2_extruder_0" } }, "overrides": { + "adhesion_type": { "value": "'skirt'" }, + "layer_height": { "default_value": 0.2 }, + "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, + "machine_center_is_zero": { "default_value": false }, + "machine_depth": { "default_value": 250 }, + "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, + "machine_heated_bed": { "default_value": true }, "machine_height": { "default_value": 260 }, - "machine_width": { "default_value": 250 }, - "machine_depth": { "default_value": 250 }, - "machine_name": - { - "description": "Anycubic Kobra 3 v2", - "default_value": "Anycubic Kobra 3 v2" + "machine_name": + { + "default_value": "Anycubic Kobra 3 v2", + "description": "Anycubic Kobra 3 v2" }, - "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, - "machine_heated_bed": { "default_value": true}, - "machine_center_is_zero": { "default_value": false }, - "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, - "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, - - "material_bed_temperature": - { "maximum_value": "110", - "maximum_value_warning": "90" - }, - "material_print_temperature":{ "maximum_value": 300 }, - "material_diameter": { "default_value": 1.75 }, - "material_initial_print_temperature": + "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_start_gcode_first": { "default_value": true }, + "machine_width": { "default_value": 250 }, + "material_bed_temperature": + { + "maximum_value": "110", + "maximum_value_warning": "90" + }, + "material_diameter": { "default_value": 1.75 }, + "material_initial_print_temperature": { "maximum_value_warning": 295, "value": "material_print_temperature + 5" @@ -46,9 +47,6 @@ "maximum_value_warning": 295, "value": "material_print_temperature + 5" }, - - "layer_height": { "default_value": 0.2 }, - "adhesion_type": { "value": "'skirt'" }, - "relative_extrusion" : {"value": true} + "relative_extrusion": { "value": true } } -} +} \ No newline at end of file diff --git a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json index 6e6fb31e2a..fc464c9eee 100644 --- a/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json +++ b/resources/definitions/anycubic_kobra3v2_ACE_PRO.def.json @@ -8,55 +8,54 @@ "author": "Sam Bonnekamp", "manufacturer": "Anycubic", "file_formats": "text/x-gcode", - "has_textured_buildplate": true, "platform": "anycubic_kobra3v2_buildplate.stl", - "machine_extruder_trains": { - "0": "anycubic_kobra3v2_ACEPRO_extruder_0", - "1": "anycubic_kobra3v2_ACEPRO_extruder_1", - "2": "anycubic_kobra3v2_ACEPRO_extruder_2", - "3": "anycubic_kobra3v2_ACEPRO_extruder_3" - } + "has_textured_buildplate": true, + "machine_extruder_trains": + { + "0": "anycubic_kobra3v2_ACEPRO_extruder_0", + "1": "anycubic_kobra3v2_ACEPRO_extruder_1", + "2": "anycubic_kobra3v2_ACEPRO_extruder_2", + "3": "anycubic_kobra3v2_ACEPRO_extruder_3" + } }, "overrides": { - "machine_height": { "default_value": 260 }, - "machine_width": { "default_value": 250 }, - "machine_depth": { "default_value": 250 }, - "machine_name": - { - "description": "Anycubic Kobra 3 v2", - "default_value": "Anycubic Kobra 3 v2" - }, + "adhesion_type": { "value": "'skirt'" }, + "layer_height": { "default_value": 0.2 }, + "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, + "machine_center_is_zero": { "default_value": false }, + "machine_depth": { "default_value": 250 }, + "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, "machine_extruder_count": { "default_value": 4 }, - "machine_buildplate_type": { "default_value": "PEI Spring Steel" }, - "machine_heated_bed": { "default_value": true}, - "material_bed_temperature": - { - "maximum_value": "110", - "maximum_value_warning": "90" - }, - "material_print_temp_wait": { "value": true }, - "machine_center_is_zero": { "default_value": false }, - "machine_start_gcode_first": { "default_value": true }, - "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, - "machine_end_gcode": { "default_value": "G1 X5 Y{machine_depth*0.95} F{speed_travel*60} ; present print\nM140 S0 ; turn off heat bed\nM104 S0 ; turn off temperature\nM84; disable motors ; disable stepper motors" }, - - "material_diameter": { "default_value": 1.75 }, - "material_initial_print_temperature": - { + "machine_heated_bed": { "default_value": true }, + "machine_height": { "default_value": 260 }, + "machine_name": + { + "default_value": "Anycubic Kobra 3 v2", + "description": "Anycubic Kobra 3 v2" + }, + "machine_start_gcode": { "default_value": "; thumbnail begin 32x32\n; iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAX\n; NSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARWSURBVHgB1Vc7bx1FFD5n/cC+YOQ4RBTXlg0STfIH\n; gM78h0SAhGgQjyqARO8eFEQFBaKhCHIoKagcOkJJQUWBI19XJLaJgp+5M8x5zZzdO+sg0ZCR1zM7u3\n; e+73znMbMAT0KLMVqPdKVhvuze+o2NjUafget7Gz4GeO3s7Ow29YiYiXSJ2TPft0BwEmZ2dpYnp+Ec\n; 8NPT09tpyOAhhF4CXSBPwhOvEZk+B/ynNFzd2QvwxlfHMEq9PqR//mVaWcegmqKCIf9FKGS2Px30Ey\n; A/Hh8fE/gWgRPomww+VkxVgYFoWcz3xXpUJo2QsXvEFu8qAQJvmobA13b3I1u+Q+AhQuRfB1kTBDS6\n; FUVmVBx5iwmqNIgNv9ONkUzg4ODgBQNn2b80yyNbCzFQqMsC4MCNg/eveSQGdQM6glhXYDAYbJHvRw\n; q+ux90dQ8eeBxacaD+5T5ZSdLbfIcT61hTgCZTuq1R/zqDjxMOva6SKzBdMQRoO1OsovcLZrpPkmOO\n; ytibCdPejOQCGJHl5l+zlEGNxBiuvjxI1zNwZTgDzw4a6LbV6/fTT8eqSGD/G3hVgTQRT05OOv6Nyl\n; zBwxgW5iN8/e4leOWlOTivESjAlNhvYJoEvTEA6gq7wIqMyZ76zevPw5XlWdi5/wi+2foLNu88hAeH\n; QQKQJG/ScjjFY1cUFD3mYKxmgeef+1z9AstO4KMEfu3zXdi594gXlHIj3hYFY6su5fVQU7OjgAvZNg\n; kpaDFn8jvrC9zf+GEvgZ+16HqX5QRVctl6FmAyXhq3TmHnROBhWuBysp7aj7/+rS+IWybKO8oPWTns\n; zFX2kxalEKTe+6pZXR9kx0X3olhXdmqJ+GIF6lyfC8ArUCnZ8PPvJ9xfXn4KLMCkuBlo306payJMWD\n; 9BoGspE9LrVop4ajfevgQrz82U5xkQyg7onyEWfpUzxSQBlUnjWRZM8t66cwi/jU5h5eIMbH40hKuv\n; LqS6UFwh7mhv1RL4AYojY9VQbqkQRWL34icPtQ5o6dXqR9fwQlLgrYuPLUSrH96TvZDigsg1UwxFc3\n; c/e5rfsRNRI/JFNNns8MBy8QKNvJYKzGgf4NoXf8LH3+5xTDw4jP0szH16ZvBlybdcitMhpJzrwKUB\n; jcmCiDkDvv/liK92qRWLeTdsZYSsQaPhhXOOZD44uLJhqXAIxYKIQUIaSqBGg+Di09CuBiUYxQ0E/t\n; 0Hc2bk9gQB26kiYJEK7a6kJyqdvL/bKSiKOmx9dGumfpnA35/jnsDH4/H6BAH7Qba3dobTTQYrOY0N\n; usOnbr9JieEiCPgSzzH4/Pz8dpVASw3T2OY0Y2V7FkBrdjDOarErkLPmpgNPkb/u5e8l4CqHG1IqhU\n; xEoQuZztlwuIRw8705WFma8uB3a0jcUhb05pQ/zdp+Ufso8R8hdLqygDs6OnptcXHxD+gx9V8R6H6a\n; dQ8WrSwq81XZ/1NToLztuY9T8D3U687/r/0D2siIlZoKRzIAAAAASUVORK5CYII=\n; thumbnail end\n; external perimeters extrusion width = 0.42mm\n; perimeters extrusion width = 0.45mm\n; infill extrusion width = 0.45mm\n; solid infill extrusion width = 0.45mm\n; top infill extrusion width = 0.42mm\n; support material extrusion width = 0.42mm\n; first layer extrusion width = 0.50mm\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117 ;display LCD message\nM900 K0.05 ;linear advance factor, ive only seen this set to k0.05\n;START HEADER\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nT0 ;set or report the current extruder or other tool\nM107 ; turn fan off" }, + "machine_start_gcode_first": { "default_value": true }, + "machine_width": { "default_value": 250 }, + "material_bed_temperature": + { + "maximum_value": "110", + "maximum_value_warning": "90" + }, + "material_diameter": { "default_value": 1.75 }, + "material_initial_print_temperature": + { "maximum_value_warning": 295, "value": "material_print_temperature + 5" - }, - "material_print_temperature": { "maximum_value": 300 }, - "material_standby_temperature" : {"default_value": "material_print_temperature"}, + }, + "material_print_temp_wait": { "value": true }, + "material_print_temperature": { "maximum_value": 300 }, "material_print_temperature_layer_0": { "maximum_value_warning": 295, "value": "material_print_temperature + 5" }, - - "layer_height": { "default_value": 0.2 }, - "adhesion_type": { "value": "'skirt'" }, - "relative_extrusion" : {"value": true} + "material_standby_temperature": { "default_value": "material_print_temperature" }, + "relative_extrusion": { "value": true } } -} +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json index ab4a7d1a68..5537606b12 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_0.def.json @@ -13,4 +13,4 @@ "machine_nozzle_size": { "default_value": 0.4 }, "material_diameter": { "default_value": 1.75 } } -} +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json index 029e7ad2bf..2370427eea 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_1.def.json @@ -13,4 +13,4 @@ "machine_nozzle_size": { "default_value": 0.4 }, "material_diameter": { "default_value": 1.75 } } -} +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json index fb732d5fd4..ae860e5f7a 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_2.def.json @@ -13,4 +13,4 @@ "machine_nozzle_size": { "default_value": 0.4 }, "material_diameter": { "default_value": 1.75 } } -} +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json index 2f64b0532a..fed2c1fc6b 100644 --- a/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json +++ b/resources/extruders/anycubic_kobra3v2_ACEPRO_extruder_3.def.json @@ -13,4 +13,4 @@ "machine_nozzle_size": { "default_value": 0.4 }, "material_diameter": { "default_value": 1.75 } } -} +} \ No newline at end of file diff --git a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json index dba5e6e559..f5983cf3fb 100644 --- a/resources/extruders/anycubic_kobra3v2_extruder_0.def.json +++ b/resources/extruders/anycubic_kobra3v2_extruder_0.def.json @@ -13,4 +13,4 @@ "machine_nozzle_size": { "default_value": 0.4 }, "material_diameter": { "default_value": 1.75 } } -} +} \ No newline at end of file From be3d6201425e42cf44a710a52dbc200191b551b5 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Fri, 20 Jun 2025 12:28:51 +0200 Subject: [PATCH 25/55] typo fix --- plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml index 133cc0edde..931a4fe9f0 100644 --- a/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml +++ b/plugins/DigitalLibrary/resources/qml/ProjectSummaryCard.qml @@ -14,7 +14,7 @@ Cura.RoundedRectangle border.color: enabled ? UM.Theme.getColor("lining") : UM.Theme.getColor("action_button_disabled_border") border.width: UM.Theme.getSize("default_lining").width radius: UM.Theme.getSize("default_radius").width - color: getBackgoundColor() + color: getBackgroundColor() signal clicked() property alias imageSource: projectImage.source property alias projectNameText: displayNameLabel.text @@ -106,4 +106,4 @@ Cura.RoundedRectangle return UM.Theme.getColor("action_button_disabled") } } -} \ No newline at end of file +} From 4001e23d913a84bece91bbd54976adb272627d2d Mon Sep 17 00:00:00 2001 From: HellAholic Date: Fri, 20 Jun 2025 14:08:09 +0200 Subject: [PATCH 26/55] Update Cura.proto Add the types missing --- plugins/CuraEngineBackend/Cura.proto | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index 8018c9186f..cdbe463d81 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/plugins/CuraEngineBackend/Cura.proto @@ -78,10 +78,14 @@ message Polygon { SkirtType = 5; InfillType = 6; SupportInfillType = 7; - MoveUnretractedType = 8; - MoveRetractedType = 9; + MoveUnretracted = 8; + MoveRetracted = 9; SupportInterfaceType = 10; PrimeTowerType = 11; + MoveWhileRetracting = 12; + MoveWhileUnretracting = 13; + StationaryRetractUnretract = 14; + NumPrintFeatureTypes = 15; } Type type = 1; // Type of move bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used) From 436d5a669d4fc2af5cd656795ef06508e971d676 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Fri, 20 Jun 2025 20:39:43 +0200 Subject: [PATCH 27/55] Create find-packages.yml --- .github/workflows/find-packages.yml | 118 ++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 .github/workflows/find-packages.yml diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml new file mode 100644 index 0000000000..693dff9ae8 --- /dev/null +++ b/.github/workflows/find-packages.yml @@ -0,0 +1,118 @@ +name: Conan Package Discovery by Jira Ticket + +on: + workflow_dispatch: + inputs: + jira_ticket_number: + description: 'Jira ticket number for Conan package discovery (e.g., cura_12345)' + required: true + type: string + +jobs: + discover_conan_packages: + runs-on: ubuntu-latest + steps: + - name: Checkout repository code + uses: actions/checkout@v4 + + - name: Validate Jira Ticket Number Format + id: validate_input + run: | + set -eou pipefail + JIRA_TICKET="${{ github.event.inputs.jira_ticket_number }}" + # Regex to validate the format: "cura_" followed by one or more digits. + # The '^' and '$' anchors ensure the entire string matches the pattern. + if [[ ! "$JIRA_TICKET" =~ ^cura_[0-9]+$ ]]; then + # Output an error message that will appear as an annotation in the GitHub Actions UI. + # This provides immediate and clear feedback to the user about the expected format. + echo "::error::Invalid Jira ticket number format. Expected format: cura_# (e.g., cura_12345)." + # Exit with a non-zero status code to fail the workflow immediately. + exit 1 + fi + echo "Jira ticket number '$JIRA_TICKET' is valid." + + - name: Setup Conan Client + uses: conan-io/setup-conan@v1 + + - name: Install jq for JSON parsing + run: sudo apt-get update && sudo apt-get install -y jq + + - name: Discover Conan Packages + id: conan_search + env: + CONAN_USERNAME: ${{ secrets.CONAN_USERNAME }} + CONAN_PASSWORD: ${{ secrets.CONAN_PASSWORD }} + run: | + set -eou pipefail + JIRA_TICKET="${{ github.event.inputs.jira_ticket_number }}" + # Construct the full Conan package reference dynamically. + # The format '5.11.0-alpha.0@ultimaker/cura_#' is based on the user's requirement. + CONAN_PACKAGE_REFERENCE="5.11.0-alpha.0@ultimaker/${JIRA_TICKET}" + echo "Searching for Conan packages matching tag: $CONAN_PACKAGE_REFERENCE" + + # Initialize an empty array to store discovered packages. + # This array will hold Markdown-formatted strings for the summary. + DISCOVERED_PACKAGES=() + + # Get a list of all configured Conan remotes in JSON format. + # Conan 2.x's 'conan remote list --format=json' provides structured output. + # This dynamic approach ensures the workflow adapts to any remote configuration changes. + REMOTES_JSON=$(conan remote list --format=json) + + # Parse the JSON to extract remote names. + # The 'jq -r..name' command extracts the 'name' field from each object in the JSON array. + REMOTE_NAMES=$(echo "$REMOTES_JSON" | jq -r '..name') + + # Iterate through each remote to perform a targeted search for binaries. + # For Conan 2.x, 'conan list' is used for detailed package information. + # To find specific binaries, iteration over individual remotes is necessary. + for REMOTE_NAME in $REMOTE_NAMES; do + echo "Searching remote: $REMOTE_NAME" + # Authenticate with the remote if credentials are provided as secrets. + # This is a security best practice for private remotes. + if [ -n "${CONAN_USERNAME:-}" ] && [ -n "${CONAN_PASSWORD:-}" ]; then + echo "Attempting to log in to remote '$REMOTE_NAME'..." + conan remote login "$REMOTE_NAME" -u "$CONAN_USERNAME" -p "$CONAN_PASSWORD" || echo "Login failed for remote $REMOTE_NAME, continuing without authentication for this remote." + fi + # Execute 'conan list' for the specific package reference on the current remote. + # The '--format=json' flag ensures machine-readable output. + # Redirect stderr to /dev/null to suppress non-critical error messages (e.g., remote not found). + SEARCH_RESULT=$(conan list "$CONAN_PACKAGE_REFERENCE" -r "$REMOTE_NAME" --format=json 2>/dev/null || true) + + # Check if any packages were found in this remote's search result. + # The '.results.items | select(.recipe.id == "$CONAN_PACKAGE_REFERENCE")' + # filters for the exact recipe ID. + # '.packages.id' extracts package IDs if binaries are present. + FOUND_ITEMS=$(echo "$SEARCH_RESULT" | jq -r --arg ref "$CONAN_PACKAGE_REFERENCE" \ + '.results.items | select(.recipe.id == $ref) |.packages.id' 2>/dev/null || true) + + if [ -n "$FOUND_ITEMS" ]; then + # If packages are found, add them to the DISCOVERED_PACKAGES array. + # Format: "- `package/version@user/channel#package_id` (Remote: `remote_name`)" + while IFS= read -r PACKAGE_ID; do + DISCOVERED_PACKAGES+=("- \`${CONAN_PACKAGE_REFERENCE}#${PACKAGE_ID}\` (Remote: \`${REMOTE_NAME}\`)") + done <<< "$FOUND_ITEMS" + else + echo "No packages found in $REMOTE_NAME matching $CONAN_PACKAGE_REFERENCE" + fi + done + + # Prepare the summary content for the GitHub Actions run summary. + # This content will be written to the GITHUB_STEP_SUMMARY file. + echo "### Conan Packages Found for Jira Ticket: ${JIRA_TICKET}" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing + echo "The workflow searched for Conan packages tagged \`${CONAN_PACKAGE_REFERENCE}\` across all configured remotes." >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing + echo "**Discovered Packages:**" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing + + if [ ${#DISCOVERED_PACKAGES[@]} -eq 0 ]; then + echo "*No packages found matching the specified tag.*" >> "$GITHUB_STEP_SUMMARY" + else + # Iterate through the array of discovered packages and append each to the summary file. + for PACKAGE_ENTRY in "${DISCOVERED_PACKAGES[@]}"; do + echo "$PACKAGE_ENTRY" >> "$GITHUB_STEP_SUMMARY" + done + fi + echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing + echo "---" >> "$GITHUB_STEP_SUMMARY" # Add a separator for cleanliness \ No newline at end of file From 4948adf03e199b46351073c893f3e1297e4821e7 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Fri, 20 Jun 2025 21:02:32 +0200 Subject: [PATCH 28/55] Update find-packages.yml --- .github/workflows/find-packages.yml | 113 ++-------------------------- 1 file changed, 6 insertions(+), 107 deletions(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index 693dff9ae8..e9c60a89b4 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -9,110 +9,9 @@ on: type: string jobs: - discover_conan_packages: - runs-on: ubuntu-latest - steps: - - name: Checkout repository code - uses: actions/checkout@v4 - - - name: Validate Jira Ticket Number Format - id: validate_input - run: | - set -eou pipefail - JIRA_TICKET="${{ github.event.inputs.jira_ticket_number }}" - # Regex to validate the format: "cura_" followed by one or more digits. - # The '^' and '$' anchors ensure the entire string matches the pattern. - if [[ ! "$JIRA_TICKET" =~ ^cura_[0-9]+$ ]]; then - # Output an error message that will appear as an annotation in the GitHub Actions UI. - # This provides immediate and clear feedback to the user about the expected format. - echo "::error::Invalid Jira ticket number format. Expected format: cura_# (e.g., cura_12345)." - # Exit with a non-zero status code to fail the workflow immediately. - exit 1 - fi - echo "Jira ticket number '$JIRA_TICKET' is valid." - - - name: Setup Conan Client - uses: conan-io/setup-conan@v1 - - - name: Install jq for JSON parsing - run: sudo apt-get update && sudo apt-get install -y jq - - - name: Discover Conan Packages - id: conan_search - env: - CONAN_USERNAME: ${{ secrets.CONAN_USERNAME }} - CONAN_PASSWORD: ${{ secrets.CONAN_PASSWORD }} - run: | - set -eou pipefail - JIRA_TICKET="${{ github.event.inputs.jira_ticket_number }}" - # Construct the full Conan package reference dynamically. - # The format '5.11.0-alpha.0@ultimaker/cura_#' is based on the user's requirement. - CONAN_PACKAGE_REFERENCE="5.11.0-alpha.0@ultimaker/${JIRA_TICKET}" - echo "Searching for Conan packages matching tag: $CONAN_PACKAGE_REFERENCE" - - # Initialize an empty array to store discovered packages. - # This array will hold Markdown-formatted strings for the summary. - DISCOVERED_PACKAGES=() - - # Get a list of all configured Conan remotes in JSON format. - # Conan 2.x's 'conan remote list --format=json' provides structured output. - # This dynamic approach ensures the workflow adapts to any remote configuration changes. - REMOTES_JSON=$(conan remote list --format=json) - - # Parse the JSON to extract remote names. - # The 'jq -r..name' command extracts the 'name' field from each object in the JSON array. - REMOTE_NAMES=$(echo "$REMOTES_JSON" | jq -r '..name') - - # Iterate through each remote to perform a targeted search for binaries. - # For Conan 2.x, 'conan list' is used for detailed package information. - # To find specific binaries, iteration over individual remotes is necessary. - for REMOTE_NAME in $REMOTE_NAMES; do - echo "Searching remote: $REMOTE_NAME" - # Authenticate with the remote if credentials are provided as secrets. - # This is a security best practice for private remotes. - if [ -n "${CONAN_USERNAME:-}" ] && [ -n "${CONAN_PASSWORD:-}" ]; then - echo "Attempting to log in to remote '$REMOTE_NAME'..." - conan remote login "$REMOTE_NAME" -u "$CONAN_USERNAME" -p "$CONAN_PASSWORD" || echo "Login failed for remote $REMOTE_NAME, continuing without authentication for this remote." - fi - # Execute 'conan list' for the specific package reference on the current remote. - # The '--format=json' flag ensures machine-readable output. - # Redirect stderr to /dev/null to suppress non-critical error messages (e.g., remote not found). - SEARCH_RESULT=$(conan list "$CONAN_PACKAGE_REFERENCE" -r "$REMOTE_NAME" --format=json 2>/dev/null || true) - - # Check if any packages were found in this remote's search result. - # The '.results.items | select(.recipe.id == "$CONAN_PACKAGE_REFERENCE")' - # filters for the exact recipe ID. - # '.packages.id' extracts package IDs if binaries are present. - FOUND_ITEMS=$(echo "$SEARCH_RESULT" | jq -r --arg ref "$CONAN_PACKAGE_REFERENCE" \ - '.results.items | select(.recipe.id == $ref) |.packages.id' 2>/dev/null || true) - - if [ -n "$FOUND_ITEMS" ]; then - # If packages are found, add them to the DISCOVERED_PACKAGES array. - # Format: "- `package/version@user/channel#package_id` (Remote: `remote_name`)" - while IFS= read -r PACKAGE_ID; do - DISCOVERED_PACKAGES+=("- \`${CONAN_PACKAGE_REFERENCE}#${PACKAGE_ID}\` (Remote: \`${REMOTE_NAME}\`)") - done <<< "$FOUND_ITEMS" - else - echo "No packages found in $REMOTE_NAME matching $CONAN_PACKAGE_REFERENCE" - fi - done - - # Prepare the summary content for the GitHub Actions run summary. - # This content will be written to the GITHUB_STEP_SUMMARY file. - echo "### Conan Packages Found for Jira Ticket: ${JIRA_TICKET}" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing - echo "The workflow searched for Conan packages tagged \`${CONAN_PACKAGE_REFERENCE}\` across all configured remotes." >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing - echo "**Discovered Packages:**" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing - - if [ ${#DISCOVERED_PACKAGES[@]} -eq 0 ]; then - echo "*No packages found matching the specified tag.*" >> "$GITHUB_STEP_SUMMARY" - else - # Iterate through the array of discovered packages and append each to the summary file. - for PACKAGE_ENTRY in "${DISCOVERED_PACKAGES[@]}"; do - echo "$PACKAGE_ENTRY" >> "$GITHUB_STEP_SUMMARY" - done - fi - echo "" >> "$GITHUB_STEP_SUMMARY" # Add a blank line for spacing - echo "---" >> "$GITHUB_STEP_SUMMARY" # Add a separator for cleanliness \ No newline at end of file + find-packages: + name: Find packages for Jira ticket + uses: ultimaker/cura-workflows/.github/workflows/find_package_by_ticket.yml@jira_find_package + with: + jira_ticket_number: ${{ inputs.jira_ticket_number }} + secrets: inherit \ No newline at end of file From 2a45cf3274cd533b3a1cb406c9d54b56d0e6e6fc Mon Sep 17 00:00:00 2001 From: HellAholic Date: Sat, 21 Jun 2025 10:03:49 +0200 Subject: [PATCH 29/55] set_permission set high level permission to none --- .github/workflows/find-packages.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index e9c60a89b4..fdb3d8ebf4 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -8,10 +8,12 @@ on: required: true type: string +permissions: {} + jobs: find-packages: name: Find packages for Jira ticket uses: ultimaker/cura-workflows/.github/workflows/find_package_by_ticket.yml@jira_find_package with: jira_ticket_number: ${{ inputs.jira_ticket_number }} - secrets: inherit \ No newline at end of file + secrets: inherit From 1ca58824acfb1bf06fa170120520eb2d32345cbd Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 4 Aug 2024 16:28:23 +0200 Subject: [PATCH 30/55] Add printer definitions for Sovol SV08. The SV08 (or SV-08; nomenclature is not completely consistent) is a relatively new printed based on Voron 2.4, running Klipper. This adds printer, extruder and material definitions for it, based on the voron2_base definitions (by copying, so as to stay independent of voron2_base is changed) plus Sovol's published profiles for Orca Slicer: https://drive.google.com/drive/folders/1KWjLxwpO_9_Xqi_f6qu84HRxZi26a_GN Unfortunately, the included STL model for the platform does not have texture coordinates, so we cannot use the platform texture (unless someone goes to add them manually or otherwise adjusts the model). The following settings were not carried over, mostly because I could not find any obvious equivalent in Cura: - Machine: "retract_before_wipe": [ "0%" ], "machine_max_acceleration_extruding": [ "20000" ], "machine_max_acceleration_retracting": [ "5000" ], "retract_length_toolchange": [ "2" ], "wipe_distance": [ "2" ], "retract_lift_below": [ "343" ], "thumbnails_format": "PNG", "before_layer_change_gcode": "TIMELAPSE_TAKE_FRAME\nG92 E0", - Filament (using ABS as an example; the exact values differ between the four material profiles): "nozzle_temperature_range_low": [ "190" ], "nozzle_temperature_range_high": [ "250" ], "overhang_fan_threshold": [ "25%" ], "temperature_vitrification": [ "60" ], # Only used for arranging. "close_fan_the_first_x_layers": [ "3" ], "full_fan_speed_layer": [ "0" ], # Inconsistent; effectively 4. # Enclosure fan (M106 P3 commands) "activate_air_filtration": [ "1" ], "complete_print_exhaust_fan_speed": [ "60" ], "during_print_exhaust_fan_speed": [ "100" ], - Process: A bunch (e.g. bridge_flow, elephant_foot_compensation, overhang_1_4_speed, etc. etc.), but it's unclear how many are printer-specific and how many are just Orca defaults where Cura wants to do things differently. The start and end G-code are mostly copied over verbatim, except that it leaves the printer in relative coordinate mode and Cura does not set this explicitly back to absolute, so we need an explicit G90 at the end. (Also, there seems to be a Klipper issue where G90 does not reset extrusion to absolute as well, so we need to send an explicit M82.) We give EXTRUDER_TEMP= and BED_TEMP= as parameters to the START_PRINT macro; the Sovol stock macros ignore these, but the popular mainline Klipper installation can use this to e.g. bed mesh at the correct temperature. We also use the new Cura 5.8 conditionals to reduce the extrusion amount for finer nozzles than 0.4mm, as we get Klipper errors otherwise. Unfortunately, Cura chooses SS_ as prefix instead of SV08_. I don't know if there is a way to override this; the other Sovol printers seem to have the same issue. I've tested this with the standard 0.4mm nozzle and ABS/PLA, using the Moonraker plugin. PETG and TPU are untested, in part because the current nozzle is said to be unsafe for PETG. The time estimates from Cura are not all that good, but klipper_estimator helps. (The Klipper object exclusion plugin is also recommended, as it allows the printer to bed mesh a smaller area.) Future work would include supporting the 0.2mm, 0.6mm and 0.8mm nozzles. There are separate profiles for them, with different layer height, support settings, print speeds, etc. -- and then there is a specific PLA/0.2mm profile with lower printing speed and higher fan settings. Also, it would be really good to support the enclosure fan (M106 P3, known as exhaust_fan in Orca) for printing ABS; it's possible that something could be done using the Cura fan control plugin, but it would be better to simply have it right in the filament settings. Similarly, the ABS/PETG profiles want to turn off the fan entirely the first three layers (to improve adhesion), but Cura can only ramp linearly starting from the first layer, not hold the first few layers constant. --- resources/definitions/sovol_sv08.def.json | 131 ++++++++++++++++++ .../extruders/sovol_sv08_extruder.def.json | 19 +++ .../meshes/sovol_sv08_buildplate_model.stl | Bin 0 -> 454884 bytes .../ABS/sovol_sv08_0.4_ABS_standard.inst.cfg | 26 ++++ .../sovol_sv08_0.4_PETG_standard.inst.cfg | 26 ++++ .../PLA/sovol_sv08_0.4_PLA_standard.inst.cfg | 26 ++++ .../TPU/sovol_sv08_0.4_TPU_standard.inst.cfg | 26 ++++ .../quality/sovol/sovol_sv08_global.inst.cfg | 30 ++++ .../variants/sovol/sovol_sv08_0.4.inst.cfg | 13 ++ 9 files changed, 297 insertions(+) create mode 100644 resources/definitions/sovol_sv08.def.json create mode 100644 resources/extruders/sovol_sv08_extruder.def.json create mode 100644 resources/meshes/sovol_sv08_buildplate_model.stl create mode 100644 resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg create mode 100644 resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg create mode 100644 resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg create mode 100644 resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg create mode 100644 resources/quality/sovol/sovol_sv08_global.inst.cfg create mode 100644 resources/variants/sovol/sovol_sv08_0.4.inst.cfg diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json new file mode 100644 index 0000000000..52f5fb6138 --- /dev/null +++ b/resources/definitions/sovol_sv08.def.json @@ -0,0 +1,131 @@ +{ + "version": 2, + "name": "Sovol SV08", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "See voron2_base", + "manufacturer": "Sovol 3D", + "preferred_variant_name": "0.4mm Nozzle", + "quality_definition": "sovol_sv08", + "variants_name": "Nozzle Size", + "platform": "sovol_sv08_buildplate_model.stl", + "file_formats": "text/x-gcode", + "exclude_materials": [], + "first_start_actions": [ "MachineSettingsAction" ], + "has_machine_quality": true, + "has_materials": true, + "has_variants": true, + "machine_extruder_trains": { "0": "voron2_extruder_0" }, + "preferred_material": "generic_abs", + "preferred_quality_type": "fast" + }, + "overrides": + { + "machine_depth": { "default_value": 350 }, + "machine_width": { "default_value": 350 }, + "machine_height": { "default_value": 345 }, + "machine_name": { "default_value": "SV08" }, + "retraction_amount": { "default_value": 0.5 }, + "machine_max_acceleration_x": { "default_value": 40000 }, + "machine_max_acceleration_y": { "default_value": 40000 }, + "machine_max_acceleration_z": { "default_value": 500 }, + "machine_max_acceleration_e": { "default_value": 5000 }, + "machine_max_feedrate_x": { "default_value": 700 }, + "machine_max_feedrate_y": { "default_value": 700 }, + "machine_max_feedrate_z": { "default_value": 20 }, + "machine_max_feedrate_e": { "default_value": 50 }, + "machine_max_jerk_e": { "default_value": 5 }, + "machine_max_jerk_xy": { "default_value": 20 }, + "machine_max_jerk_z": { "default_value": 0.5 }, + "retraction_min_travel": { "default_value": 1 }, + "retraction_hop": { "default_value": 0.4 }, + "machine_start_gcode": { "default_value": "G28 ; Move to zero\nG90 ; Absolute positioning\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nSTART_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}\nG90 ; Absolute positioning (START_PRINT might have changed it)\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nM400\nG91 ; Relative positioning\nM83 ; Relative extrusion\nM140 S{material_bed_temperature_layer_0} ; Set bed temp\nM104 S{material_print_temperature_layer_0} ; Set extruder temp\nM190 S{material_bed_temperature_layer_0} ; Wait for bed temp\nM109 S{material_print_temperature_layer_0} ; Wait for extruder temp\n{if machine_nozzle_size >= 0.4}\n; Standard Sovol blob and purge line.\nG1 E25 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.200 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 Y1 E0.16 F1800 ; Small movement back for next line\nG1 X-87.000 E13.92 F1800 ; Purge line left\nG1 X-87.000 E20.88 F1800\nG1 Y1 E0.24 F1800 ; Small movement back for next line\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 E-0.200 Z1 F600\n{else}\n; The default start G-code uses too high flow for smaller nozzles,\n; which causes Klipper errors. Scale everything back by\n; (0.25/0.4)^2, i.e., for 0.25mm nozzle. This should be good\n; enough for 0.2mm as well.\nG1 E8 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.078 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 Y1 E0.063 F1800 ; Small movement back for next line\nG1 X-87.000 E5.44 F1800 ; Purge line left\nG1 X-87.000 E8.16 F1800\nG1 Y1 E0.094 F1800 ; Small movement back for next line\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 E-0.078 Z1 F600\n{endif}\nM400 ; Wait for moves to finish\nG90 ; Absolute positioning\nM82 ; Absolute extrusion mode\n" }, + "machine_end_gcode": { "default_value": "END_PRINT\n" }, + "acceleration_enabled": { "default_value": false }, + "acceleration_layer_0": { "value": 1800 }, + "acceleration_print": { "default_value": 2200 }, + "acceleration_roofing": { "value": 1800 }, + "acceleration_travel_layer_0": { "value": 1800 }, + "acceleration_wall_0": { "value": 1800 }, + "adhesion_type": { "default_value": "skirt" }, + "alternate_extra_perimeter": { "default_value": true }, + "bridge_fan_speed": { "default_value": 100 }, + "bridge_fan_speed_2": { "resolve": "max(cool_fan_speed, 50)" }, + "bridge_fan_speed_3": { "resolve": "max(cool_fan_speed, 20)" }, + "bridge_settings_enabled": { "default_value": true }, + "bridge_wall_coast": { "default_value": 10 }, + "cool_fan_full_at_height": { "value": "resolveOrValue('layer_height_0') + resolveOrValue('layer_height') * max(1, cool_fan_full_layer - 1)" }, + "cool_fan_full_layer": { "value": 4 }, + "cool_fan_speed_min": { "value": "cool_fan_speed" }, + "cool_min_layer_time": { "default_value": 15 }, + "cool_min_layer_time_fan_speed_max": { "default_value": 20 }, + "fill_outline_gaps": { "default_value": true }, + "gantry_height": { "value": 30 }, + "infill_before_walls": { "default_value": false }, + "infill_enable_travel_optimization": { "default_value": true }, + "jerk_enabled": { "default_value": false }, + "jerk_roofing": { "value": 10 }, + "jerk_wall_0": { "value": 10 }, + "layer_height_0": { "resolve": "max(0.2, min(extruderValues('layer_height')))" }, + "line_width": { "value": "machine_nozzle_size * 1.125" }, + "machine_acceleration": { "default_value": 1500 }, + "machine_endstop_positive_direction_x": { "default_value": true }, + "machine_endstop_positive_direction_y": { "default_value": true }, + "machine_endstop_positive_direction_z": { "default_value": false }, + "machine_feeder_wheel_diameter": { "default_value": 7.5 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_head_with_fans_polygon": + { + "default_value": [ + [-35, 65], + [-35, -50], + [35, -50], + [35, 65] + ] + }, + "machine_heated_bed": { "default_value": true }, + "machine_steps_per_mm_x": { "default_value": 80 }, + "machine_steps_per_mm_y": { "default_value": 80 }, + "machine_steps_per_mm_z": { "default_value": 400 }, + "meshfix_maximum_resolution": { "default_value": 0.01 }, + "min_infill_area": { "default_value": 5.0 }, + "minimum_polygon_circumference": { "default_value": 0.2 }, + "optimize_wall_printing_order": { "default_value": true }, + "retraction_combing": { "value": "'noskin'" }, + "retraction_combing_max_distance": { "default_value": 10 }, + "retraction_hop_enabled": { "default_value": true }, + "retraction_prime_speed": + { + "maximum_value_warning": 130, + "value": "math.ceil(retraction_speed * 0.4)" + }, + "retraction_retract_speed": { "maximum_value_warning": 130 }, + "retraction_speed": + { + "default_value": 30, + "maximum_value_warning": 130 + }, + "roofing_layer_count": { "value": 1 }, + "skirt_brim_minimal_length": { "default_value": 550 }, + "speed_layer_0": { "value": "math.ceil(speed_print * 0.25)" }, + "speed_roofing": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_slowdown_layers": { "default_value": 4 }, + "speed_topbottom": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_travel": + { + "maximum_value_warning": 501, + "value": 300 + }, + "speed_travel_layer_0": { "value": "math.ceil(speed_travel * 0.4)" }, + "speed_wall": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_wall_0": { "value": "math.ceil(speed_print * 0.33)" }, + "speed_wall_x": { "value": "math.ceil(speed_print * 0.66)" }, + "travel_avoid_other_parts": { "default_value": false }, + "wall_line_width": { "value": "machine_nozzle_size" }, + "wall_overhang_angle": { "default_value": 75 }, + "wall_overhang_speed_factor": { "default_value": 50 }, + "zig_zaggify_infill": { "value": true } + } +} diff --git a/resources/extruders/sovol_sv08_extruder.def.json b/resources/extruders/sovol_sv08_extruder.def.json new file mode 100644 index 0000000000..407c81de47 --- /dev/null +++ b/resources/extruders/sovol_sv08_extruder.def.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "Nozzle Size", + "inherits": "fdmextruder", + "metadata": + { + "machine": "sovol_sv08", + "position": "0" + }, + "overrides": + { + "extruder_nr": + { + "default_value": 0, + "maximum_value": 1 + }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/meshes/sovol_sv08_buildplate_model.stl b/resources/meshes/sovol_sv08_buildplate_model.stl new file mode 100644 index 0000000000000000000000000000000000000000..91acb50bd443f13d12c84d80a912e5d5b21ff1d8 GIT binary patch literal 454884 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

8}u(~qva{JL7O*{ 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 z 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

gqjqWHOjVDU%F#pu@`EC^N&2jaCG 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 literal 0 HcmV?d00001 diff --git a/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg b/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg new file mode 100644 index 0000000000..2fda8d1811 --- /dev/null +++ b/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg @@ -0,0 +1,26 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_abs +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +cool_fan_enabled = True +cool_fan_speed = 10 +cool_fan_speed_max = 30 +cool_min_layer_time_fan_speed_max = 30 +cool_min_layer_time = 4 +cool_min_speed = 10 +material_flow = 98 +material_max_flowrate = 21 +material_print_temperature = 270 +material_print_temperature_layer_0 = 280 +material_bed_temperature = 95 +bridge_settings_enabled = True +bridge_fan_speed = 30 diff --git a/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg b/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg new file mode 100644 index 0000000000..ba43c72c8b --- /dev/null +++ b/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg @@ -0,0 +1,26 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_petg +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +cool_fan_enabled = True +cool_fan_speed = 10 +cool_fan_speed_max = 30 +cool_min_layer_time_fan_speed_max = 30 +cool_min_layer_time = 5 +cool_min_speed = 10 +material_flow = 98 +material_max_flowrate = 17 +material_print_temperature = 235 +material_print_temperature_layer_0 = 250 +material_bed_temperature = 75 +bridge_settings_enabled = True +bridge_fan_speed = 70 diff --git a/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg b/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg new file mode 100644 index 0000000000..2a70b9415b --- /dev/null +++ b/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg @@ -0,0 +1,26 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_pla +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +cool_fan_enabled = True +cool_fan_speed = 50 +cool_fan_speed_max = 70 +cool_min_layer_time_fan_speed_max = 50 +cool_min_layer_time = 5 +cool_min_speed = 10 +material_flow = 98 +material_max_flowrate = 21 +material_print_temperature = 220 +material_print_temperature_layer_0 = 235 +material_bed_temperature = 65 +bridge_settings_enabled = True +bridge_fan_speed = 100 diff --git a/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg b/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg new file mode 100644 index 0000000000..1908bdb6b0 --- /dev/null +++ b/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg @@ -0,0 +1,26 @@ +[general] +definition = sovol_sv08 +name = Standard Quality +version = 4 + +[metadata] +material = generic_tpu +quality_type = standard +setting_version = 25 +type = quality +variant = 0.4mm Nozzle + +[values] +cool_fan_enabled = True +cool_fan_speed = 80 +cool_fan_speed_max = 100 +cool_min_layer_time_fan_speed_max = 50 +cool_min_layer_time = 5 +cool_min_speed = 10 +material_flow = 98 +material_max_flowrate = 3.6 +material_print_temperature = 240 +material_print_temperature_layer_0 = 235 +material_bed_temperature = 65 +bridge_settings_enabled = True +bridge_fan_speed = 100 diff --git a/resources/quality/sovol/sovol_sv08_global.inst.cfg b/resources/quality/sovol/sovol_sv08_global.inst.cfg new file mode 100644 index 0000000000..2f2f0004ab --- /dev/null +++ b/resources/quality/sovol/sovol_sv08_global.inst.cfg @@ -0,0 +1,30 @@ +[general] +definition = sovol_sv08 +name = 0.20mm Standard +version = 4 + +[metadata] +global_quality = True +quality_type = standard +setting_version = 23 +type = quality + +[values] +layer_height = 0.2 +speed_print = 600 +speed_wall_x = 300 +speed_wall_0 = 200 +speed_infill = 200 +speed_travel = =speed_print +skirt_brim_speed = 80 +speed_ironing = 15 +speed_layer_0 = 30 +speed_slowdown_layers = 3 +acceleration_enabled = True +acceleration_print = 20000 +acceleration_wall_0 = 8000 +acceleration_wall_x = 12000 +acceleration_roofing = =acceleration_wall_0 +acceleration_topbottom = =acceleration_wall +acceleration_layer_0 = 3000 +acceleration_travel = 40000 diff --git a/resources/variants/sovol/sovol_sv08_0.4.inst.cfg b/resources/variants/sovol/sovol_sv08_0.4.inst.cfg new file mode 100644 index 0000000000..ce8ae1f23e --- /dev/null +++ b/resources/variants/sovol/sovol_sv08_0.4.inst.cfg @@ -0,0 +1,13 @@ +[general] +definition = sovol_sv08 +name = 0.4mm Nozzle +version = 4 + +[metadata] +hardware_type = nozzle +setting_version = 23 +type = variant + +[values] +machine_nozzle_size = 0.4 + From 694ef2a8c9486f37c86a66c95836d9f86b229c63 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 24 Jun 2025 12:16:05 +0200 Subject: [PATCH 31/55] Update find-packages workflow Add input to start builds Update the reference to the workflow name --- .github/workflows/find-packages.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index fdb3d8ebf4..43c4b6f17e 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -7,13 +7,18 @@ on: description: 'Jira ticket number for Conan package discovery (e.g., cura_12345)' required: true type: string + start_builds: + default: false + required: false + type: boolean permissions: {} jobs: find-packages: name: Find packages for Jira ticket - uses: ultimaker/cura-workflows/.github/workflows/find_package_by_ticket.yml@jira_find_package + uses: ultimaker/cura-workflows/.github/workflows/find-package-by-ticket.yml@jira_find_package with: jira_ticket_number: ${{ inputs.jira_ticket_number }} + start_builds: ${{ inputs.start_builds }} secrets: inherit From 94faa4cacfb64b6af8eb56d4a0060009cbad78b1 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 24 Jun 2025 12:38:09 +0200 Subject: [PATCH 32/55] Add read permission to allow for triggering builds --- .github/workflows/find-packages.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index 43c4b6f17e..ad8fb6271d 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -12,7 +12,8 @@ on: required: false type: boolean -permissions: {} +permissions: + contents: read jobs: find-packages: From 6a230b3df5cc57c49550d4b6f262104d44293e54 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 24 Jun 2025 14:03:07 +0200 Subject: [PATCH 33/55] ClusterPrinterStatus: optional init args Prevents a cura crash if any of the args are not returned from api call --- .../src/Models/Http/ClusterPrinterStatus.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py index 925b4844c1..c7250d7c37 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py @@ -20,13 +20,14 @@ from ..BaseModel import BaseModel class ClusterPrinterStatus(BaseModel): """Class representing a cluster printer""" - def __init__(self, enabled: bool, firmware_version: str, friendly_name: str, ip_address: str, machine_variant: str, - status: str, unique_name: str, uuid: str, - configuration: List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]], - reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = None, + def __init__(self, enabled: Optional[bool] = True, friendly_name: Optional[str] = "", machine_variant: Optional[str] = "", + status: Optional[str] = "unknown", unique_name: Optional[str] = "", uuid: Optional[str] = "", + configuration: Optional[List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]]] = None, + firmware_version: Optional[str] = None, ip_address: Optional[str] = None, + reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = False, firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None, - build_plate: Union[Dict[str, Any], ClusterBuildPlate] = None, - material_station: Union[Dict[str, Any], ClusterPrinterMaterialStation] = None, **kwargs) -> None: + build_plate: Optional[Union[Dict[str, Any], ClusterBuildPlate]] = None, + material_station: Optional[Union[Dict[str, Any], ClusterPrinterMaterialStation]] = None, **kwargs) -> None: """ Creates a new cluster printer status :param enabled: A printer can be disabled if it should not receive new jobs. By default, every printer is enabled. @@ -47,7 +48,7 @@ class ClusterPrinterStatus(BaseModel): :param material_station: The material station that is on the printer. """ - self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration) + self.configuration = self.parseModels(ClusterPrintCoreConfiguration, configuration) if configuration else [] self.enabled = enabled self.firmware_version = firmware_version self.friendly_name = friendly_name @@ -70,7 +71,7 @@ class ClusterPrinterStatus(BaseModel): :param controller: - The controller of the model. """ - model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version) + model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version or "") self.updateOutputModel(model) return model @@ -80,13 +81,14 @@ class ClusterPrinterStatus(BaseModel): :param model: - The output model to update. """ - model.updateKey(self.uuid) - model.updateName(self.friendly_name) - model.updateUniqueName(self.unique_name) - model.updateType(self.machine_variant) + model.updateKey(self.uuid or "") + model.updateName(self.friendly_name or "") + model.updateUniqueName(self.unique_name or "") + model.updateType(self.machine_variant or "") model.updateState(self.status if self.enabled else "disabled") model.updateBuildplate(self.build_plate.type if self.build_plate else "glass") - model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address))) + if self.ip_address: + model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address))) if not model.printerConfiguration: # Prevent accessing printer configuration when not available. From 161f2707f6ce828396ad0f41c13fed2e46175dcb Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 24 Jun 2025 17:48:30 +0200 Subject: [PATCH 34/55] Update sovol_sv08.def.json - author name - change extruder name (no longer using voron2) --- resources/definitions/sovol_sv08.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index 52f5fb6138..e60e6edf91 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -5,7 +5,7 @@ "metadata": { "visible": true, - "author": "See voron2_base", + "author": "Steinar H. Gunderson", "manufacturer": "Sovol 3D", "preferred_variant_name": "0.4mm Nozzle", "quality_definition": "sovol_sv08", @@ -17,7 +17,7 @@ "has_machine_quality": true, "has_materials": true, "has_variants": true, - "machine_extruder_trains": { "0": "voron2_extruder_0" }, + "machine_extruder_trains": { "0": "sovol_sv08_extruder" }, "preferred_material": "generic_abs", "preferred_quality_type": "fast" }, From 2cfb063c9b3c03378ad47fd0f6f088605807ad10 Mon Sep 17 00:00:00 2001 From: HellAholic <28710690+HellAholic@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:49:28 +0000 Subject: [PATCH 35/55] Apply printer-linter format --- resources/definitions/sovol_sv08.def.json | 52 +++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index e60e6edf91..a0cda31889 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -7,11 +7,8 @@ "visible": true, "author": "Steinar H. Gunderson", "manufacturer": "Sovol 3D", - "preferred_variant_name": "0.4mm Nozzle", - "quality_definition": "sovol_sv08", - "variants_name": "Nozzle Size", - "platform": "sovol_sv08_buildplate_model.stl", "file_formats": "text/x-gcode", + "platform": "sovol_sv08_buildplate_model.stl", "exclude_materials": [], "first_start_actions": [ "MachineSettingsAction" ], "has_machine_quality": true, @@ -19,30 +16,13 @@ "has_variants": true, "machine_extruder_trains": { "0": "sovol_sv08_extruder" }, "preferred_material": "generic_abs", - "preferred_quality_type": "fast" + "preferred_quality_type": "fast", + "preferred_variant_name": "0.4mm Nozzle", + "quality_definition": "sovol_sv08", + "variants_name": "Nozzle Size" }, "overrides": { - "machine_depth": { "default_value": 350 }, - "machine_width": { "default_value": 350 }, - "machine_height": { "default_value": 345 }, - "machine_name": { "default_value": "SV08" }, - "retraction_amount": { "default_value": 0.5 }, - "machine_max_acceleration_x": { "default_value": 40000 }, - "machine_max_acceleration_y": { "default_value": 40000 }, - "machine_max_acceleration_z": { "default_value": 500 }, - "machine_max_acceleration_e": { "default_value": 5000 }, - "machine_max_feedrate_x": { "default_value": 700 }, - "machine_max_feedrate_y": { "default_value": 700 }, - "machine_max_feedrate_z": { "default_value": 20 }, - "machine_max_feedrate_e": { "default_value": 50 }, - "machine_max_jerk_e": { "default_value": 5 }, - "machine_max_jerk_xy": { "default_value": 20 }, - "machine_max_jerk_z": { "default_value": 0.5 }, - "retraction_min_travel": { "default_value": 1 }, - "retraction_hop": { "default_value": 0.4 }, - "machine_start_gcode": { "default_value": "G28 ; Move to zero\nG90 ; Absolute positioning\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nSTART_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}\nG90 ; Absolute positioning (START_PRINT might have changed it)\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nM400\nG91 ; Relative positioning\nM83 ; Relative extrusion\nM140 S{material_bed_temperature_layer_0} ; Set bed temp\nM104 S{material_print_temperature_layer_0} ; Set extruder temp\nM190 S{material_bed_temperature_layer_0} ; Wait for bed temp\nM109 S{material_print_temperature_layer_0} ; Wait for extruder temp\n{if machine_nozzle_size >= 0.4}\n; Standard Sovol blob and purge line.\nG1 E25 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.200 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 Y1 E0.16 F1800 ; Small movement back for next line\nG1 X-87.000 E13.92 F1800 ; Purge line left\nG1 X-87.000 E20.88 F1800\nG1 Y1 E0.24 F1800 ; Small movement back for next line\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 E-0.200 Z1 F600\n{else}\n; The default start G-code uses too high flow for smaller nozzles,\n; which causes Klipper errors. Scale everything back by\n; (0.25/0.4)^2, i.e., for 0.25mm nozzle. This should be good\n; enough for 0.2mm as well.\nG1 E8 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.078 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 Y1 E0.063 F1800 ; Small movement back for next line\nG1 X-87.000 E5.44 F1800 ; Purge line left\nG1 X-87.000 E8.16 F1800\nG1 Y1 E0.094 F1800 ; Small movement back for next line\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 E-0.078 Z1 F600\n{endif}\nM400 ; Wait for moves to finish\nG90 ; Absolute positioning\nM82 ; Absolute extrusion mode\n" }, - "machine_end_gcode": { "default_value": "END_PRINT\n" }, "acceleration_enabled": { "default_value": false }, "acceleration_layer_0": { "value": 1800 }, "acceleration_print": { "default_value": 2200 }, @@ -71,6 +51,8 @@ "layer_height_0": { "resolve": "max(0.2, min(extruderValues('layer_height')))" }, "line_width": { "value": "machine_nozzle_size * 1.125" }, "machine_acceleration": { "default_value": 1500 }, + "machine_depth": { "default_value": 350 }, + "machine_end_gcode": { "default_value": "END_PRINT\n" }, "machine_endstop_positive_direction_x": { "default_value": true }, "machine_endstop_positive_direction_y": { "default_value": true }, "machine_endstop_positive_direction_z": { "default_value": false }, @@ -86,16 +68,34 @@ ] }, "machine_heated_bed": { "default_value": true }, + "machine_height": { "default_value": 345 }, + "machine_max_acceleration_e": { "default_value": 5000 }, + "machine_max_acceleration_x": { "default_value": 40000 }, + "machine_max_acceleration_y": { "default_value": 40000 }, + "machine_max_acceleration_z": { "default_value": 500 }, + "machine_max_feedrate_e": { "default_value": 50 }, + "machine_max_feedrate_x": { "default_value": 700 }, + "machine_max_feedrate_y": { "default_value": 700 }, + "machine_max_feedrate_z": { "default_value": 20 }, + "machine_max_jerk_e": { "default_value": 5 }, + "machine_max_jerk_xy": { "default_value": 20 }, + "machine_max_jerk_z": { "default_value": 0.5 }, + "machine_name": { "default_value": "SV08" }, + "machine_start_gcode": { "default_value": "G28 ; Move to zero\nG90 ; Absolute positioning\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nSTART_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0}\nG90 ; Absolute positioning (START_PRINT might have changed it)\nG1 X0 F9000\nG1 Y20\nG1 Z0.600 F600\nG1 Y0 F9000\nM400\nG91 ; Relative positioning\nM83 ; Relative extrusion\nM140 S{material_bed_temperature_layer_0} ; Set bed temp\nM104 S{material_print_temperature_layer_0} ; Set extruder temp\nM190 S{material_bed_temperature_layer_0} ; Wait for bed temp\nM109 S{material_print_temperature_layer_0} ; Wait for extruder temp\n{if machine_nozzle_size >= 0.4}\n; Standard Sovol blob and purge line.\nG1 E25 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.200 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 Y1 E0.16 F1800 ; Small movement back for next line\nG1 X-87.000 E13.92 F1800 ; Purge line left\nG1 X-87.000 E20.88 F1800\nG1 Y1 E0.24 F1800 ; Small movement back for next line\nG1 X87.000 E20.88 F1800 ; Purge line right\nG1 X87.000 E13.92 F1800\nG1 E-0.200 Z1 F600\n{else}\n; The default start G-code uses too high flow for smaller nozzles,\n; which causes Klipper errors. Scale everything back by\n; (0.25/0.4)^2, i.e., for 0.25mm nozzle. This should be good\n; enough for 0.2mm as well.\nG1 E8 F300 ; Purge blob\nG4 P1000 ; Wait a bit to let it finish\nG1 E-0.078 Z5 F600 ; Retract\nG1 X88.000 F9000 ; Move right and then down for purge line\nG1 Z-5.000 F600\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 Y1 E0.063 F1800 ; Small movement back for next line\nG1 X-87.000 E5.44 F1800 ; Purge line left\nG1 X-87.000 E8.16 F1800\nG1 Y1 E0.094 F1800 ; Small movement back for next line\nG1 X87.000 E8.16 F1800 ; Purge line right\nG1 X87.000 E5.44 F1800\nG1 E-0.078 Z1 F600\n{endif}\nM400 ; Wait for moves to finish\nG90 ; Absolute positioning\nM82 ; Absolute extrusion mode\n" }, "machine_steps_per_mm_x": { "default_value": 80 }, "machine_steps_per_mm_y": { "default_value": 80 }, "machine_steps_per_mm_z": { "default_value": 400 }, + "machine_width": { "default_value": 350 }, "meshfix_maximum_resolution": { "default_value": 0.01 }, "min_infill_area": { "default_value": 5.0 }, "minimum_polygon_circumference": { "default_value": 0.2 }, "optimize_wall_printing_order": { "default_value": true }, + "retraction_amount": { "default_value": 0.5 }, "retraction_combing": { "value": "'noskin'" }, "retraction_combing_max_distance": { "default_value": 10 }, + "retraction_hop": { "default_value": 0.4 }, "retraction_hop_enabled": { "default_value": true }, + "retraction_min_travel": { "default_value": 1 }, "retraction_prime_speed": { "maximum_value_warning": 130, @@ -128,4 +128,4 @@ "wall_overhang_speed_factor": { "default_value": 50 }, "zig_zaggify_infill": { "value": true } } -} +} \ No newline at end of file From 594ad121ddd0c1218fbbe88ab440e264deb95587 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 24 Jun 2025 17:49:44 +0200 Subject: [PATCH 36/55] bump setting version to 25 --- resources/variants/sovol/sovol_sv08_0.4.inst.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/variants/sovol/sovol_sv08_0.4.inst.cfg b/resources/variants/sovol/sovol_sv08_0.4.inst.cfg index ce8ae1f23e..b5c72e92d3 100644 --- a/resources/variants/sovol/sovol_sv08_0.4.inst.cfg +++ b/resources/variants/sovol/sovol_sv08_0.4.inst.cfg @@ -5,7 +5,7 @@ version = 4 [metadata] hardware_type = nozzle -setting_version = 23 +setting_version = 25 type = variant [values] From 08d14c39b4ad7bd70ec336b65acb2fd65bb15146 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 24 Jun 2025 17:50:07 +0200 Subject: [PATCH 37/55] bump setting version to 25 --- resources/quality/sovol/sovol_sv08_global.inst.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/quality/sovol/sovol_sv08_global.inst.cfg b/resources/quality/sovol/sovol_sv08_global.inst.cfg index 2f2f0004ab..bebbc3acb1 100644 --- a/resources/quality/sovol/sovol_sv08_global.inst.cfg +++ b/resources/quality/sovol/sovol_sv08_global.inst.cfg @@ -6,7 +6,7 @@ version = 4 [metadata] global_quality = True quality_type = standard -setting_version = 23 +setting_version = 25 type = quality [values] From 75a719ec6031e7300848f10c672cacef99626fde Mon Sep 17 00:00:00 2001 From: HellAholic <28710690+HellAholic@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:51:48 +0000 Subject: [PATCH 38/55] Apply printer-linter format --- .../quality/sovol/sovol_sv08_global.inst.cfg | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/resources/quality/sovol/sovol_sv08_global.inst.cfg b/resources/quality/sovol/sovol_sv08_global.inst.cfg index bebbc3acb1..4030ec9441 100644 --- a/resources/quality/sovol/sovol_sv08_global.inst.cfg +++ b/resources/quality/sovol/sovol_sv08_global.inst.cfg @@ -10,21 +10,22 @@ setting_version = 25 type = quality [values] -layer_height = 0.2 -speed_print = 600 -speed_wall_x = 300 -speed_wall_0 = 200 -speed_infill = 200 -speed_travel = =speed_print -skirt_brim_speed = 80 -speed_ironing = 15 -speed_layer_0 = 30 -speed_slowdown_layers = 3 acceleration_enabled = True +acceleration_layer_0 = 3000 acceleration_print = 20000 -acceleration_wall_0 = 8000 -acceleration_wall_x = 12000 acceleration_roofing = =acceleration_wall_0 acceleration_topbottom = =acceleration_wall -acceleration_layer_0 = 3000 acceleration_travel = 40000 +acceleration_wall_0 = 8000 +acceleration_wall_x = 12000 +layer_height = 0.2 +skirt_brim_speed = 80 +speed_infill = 200 +speed_ironing = 15 +speed_layer_0 = 30 +speed_print = 600 +speed_slowdown_layers = 3 +speed_travel = =speed_print +speed_wall_0 = 200 +speed_wall_x = 300 + From 9cf75648ab527579f2e0c1e5a051b56581c2c5e9 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Wed, 25 Jun 2025 13:54:56 +0200 Subject: [PATCH 39/55] Review comment - Set default to `""` for `Optional[str]` to remove the `or ""` -The FW version can be returned as None which requires the fallback `or ""` --- .../src/Models/Http/ClusterPrinterStatus.py | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py index c7250d7c37..260d276427 100644 --- a/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py +++ b/plugins/UM3NetworkPrinting/src/Models/Http/ClusterPrinterStatus.py @@ -20,14 +20,23 @@ from ..BaseModel import BaseModel class ClusterPrinterStatus(BaseModel): """Class representing a cluster printer""" - def __init__(self, enabled: Optional[bool] = True, friendly_name: Optional[str] = "", machine_variant: Optional[str] = "", - status: Optional[str] = "unknown", unique_name: Optional[str] = "", uuid: Optional[str] = "", + def __init__(self, + enabled: Optional[bool] = True, + friendly_name: Optional[str] = "", + machine_variant: Optional[str] = "", + status: Optional[str] = "unknown", + unique_name: Optional[str] = "", + uuid: Optional[str] = "", configuration: Optional[List[Union[Dict[str, Any], ClusterPrintCoreConfiguration]]] = None, - firmware_version: Optional[str] = None, ip_address: Optional[str] = None, - reserved_by: Optional[str] = None, maintenance_required: Optional[bool] = False, - firmware_update_status: Optional[str] = None, latest_available_firmware: Optional[str] = None, + firmware_version: Optional[str] = None, + ip_address: Optional[str] = None, + reserved_by: Optional[str] = "", + maintenance_required: Optional[bool] = False, + firmware_update_status: Optional[str] = "", + latest_available_firmware: Optional[str] = "", build_plate: Optional[Union[Dict[str, Any], ClusterBuildPlate]] = None, - material_station: Optional[Union[Dict[str, Any], ClusterPrinterMaterialStation]] = None, **kwargs) -> None: + material_station: Optional[Union[Dict[str, Any], ClusterPrinterMaterialStation]] = None, + **kwargs) -> None: """ Creates a new cluster printer status :param enabled: A printer can be disabled if it should not receive new jobs. By default, every printer is enabled. @@ -81,10 +90,10 @@ class ClusterPrinterStatus(BaseModel): :param model: - The output model to update. """ - model.updateKey(self.uuid or "") - model.updateName(self.friendly_name or "") - model.updateUniqueName(self.unique_name or "") - model.updateType(self.machine_variant or "") + model.updateKey(self.uuid) + model.updateName(self.friendly_name) + model.updateUniqueName(self.unique_name) + model.updateType(self.machine_variant) model.updateState(self.status if self.enabled else "disabled") model.updateBuildplate(self.build_plate.type if self.build_plate else "glass") if self.ip_address: From 90170c694ccb09ec4e7d62ad5ebbe6ca735805b5 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 09:23:20 +0200 Subject: [PATCH 40/55] Unlink the reusable workflows and add build args --- .github/workflows/find-packages.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index ad8fb6271d..671121904a 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -11,6 +11,18 @@ on: default: false required: false type: boolean + conan_args: + description: 'Conan args' + default: '' + type: string + enterprise: + description: 'Build Cura as an Enterprise edition' + default: false + type: boolean + staging: + description: 'Use staging API' + default: false + type: boolean permissions: contents: read @@ -23,3 +35,17 @@ jobs: jira_ticket_number: ${{ inputs.jira_ticket_number }} start_builds: ${{ inputs.start_builds }} secrets: inherit + + installers: + name: Create installers + needs: find-packages + if: ${{ inputs.start_builds == true && needs.find-packages.outputs.cura_conan_version != '' }} + uses: ultimaker/cura-workflows/.github/workflows/cura-installers.yml@main + with: + cura_conan_version: ${{ needs.find-packages.outputs.cura_conan_version }} + package_overrides: ${{ needs.find-packages.outputs.package_overrides }} + conan_args: ${{ inputs.conan_args }} + enterprise: ${{ inputs.enterprise }} + staging: ${{ inputs.staging }} + secrets: inherit + From 06cf1151e5e7e63285b11de8c3ddedb28c334ddd Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 09:26:46 +0200 Subject: [PATCH 41/55] remove input for starting build from workflow - installers job is moved to this workflow so the trigger to start build is handled locally and should not be sent to reusable workflow --- .github/workflows/find-packages.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index 671121904a..60f87c8688 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -33,7 +33,6 @@ jobs: uses: ultimaker/cura-workflows/.github/workflows/find-package-by-ticket.yml@jira_find_package with: jira_ticket_number: ${{ inputs.jira_ticket_number }} - start_builds: ${{ inputs.start_builds }} secrets: inherit installers: From 9cc61d7db092427cb077aa8371a5e0f00970ba85 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 09:44:01 +0200 Subject: [PATCH 42/55] workflow input/output name correction --- .github/workflows/find-packages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index 60f87c8688..fa2dfe2d39 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -38,10 +38,10 @@ jobs: installers: name: Create installers needs: find-packages - if: ${{ inputs.start_builds == true && needs.find-packages.outputs.cura_conan_version != '' }} + if: ${{ inputs.start_builds == true && needs.find-packages.outputs.discovered_packages != '' }} uses: ultimaker/cura-workflows/.github/workflows/cura-installers.yml@main with: - cura_conan_version: ${{ needs.find-packages.outputs.cura_conan_version }} + cura_conan_version: ${{ needs.find-packages.outputs.cura_package }} package_overrides: ${{ needs.find-packages.outputs.package_overrides }} conan_args: ${{ inputs.conan_args }} enterprise: ${{ inputs.enterprise }} From 300d776f1a1a96e3d278ffd5db0db07ee77ede89 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 09:48:46 +0200 Subject: [PATCH 43/55] adjust to access the workflow_dispatch input inputs.start_builds is only available at the top-level workflow, not inside a job's if condition. --- .github/workflows/find-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index fa2dfe2d39..4e584844d6 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -38,7 +38,7 @@ jobs: installers: name: Create installers needs: find-packages - if: ${{ inputs.start_builds == true && needs.find-packages.outputs.discovered_packages != '' }} + if: ${{ github.event.inputs.start_builds == 'true' && needs.find-packages.outputs.discovered_packages != '' }} uses: ultimaker/cura-workflows/.github/workflows/cura-installers.yml@main with: cura_conan_version: ${{ needs.find-packages.outputs.cura_package }} From a6bb69566665c99e210e2d7045110a3dcc699f69 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 10:04:28 +0200 Subject: [PATCH 44/55] debug - remove the input check --- .github/workflows/find-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index 4e584844d6..283e59286e 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -38,7 +38,7 @@ jobs: installers: name: Create installers needs: find-packages - if: ${{ github.event.inputs.start_builds == 'true' && needs.find-packages.outputs.discovered_packages != '' }} + if: ${{ needs.find-packages.outputs.discovered_packages != '' }} uses: ultimaker/cura-workflows/.github/workflows/cura-installers.yml@main with: cura_conan_version: ${{ needs.find-packages.outputs.cura_package }} From 7637bbe3ca8abeccf4fd7d5842712e743e9dd6db Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 10:07:03 +0200 Subject: [PATCH 45/55] debug output --- .github/workflows/find-packages.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index 283e59286e..d9772dd782 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -35,6 +35,16 @@ jobs: jira_ticket_number: ${{ inputs.jira_ticket_number }} secrets: inherit + debug-outputs: + name: Debug Outputs + needs: find-packages + runs-on: ubuntu-latest + steps: + - run: | + echo "discovered_packages: '${{ needs.find-packages.outputs.discovered_packages }}'" + echo "cura_package: '${{ needs.find-packages.outputs.cura_package }}'" + echo "package_overrides: '${{ needs.find-packages.outputs.package_overrides }}'" + installers: name: Create installers needs: find-packages From f2c754ef7a3c3f41c73279724bf7a09ebea9e1fa Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 10:16:13 +0200 Subject: [PATCH 46/55] Remove debug and set the start_builds condition back --- .github/workflows/find-packages.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index d9772dd782..fa2dfe2d39 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -35,20 +35,10 @@ jobs: jira_ticket_number: ${{ inputs.jira_ticket_number }} secrets: inherit - debug-outputs: - name: Debug Outputs - needs: find-packages - runs-on: ubuntu-latest - steps: - - run: | - echo "discovered_packages: '${{ needs.find-packages.outputs.discovered_packages }}'" - echo "cura_package: '${{ needs.find-packages.outputs.cura_package }}'" - echo "package_overrides: '${{ needs.find-packages.outputs.package_overrides }}'" - installers: name: Create installers needs: find-packages - if: ${{ needs.find-packages.outputs.discovered_packages != '' }} + if: ${{ inputs.start_builds == true && needs.find-packages.outputs.discovered_packages != '' }} uses: ultimaker/cura-workflows/.github/workflows/cura-installers.yml@main with: cura_conan_version: ${{ needs.find-packages.outputs.cura_package }} From f506fe570949559a5da33c49d43c264626b98322 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 10:22:22 +0200 Subject: [PATCH 47/55] Update find-packages.yml --- .github/workflows/find-packages.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/find-packages.yml b/.github/workflows/find-packages.yml index fa2dfe2d39..81f68857e5 100644 --- a/.github/workflows/find-packages.yml +++ b/.github/workflows/find-packages.yml @@ -1,4 +1,4 @@ -name: Conan Package Discovery by Jira Ticket +name: Find packages for Jira ticket and create installers on: workflow_dispatch: @@ -30,7 +30,7 @@ permissions: jobs: find-packages: name: Find packages for Jira ticket - uses: ultimaker/cura-workflows/.github/workflows/find-package-by-ticket.yml@jira_find_package + uses: ultimaker/cura-workflows/.github/workflows/find-package-by-ticket.yml@main with: jira_ticket_number: ${{ inputs.jira_ticket_number }} secrets: inherit @@ -47,4 +47,3 @@ jobs: enterprise: ${{ inputs.enterprise }} staging: ${{ inputs.staging }} secrets: inherit - From 21764d3db00a573aa3635fe1635d995ec85b034d Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 10:38:48 +0200 Subject: [PATCH 48/55] Remove redundant override The value is overwritten with the formula in fdmprinter, so setting the default_value does not have any effect in this case. --- resources/definitions/sovol_sv08.def.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index a0cda31889..e64cca8a06 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -95,7 +95,6 @@ "retraction_combing_max_distance": { "default_value": 10 }, "retraction_hop": { "default_value": 0.4 }, "retraction_hop_enabled": { "default_value": true }, - "retraction_min_travel": { "default_value": 1 }, "retraction_prime_speed": { "maximum_value_warning": 130, @@ -128,4 +127,4 @@ "wall_overhang_speed_factor": { "default_value": 50 }, "zig_zaggify_infill": { "value": true } } -} \ No newline at end of file +} From 445ea884ffc5837610801a32dbf73ab7da434f69 Mon Sep 17 00:00:00 2001 From: HellAholic <28710690+HellAholic@users.noreply.github.com> Date: Thu, 26 Jun 2025 08:39:47 +0000 Subject: [PATCH 49/55] Apply printer-linter format --- resources/definitions/sovol_sv08.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index e64cca8a06..a0b7125a9e 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -127,4 +127,4 @@ "wall_overhang_speed_factor": { "default_value": 50 }, "zig_zaggify_infill": { "value": true } } -} +} \ No newline at end of file From 8bfd3047548c41432fa374f7dcc4c1f87258704e Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 14:26:51 +0200 Subject: [PATCH 50/55] definition fix wall_overhang_speed_factor: Int should be wall_overhang_speed_factors: List --- resources/definitions/sovol_sv08.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index a0b7125a9e..c3896304fe 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -124,7 +124,7 @@ "travel_avoid_other_parts": { "default_value": false }, "wall_line_width": { "value": "machine_nozzle_size" }, "wall_overhang_angle": { "default_value": 75 }, - "wall_overhang_speed_factor": { "default_value": 50 }, + "wall_overhang_speed_factors": { "default_value": [50] }, "zig_zaggify_infill": { "value": true } } } \ No newline at end of file From ccea5bcfee63e8ccea0615f7e02f7ad7bf035949 Mon Sep 17 00:00:00 2001 From: HellAholic <28710690+HellAholic@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:27:49 +0000 Subject: [PATCH 51/55] Apply printer-linter format --- resources/definitions/sovol_sv08.def.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index c3896304fe..95a03f6e99 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -124,7 +124,12 @@ "travel_avoid_other_parts": { "default_value": false }, "wall_line_width": { "value": "machine_nozzle_size" }, "wall_overhang_angle": { "default_value": 75 }, - "wall_overhang_speed_factors": { "default_value": [50] }, + "wall_overhang_speed_factors": + { + "default_value": [ + 50 + ] + }, "zig_zaggify_infill": { "value": true } } } \ No newline at end of file From dd12c8152c14dde1495ea7012baabcf4e8a89a04 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Thu, 26 Jun 2025 14:47:21 +0200 Subject: [PATCH 52/55] overhang speed factors update to string(list) --- resources/definitions/sovol_sv08.def.json | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index 95a03f6e99..147b839fbf 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -124,12 +124,7 @@ "travel_avoid_other_parts": { "default_value": false }, "wall_line_width": { "value": "machine_nozzle_size" }, "wall_overhang_angle": { "default_value": 75 }, - "wall_overhang_speed_factors": - { - "default_value": [ - 50 - ] - }, + "wall_overhang_speed_factors": { "default_value": "[50]" }, "zig_zaggify_infill": { "value": true } } -} \ No newline at end of file +} From b73c5091ac6f8cdcb27f255473218d50c2d047bd Mon Sep 17 00:00:00 2001 From: HellAholic <28710690+HellAholic@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:48:18 +0000 Subject: [PATCH 53/55] Apply printer-linter format --- resources/definitions/sovol_sv08.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/sovol_sv08.def.json b/resources/definitions/sovol_sv08.def.json index 147b839fbf..a662b19618 100644 --- a/resources/definitions/sovol_sv08.def.json +++ b/resources/definitions/sovol_sv08.def.json @@ -127,4 +127,4 @@ "wall_overhang_speed_factors": { "default_value": "[50]" }, "zig_zaggify_infill": { "value": true } } -} +} \ No newline at end of file From 39c791ba470d42d56c221cebf71d4fd07928cbb7 Mon Sep 17 00:00:00 2001 From: HellAholic <28710690+HellAholic@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:01:17 +0000 Subject: [PATCH 54/55] Apply printer-linter format --- resources/extruders/sovol_sv08_extruder.def.json | 2 +- .../sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg | 9 +++++---- .../sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg | 9 +++++---- .../sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg | 9 +++++---- .../sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg | 9 +++++---- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/resources/extruders/sovol_sv08_extruder.def.json b/resources/extruders/sovol_sv08_extruder.def.json index 407c81de47..d0ccdee1de 100644 --- a/resources/extruders/sovol_sv08_extruder.def.json +++ b/resources/extruders/sovol_sv08_extruder.def.json @@ -16,4 +16,4 @@ }, "material_diameter": { "default_value": 1.75 } } -} +} \ No newline at end of file diff --git a/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg b/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg index 2fda8d1811..4fdd35f5ba 100644 --- a/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg +++ b/resources/quality/sovol/ABS/sovol_sv08_0.4_ABS_standard.inst.cfg @@ -11,16 +11,17 @@ type = quality variant = 0.4mm Nozzle [values] +bridge_fan_speed = 30 +bridge_settings_enabled = True cool_fan_enabled = True cool_fan_speed = 10 cool_fan_speed_max = 30 -cool_min_layer_time_fan_speed_max = 30 cool_min_layer_time = 4 +cool_min_layer_time_fan_speed_max = 30 cool_min_speed = 10 +material_bed_temperature = 95 material_flow = 98 material_max_flowrate = 21 material_print_temperature = 270 material_print_temperature_layer_0 = 280 -material_bed_temperature = 95 -bridge_settings_enabled = True -bridge_fan_speed = 30 + diff --git a/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg b/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg index ba43c72c8b..3cf9d68d04 100644 --- a/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg +++ b/resources/quality/sovol/PETG/sovol_sv08_0.4_PETG_standard.inst.cfg @@ -11,16 +11,17 @@ type = quality variant = 0.4mm Nozzle [values] +bridge_fan_speed = 70 +bridge_settings_enabled = True cool_fan_enabled = True cool_fan_speed = 10 cool_fan_speed_max = 30 -cool_min_layer_time_fan_speed_max = 30 cool_min_layer_time = 5 +cool_min_layer_time_fan_speed_max = 30 cool_min_speed = 10 +material_bed_temperature = 75 material_flow = 98 material_max_flowrate = 17 material_print_temperature = 235 material_print_temperature_layer_0 = 250 -material_bed_temperature = 75 -bridge_settings_enabled = True -bridge_fan_speed = 70 + diff --git a/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg b/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg index 2a70b9415b..daca9ebd94 100644 --- a/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg +++ b/resources/quality/sovol/PLA/sovol_sv08_0.4_PLA_standard.inst.cfg @@ -11,16 +11,17 @@ type = quality variant = 0.4mm Nozzle [values] +bridge_fan_speed = 100 +bridge_settings_enabled = True cool_fan_enabled = True cool_fan_speed = 50 cool_fan_speed_max = 70 -cool_min_layer_time_fan_speed_max = 50 cool_min_layer_time = 5 +cool_min_layer_time_fan_speed_max = 50 cool_min_speed = 10 +material_bed_temperature = 65 material_flow = 98 material_max_flowrate = 21 material_print_temperature = 220 material_print_temperature_layer_0 = 235 -material_bed_temperature = 65 -bridge_settings_enabled = True -bridge_fan_speed = 100 + diff --git a/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg b/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg index 1908bdb6b0..c1b44b46b0 100644 --- a/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg +++ b/resources/quality/sovol/TPU/sovol_sv08_0.4_TPU_standard.inst.cfg @@ -11,16 +11,17 @@ type = quality variant = 0.4mm Nozzle [values] +bridge_fan_speed = 100 +bridge_settings_enabled = True cool_fan_enabled = True cool_fan_speed = 80 cool_fan_speed_max = 100 -cool_min_layer_time_fan_speed_max = 50 cool_min_layer_time = 5 +cool_min_layer_time_fan_speed_max = 50 cool_min_speed = 10 +material_bed_temperature = 65 material_flow = 98 material_max_flowrate = 3.6 material_print_temperature = 240 material_print_temperature_layer_0 = 235 -material_bed_temperature = 65 -bridge_settings_enabled = True -bridge_fan_speed = 100 + From ae2a189c14dbab873b197b9235e8382847a7cf7e Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 30 Jun 2025 09:53:54 +0200 Subject: [PATCH 55/55] Replace "cloudActive" property by generic "active" CURA-12557 --- .../NetworkedPrinterOutputDevice.py | 4 ++-- cura/PrinterOutput/PrinterOutputDevice.py | 21 ++++++++++++++++++- cura/Settings/MachineManager.py | 11 ++++------ .../src/Cloud/CloudOutputDevice.py | 16 +++----------- .../UltimakerNetworkedPrinterOutputDevice.py | 4 ++-- .../qml/PrinterSelector/MachineSelector.qml | 4 ++-- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py index 3dc245d468..1d0be1389e 100644 --- a/cura/PrinterOutput/NetworkedPrinterOutputDevice.py +++ b/cura/PrinterOutput/NetworkedPrinterOutputDevice.py @@ -33,8 +33,8 @@ class AuthState(IntEnum): class NetworkedPrinterOutputDevice(PrinterOutputDevice): authenticationStateChanged = pyqtSignal() - def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None) -> None: - super().__init__(device_id = device_id, connection_type = connection_type, parent = parent) + def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None, active: bool = True) -> None: + super().__init__(device_id = device_id, connection_type = connection_type, parent = parent, active = active) self._manager = None # type: Optional[QNetworkAccessManager] self._timeout_time = 10 # After how many seconds of no response should a timeout occur? diff --git a/cura/PrinterOutput/PrinterOutputDevice.py b/cura/PrinterOutput/PrinterOutputDevice.py index 9c1727f569..b369fc1129 100644 --- a/cura/PrinterOutput/PrinterOutputDevice.py +++ b/cura/PrinterOutput/PrinterOutputDevice.py @@ -72,7 +72,10 @@ class PrinterOutputDevice(QObject, OutputDevice): # Signal to indicate that the configuration of one of the printers has changed. uniqueConfigurationsChanged = pyqtSignal() - def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None) -> None: + # Signal to indicate that the printer has become active or inactive + activeChanged = pyqtSignal() + + def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None, active: bool = True) -> None: super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance self._printers = [] # type: List[PrinterOutputModel] @@ -88,6 +91,8 @@ class PrinterOutputDevice(QObject, OutputDevice): self._accepts_commands = False # type: bool + self._active: bool = active + self._update_timer = QTimer() # type: QTimer self._update_timer.setInterval(2000) # TODO; Add preference for update interval self._update_timer.setSingleShot(False) @@ -295,3 +300,17 @@ class PrinterOutputDevice(QObject, OutputDevice): return self._firmware_updater.updateFirmware(firmware_file) + + @pyqtProperty(bool, notify = activeChanged) + def active(self) -> bool: + """ + Indicates whether the printer is active, which is not the same as "being the active printer". In this case, + active means that the printer can be used. An example of an inactive printer is one that cannot be used because + the user doesn't have enough seats on Digital Factory. + """ + return self._active + + def _setActive(self, active: bool) -> None: + if active != self._active: + self._active = active + self.activeChanged.emit() diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 1bdb32f4ac..3a2201449d 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -184,15 +184,13 @@ class MachineManager(QObject): def _onOutputDevicesChanged(self) -> None: for printer_output_device in self._printer_output_devices: - if hasattr(printer_output_device, "cloudActiveChanged"): - printer_output_device.cloudActiveChanged.disconnect(self.printerConnectedStatusChanged) + printer_output_device.activeChanged.disconnect(self.printerConnectedStatusChanged) self._printer_output_devices = [] for printer_output_device in self._application.getOutputDeviceManager().getOutputDevices(): if isinstance(printer_output_device, PrinterOutputDevice): self._printer_output_devices.append(printer_output_device) - if hasattr(printer_output_device, "cloudActiveChanged"): - printer_output_device.cloudActiveChanged.connect(self.printerConnectedStatusChanged) + printer_output_device.activeChanged.connect(self.printerConnectedStatusChanged) self.outputDevicesChanged.emit() @@ -576,12 +574,11 @@ class MachineManager(QObject): return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection @pyqtProperty(bool, notify = printerConnectedStatusChanged) - def activeMachineIsCloudActive(self) -> bool: + def activeMachineIsActive(self) -> bool: if not self._printer_output_devices: return True - first_printer = self._printer_output_devices[0] - return True if not hasattr(first_printer, 'cloudActive') else first_printer.cloudActive + return self._printer_output_devices[0].active def activeMachineNetworkKey(self) -> str: if self._global_container_stack: diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index 020cafacd8..010ef93fbd 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -65,8 +65,6 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): # Therefore, we create a private signal used to trigger the printersChanged signal. _cloudClusterPrintersChanged = pyqtSignal() - cloudActiveChanged = pyqtSignal() - def __init__(self, api_client: CloudApiClient, cluster: CloudClusterResponse, parent: QObject = None) -> None: """Creates a new cloud output device @@ -91,7 +89,8 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): address="", connection_type=ConnectionType.CloudConnection, properties=properties, - parent=parent + parent=parent, + active=cluster.display_status != "inactive" ) self._api = api_client @@ -115,9 +114,6 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._pre_upload_print_job = None # type: Optional[CloudPrintJobResponse] self._uploaded_print_job = None # type: Optional[CloudPrintJobResponse] - # Whether the printer is active, i.e. authorized for use i.r.t to workspace limitations - self._active = cluster.display_status != "inactive" - CuraApplication.getInstance().getBackend().backendDone.connect(self._resetPrintJob) CuraApplication.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) @@ -197,9 +193,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): self._received_print_jobs = status.print_jobs self._updatePrintJobs(status.print_jobs) - if status.active != self._active: - self._active = status.active - self.cloudActiveChanged.emit() + self._setActive(status.active) def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, filter_by_machine: bool = False, **kwargs) -> None: @@ -445,10 +439,6 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice): root_url_prefix = "-staging" if self._account.is_staging else "" return f"https://digitalfactory{root_url_prefix}.ultimaker.com/app/jobs/{self.clusterData.cluster_id}" - @pyqtProperty(bool, notify = cloudActiveChanged) - def cloudActive(self) -> bool: - return self._active - def __del__(self): CuraApplication.getInstance().getBackend().backendDone.disconnect(self._resetPrintJob) CuraApplication.getInstance().getController().getScene().sceneChanged.disconnect(self._onSceneChanged) diff --git a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py index 8f25df37db..3ac5ccc7e7 100644 --- a/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/UltimakerNetworkedPrinterOutputDevice.py @@ -46,10 +46,10 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice): QUEUED_PRINT_JOBS_STATES = {"queued", "error"} def __init__(self, device_id: str, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType, - parent=None) -> None: + parent=None, active: bool = True) -> None: super().__init__(device_id=device_id, address=address, properties=properties, connection_type=connection_type, - parent=parent) + parent=parent, active=active) # Trigger the printersChanged signal when the private signal is triggered. self.printersChanged.connect(self._clusterPrintersChanged) diff --git a/resources/qml/PrinterSelector/MachineSelector.qml b/resources/qml/PrinterSelector/MachineSelector.qml index 7acdd9573b..e8ee98fe8f 100644 --- a/resources/qml/PrinterSelector/MachineSelector.qml +++ b/resources/qml/PrinterSelector/MachineSelector.qml @@ -16,7 +16,7 @@ Cura.ExpandablePopup property bool isConnectedCloudPrinter: machineManager.activeMachineHasCloudConnection property bool isCloudRegistered: machineManager.activeMachineHasCloudRegistration property bool isGroup: machineManager.activeMachineIsGroup - property bool isCloudActive: machineManager.activeMachineIsCloudActive + property bool isActive: machineManager.activeMachineIsActive property string machineName: { if (isNetworkPrinter && machineManager.activeMachineNetworkGroupName != "") { @@ -41,7 +41,7 @@ Cura.ExpandablePopup } else if (isConnectedCloudPrinter && Cura.API.connectionStatus.isInternetReachable) { - if (isCloudActive) + if (isActive) { return "printer_cloud_connected" }