From 9eeb73b68bbb95c551a16c95010f4a17d48df899 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Wed, 17 Jun 2026 15:39:40 +0800 Subject: [PATCH] feat: Support 3MF as g-code (use_3mf option) (#14238) * feat: add support for 3MF file format in printer configurations and export options * fix file extension * enable 3mf for X Max 4 * disable use_3mf for X Plus 4 * Fixed an issue where `label_object_enabled` was not properly propagated to 3mf * enable exclude object for Max 4 * remove hardcoded use 3mf for flashforge, move them to the new printer profiles config --- resources/profiles/Flashforge.json | 2 +- .../Flashforge Guider4 0.4 HF nozzle.json | 1 + .../Flashforge Guider4 0.6 HF nozzle.json | 1 + .../Flashforge Guider4 0.6 nozzle.json | 1 + .../Flashforge Guider4 0.8 HF nozzle.json | 1 + .../Flashforge Guider4 Pro 0.4 HF nozzle.json | 1 + .../Flashforge Guider4 Pro 0.6 HF nozzle.json | 1 + .../Flashforge Guider4 Pro 0.6 nozzle.json | 1 + .../Flashforge Guider4 Pro 0.8 HF nozzle.json | 1 + .../machine/fdm_adventurer5m_common.json | 3 +- resources/profiles/Qidi.json | 2 +- .../Qidi/machine/Qidi Q2 0.4 nozzle.json | 5 +- .../Qidi/machine/Qidi Q2C 0.4 nozzle.json | 5 +- .../Qidi/machine/Qidi X-Max 4 0.4 nozzle.json | 3 +- .../machine/Qidi X-Plus 4 0.4 nozzle.json | 2 +- .../Qidi/process/fdm_process_n_common.json | 4 +- src/libslic3r/Format/bbs_3mf.cpp | 50 ++++++++++++++++++- src/libslic3r/GCode.cpp | 4 +- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 8 +++ src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 27 ++++++++++ src/slic3r/GUI/Plater.cpp | 36 +++++++------ src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 1 + 26 files changed, 137 insertions(+), 31 deletions(-) diff --git a/resources/profiles/Flashforge.json b/resources/profiles/Flashforge.json index cce1f362f4..da8160ea01 100644 --- a/resources/profiles/Flashforge.json +++ b/resources/profiles/Flashforge.json @@ -1,7 +1,7 @@ { "name": "Flashforge", "url": "", - "version": "02.04.00.01", + "version": "02.04.00.02", "force_update": "0", "description": "Flashforge configurations", "machine_model_list": [ diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.4 HF nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.4 HF nozzle.json index beda0488ee..2a53e60375 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.4 HF nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.4 HF nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 HF nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 HF nozzle.json index 6cf091acde..52dc1fd69d 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 HF nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 HF nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 nozzle.json index b2b449ab17..ae400d977c 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.6 nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": "", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.8 HF nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.8 HF nozzle.json index 2ff0ba8522..a98730ea82 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 0.8 HF nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 0.8 HF nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": "", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.4 HF nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.4 HF nozzle.json index 105985515a..54fe2df219 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.4 HF nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.4 HF nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 HF nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 HF nozzle.json index 78fcc22c12..a706546c17 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 HF nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 HF nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 nozzle.json index 01ca9d9919..57d3d8ba2d 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.6 nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": "", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.8 HF nozzle.json b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.8 HF nozzle.json index 8f267e0daf..dd46eccff1 100644 --- a/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.8 HF nozzle.json +++ b/resources/profiles/Flashforge/machine/Flashforge Guider4 Pro 0.8 HF nozzle.json @@ -49,6 +49,7 @@ "head_wrap_detect_zone": [], "high_current_on_filament_swap": "0", "host_type": "octoprint", + "use_3mf": "1", "layer_change_gcode": "", "long_retractions_when_cut": [ "0" diff --git a/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json b/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json index fb4fad8e04..fed3eb1fff 100644 --- a/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json +++ b/resources/profiles/Flashforge/machine/fdm_adventurer5m_common.json @@ -118,5 +118,6 @@ ], "extruder_clearance_height_to_lid": [ "150" - ] + ], + "use_3mf": "1" } diff --git a/resources/profiles/Qidi.json b/resources/profiles/Qidi.json index 97b0462855..7ff96dbeea 100644 --- a/resources/profiles/Qidi.json +++ b/resources/profiles/Qidi.json @@ -1,6 +1,6 @@ { "name": "Qidi", - "version": "02.04.00.02", + "version": "02.04.00.03", "force_update": "0", "description": "Qidi configurations", "machine_model_list": [ diff --git a/resources/profiles/Qidi/machine/Qidi Q2 0.4 nozzle.json b/resources/profiles/Qidi/machine/Qidi Q2 0.4 nozzle.json index 24e6450d00..6593d851bb 100644 --- a/resources/profiles/Qidi/machine/Qidi Q2 0.4 nozzle.json +++ b/resources/profiles/Qidi/machine/Qidi Q2 0.4 nozzle.json @@ -48,7 +48,7 @@ "20" ], "machine_pause_gcode": "PAUSE", - "machine_start_gcode": "INIT_MAPPING_VALUE\nPRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature] EXTRUDER=[initial_no_support_extruder]\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nM83\nT[initial_tool]\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nM141 S[chamber_temperature]\nG4 P3000\nG1 X108 Y1 F30000\nG0 Z[initial_layer_print_height] F600\nG0 X128 E8 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG1 X154 Z-0.1\nG1 X158\nG1 Z1 F600\nG1 X108 Y2.5 F30000\nG0 Z[initial_layer_print_height] F600\nG0 X128 E10 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG1 X154 Z-0.1\nG1 X158\nG1 Z1 F600", + "machine_start_gcode": "PRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature] EXTRUDER=[initial_no_support_extruder]\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nM83\nT[initial_tool]\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nM141 S[chamber_temperature]\nG4 P3000\nG1 X108 Y1 F30000\nG0 Z[initial_layer_print_height] F600\nG0 X128 E8 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG1 X154 Z-0.1\nG1 X158\nG1 Z1 F600\nG1 X108 Y2.5 F30000\nG0 Z[initial_layer_print_height] F600\nG0 X128 E10 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG1 X154 Z-0.1\nG1 X158\nG1 Z1 F600", "machine_unload_filament_time": "35", "nozzle_diameter": [ "0.4" @@ -71,5 +71,6 @@ "thumbnail_size": [ "150x150" ], - "printer_agent": "qidi" + "printer_agent": "qidi", + "use_3mf": "1" } diff --git a/resources/profiles/Qidi/machine/Qidi Q2C 0.4 nozzle.json b/resources/profiles/Qidi/machine/Qidi Q2C 0.4 nozzle.json index 0a5dcd9b90..290ca22210 100644 --- a/resources/profiles/Qidi/machine/Qidi Q2C 0.4 nozzle.json +++ b/resources/profiles/Qidi/machine/Qidi Q2C 0.4 nozzle.json @@ -48,7 +48,7 @@ "20" ], "machine_pause_gcode": "PAUSE", - "machine_start_gcode": "INIT_MAPPING_VALUE\nPRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] EXTRUDER=[initial_no_support_extruder]\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nM83\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nG4 P3000\nT[initial_tool]\nG1 X108.000 Y1 F30000\nG0 Z[initial_layer_print_height] F600\n;G1 E3 F1800\nG90\nM83\nG0 X128 E8 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG91\nG1 X1 Z-0.300\nG1 X4\nG1 Z1 F1200\nG90\nM400\nG1 X108.000 Y2.5 F30000\nG0 Z[initial_layer_print_height] F600\nM83\nG0 X128 E10 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG91\nG1 X1 Z-0.300\nG1 X4\nG1 Z1 F1200\nG90\nM400\nG1 Z1 F600", + "machine_start_gcode": "PRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] EXTRUDER=[initial_no_support_extruder]\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nM83\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nG4 P3000\nT[initial_tool]\nG1 X108.000 Y1 F30000\nG0 Z[initial_layer_print_height] F600\n;G1 E3 F1800\nG90\nM83\nG0 X128 E8 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG91\nG1 X1 Z-0.300\nG1 X4\nG1 Z1 F1200\nG90\nM400\nG1 X108.000 Y2.5 F30000\nG0 Z[initial_layer_print_height] F600\nM83\nG0 X128 E10 F{outer_wall_volumetric_speed/(24/20) * 60}\nG0 X133 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X138 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X143 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG0 X148 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5) * 60}\nG0 X153 E.3742 F{outer_wall_volumetric_speed/(0.3*0.5)/4 * 60}\nG91\nG1 X1 Z-0.300\nG1 X4\nG1 Z1 F1200\nG90\nM400\nG1 Z1 F600", "machine_unload_filament_time": "35", "nozzle_diameter": [ "0.4" @@ -71,5 +71,6 @@ "thumbnail_size": [ "150x150" ], - "printer_agent": "qidi" + "printer_agent": "qidi", + "use_3mf": "1" } diff --git a/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json b/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json index 61b143aa42..50ebf15682 100644 --- a/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json +++ b/resources/profiles/Qidi/machine/Qidi X-Max 4 0.4 nozzle.json @@ -78,5 +78,6 @@ "thumbnail_size": [ "150x150" ], - "fan_direction": "left" + "fan_direction": "left", + "use_3mf": "1" } diff --git a/resources/profiles/Qidi/machine/Qidi X-Plus 4 0.4 nozzle.json b/resources/profiles/Qidi/machine/Qidi X-Plus 4 0.4 nozzle.json index f4ebc236b1..1773bf68e3 100644 --- a/resources/profiles/Qidi/machine/Qidi X-Plus 4 0.4 nozzle.json +++ b/resources/profiles/Qidi/machine/Qidi X-Plus 4 0.4 nozzle.json @@ -94,7 +94,7 @@ "layer_change_gcode": "{if timelapse_type == 1} ; timelapse with wipe tower\nG92 E0\nG1 E-[retraction_length] F1800\nG2 Z{layer_z + 0.4} I0.86 J0.86 P1 F20000 ; spiral lift a little\nG1 Y304 F20000\nG1 X95 F20000\nG92 E0\nM400\nTIMELAPSE_TAKE_FRAME\nG1 Y324 F5000\nG1 E[retraction_length] F300\nG1 X65 F5000\nG1 Y290 F20000\n{elsif timelapse_type == 0} ; timelapse without wipe tower\nTIMELAPSE_TAKE_FRAME\n{endif}\nG92 E0\nSET_PRINT_STATS_INFO CURRENT_LAYER={layer_num + 1}", "machine_end_gcode": "DISABLE_BOX_HEATER\nM141 S0\nM140 S0\nDISABLE_ALL_SENSOR\nG1 E-3 F1800\nG0 Z{max_layer_z + 3} F600\nUNLOAD_FILAMENT T=[current_extruder]\nG0 Y290 F12000\nG0 X90 Y290 F12000\n{if max_layer_z < max_print_height / 2}G1 Z{max_print_height / 2 + 10} F600{else}G1 Z{min(max_print_height, max_layer_z + 3)}{endif}\nM104 S0", "time_lapse_gcode": "{if timelapse_type == 1} ; timelapse with wipe tower\nG92 E0\nG1 E-[retraction_length] F1800\nG2 Z{layer_z + 0.4} I0.86 J0.86 P1 F20000 ; spiral lift a little\nG1 Y304 F20000\nG1 X95 F20000\nG92 E0\nM400\nTIMELAPSE_TAKE_FRAME\nG1 Y324 F5000\nG1 E[retraction_length] F300\nG1 X65 F5000\nG1 Y290 F20000\n{elsif timelapse_type == 0} ; timelapse without wipe tower\nTIMELAPSE_TAKE_FRAME\n{endif}", - "machine_start_gcode": "INIT_MAPPING_VALUE\nPRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature] EXTRUDER=[initial_no_support_extruder]\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nM83\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nM141 S[chamber_temperature]\nG4 P3000\nT[initial_tool]\nG0 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0)} Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0] - 12, first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0) + 3} Z0\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0) + 6}\nG1 Z1 F600\nSET_PRINT_STATS_INFO CURRENT_LAYER=1", + "machine_start_gcode": "PRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature] EXTRUDER=[initial_no_support_extruder]\nSET_PRINT_STATS_INFO TOTAL_LAYER=[total_layer_count]\nM83\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nM141 S[chamber_temperature]\nG4 P3000\nT[initial_tool]\nG0 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0)} Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0] - 12, first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 3, first_layer_print_min[1] + 80) - 85), 0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0) + 3} Z0\nG1 X{max((min(print_bed_max[0] - 12, first_layer_print_min[0] + 80) - 85), 0) + 6}\nG1 Z1 F600\nSET_PRINT_STATS_INFO CURRENT_LAYER=1", "thumbnails_format": "PNG", "default_filament_profile": [ "Qidi Generic PLA @Qidi X-Plus 4 0.4 nozzle" diff --git a/resources/profiles/Qidi/process/fdm_process_n_common.json b/resources/profiles/Qidi/process/fdm_process_n_common.json index 4fd6a2d9d1..df7ff0f1a4 100644 --- a/resources/profiles/Qidi/process/fdm_process_n_common.json +++ b/resources/profiles/Qidi/process/fdm_process_n_common.json @@ -228,5 +228,7 @@ "prime_tower_lift_height": "-1", "prime_tower_max_speed": "90", "prime_tower_flat_ironing": "0", - "compatible_printers": [] + "compatible_printers": [], + "gcode_label_objects": "0", + "exclude_object": "1" } diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 411f134fb2..31519e4fbe 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -8137,6 +8138,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) return true; } + // Orca: replicates GCode's object-label sanitization (sanitize_instance_name in GCode.cpp), used to + // build the per-instance object name written into slice_info.config so it matches the EXCLUDE_OBJECT / + // M486 object name embedded in the g-code. Keep this in sync with GCode.cpp. + static std::string sanitize_object_label(const std::string& name) + { + // Compiled once: building a std::regex is expensive and this runs per object instance. + static const std::regex non_word_re("[ !@#$%^&*()=+\\[\\]{};:\",']+"); + std::string result = std::regex_replace(name, non_word_re, "_"); + if (!result.empty() && result.front() == '_') + result.erase(result.begin()); + if (!result.empty() && result.back() == '_') + result.erase(result.end() - 1); + return result; + } + bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config) { std::stringstream stream; @@ -8217,6 +8233,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) stream << "\"/>\n"; } + // Orca: for non-BambuLab printers that label objects in the g-code (Klipper/Marlin/RRF via + // EXCLUDE_OBJECT / M486), write the per-instance g-code object name into slice_info.config + // (e.g. "OrcaCube_v2.drc_id_0_copy_0") so the printer can correlate objects between the 3MF + // and the g-code. The g-code names objects per plate as + // "_id__copy_" (see GCode::set_object_info), which we + // reconstruct here from the (sorted) objects_and_instances list. identify_id is unchanged. + // BambuLab printers keep the raw object name. + const GCodeFlavor slice_gcode_flavor = config.opt_enum("gcode_flavor"); + const bool use_gcode_object_name = !GCodeProcessor::s_IsBBLPrinter && + (slice_gcode_flavor == gcfKlipper || slice_gcode_flavor == gcfMarlinLegacy || + slice_gcode_flavor == gcfMarlinFirmware || slice_gcode_flavor == gcfRepRapFirmware); + int gcode_object_index = -1; + int gcode_copy_index = 0; + int last_object_id = -1; + for (auto it = plate_data->objects_and_instances.begin(); it != plate_data->objects_and_instances.end(); it++) { int obj_id = it->first; @@ -8241,7 +8272,24 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) identify_id = inst->id().id; bool skipped = std::find(plate_data->skipped_objects.begin(), plate_data->skipped_objects.end(), identify_id) != plate_data->skipped_objects.end(); - stream << " <" << OBJECT_TAG << " " << IDENTIFYID_ATTR << "=\"" << std::to_string(identify_id) << "\" " << NAME_ATTR << "=\"" << xml_escape(obj->name) + + // Advance the per-plate g-code object/copy index. objects_and_instances is sorted, + // so all instances of the same object are contiguous. + if (obj_id != last_object_id) { + ++gcode_object_index; + gcode_copy_index = 0; + last_object_id = obj_id; + } else { + ++gcode_copy_index; + } + + std::string object_name = obj->name; + if (use_gcode_object_name) + // Matches GCode::get_instance_name(): sanitize(sanitize(name) + "_id__copy_"). + object_name = sanitize_object_label(sanitize_object_label(obj->name) + "_id_" + + std::to_string(gcode_object_index) + "_copy_" + std::to_string(gcode_copy_index)); + + stream << " <" << OBJECT_TAG << " " << IDENTIFYID_ATTR << "=\"" << std::to_string(identify_id) << "\" " << NAME_ATTR << "=\"" << xml_escape(object_name) << "\" " << SKIPPED_ATTR << "=\"" << (skipped ? "true" : "false") << "\" />\n"; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cdcca01f38..dd9fed2d07 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2194,7 +2194,9 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); print->set_done(psGCodeExport); - if(is_BBL_Printer() && result != nullptr) + // Orca: label_object_enabled reflects whether objects are labeled in the g-code (EXCLUDE_OBJECT / + // M486), which is driven by exclude_object for every printer + if(result != nullptr) result->label_object_enabled = m_enable_exclude_object; // Write the profiler measurements to file PROFILE_UPDATE(); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d3abf95fb4..948c779137 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1346,7 +1346,7 @@ static std::vector s_Preset_printer_options { "cooling_tube_retraction", "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower", "z_offset", - "disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", + "disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", "bed_temperature_formula", "nozzle_flush_dataset" }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 78bdc8f1cf..a118a9330e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -237,7 +237,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "bed_temperature_formula", "filament_notes", "process_notes", - "printer_notes" + "printer_notes", + "use_3mf" }; static std::unordered_set steps_ignore; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b27540dd87..41c23f70b5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -818,6 +818,14 @@ void PrintConfigDef::init_common_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("use_3mf", coBool); + def->label = L("Use 3MF instead of G-code"); + def->tooltip = L("Enable this if the printer accepts a 3MF file as the print job. When enabled, Orca Slicer " + "sends the sliced file as a .gcode.3mf, instead of a plain .gcode file."); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("printer_agent", coString); def->label = L("Printer Agent"); def->tooltip = L("Select the network agent implementation for printer communication."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 352d3b5f67..45281d5ce1 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1467,6 +1467,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, enable_filament_ramming)) ((ConfigOptionBool, tool_change_on_wipe_tower)) ((ConfigOptionBool, support_multi_bed_types)) + ((ConfigOptionBool, use_3mf)) // Small Area Infill Flow Compensation ((ConfigOptionStrings, small_area_infill_flow_compensation_model)) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f95be9f679..bd78f30a2a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -2026,6 +2026,26 @@ wxBoxSizer* MainFrame::create_side_tools() }); p->append_button(send_gcode_btn); + + // Orca: when the printer accepts a .gcode.3mf (the "Support 3MF as gcode" option), + // also offer exporting the sliced .gcode.3mf bundle + const auto& printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + const auto* use_3mf_opt = printer_config.option("use_3mf"); + if (use_3mf_opt != nullptr && use_3mf_opt->value) { + SideButton* export_sliced_file_btn = new SideButton(p, _L("Export plate sliced file"), ""); + export_sliced_file_btn->SetCornerRadius(0); + export_sliced_file_btn->Bind(wxEVT_BUTTON, [this, p](wxCommandEvent&) { + m_print_btn->SetLabel(_L("Export plate sliced file")); + m_print_select = eExportSlicedFile; + m_print_enable = get_enable_print_status(); + m_print_btn->Enable(m_print_enable); + this->Layout(); + fit_tab_labels(); // ORCA on label change + p->Dismiss(); + }); + p->append_button(export_sliced_file_btn); + } + p->append_button(export_gcode_btn); } else { @@ -4033,6 +4053,13 @@ void MainFrame::set_print_button_to_default(PrintSelectType select_type) m_print_enable = get_enable_print_status() && can_send_gcode(); m_print_btn->Enable(m_print_enable); this->Layout(); + } else if (select_type == PrintSelectType::eExportSlicedFile) { + m_print_btn->SetLabel(_L("Export plate sliced file")); + m_print_select = eExportSlicedFile; + if (m_print_enable) + m_print_enable = get_enable_print_status(); + m_print_btn->Enable(m_print_enable); + this->Layout(); } else { // unsupport return; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a5ba4f079d..be4342693e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2474,7 +2474,11 @@ void Sidebar::update_all_preset_comboboxes() else p->m_bpButton_ams_filament->Hide(); - auto print_btn_type = MainFrame::PrintSelectType::eExportGcode; + // Orca: with "Support 3MF as gcode" (use_3mf) the local export is a .gcode.3mf bundle, so when no + // printer host/IP is configured the default action is "Export plate sliced file" (mirrors the + // print dropdown) instead of "Export G-code file". + auto print_btn_type = cfg.opt_bool("use_3mf") ? MainFrame::PrintSelectType::eExportSlicedFile + : MainFrame::PrintSelectType::eExportGcode; wxString url = from_u8(PrintHost::get_print_host_webui(&cfg)); wxString apikey; if(url.empty()) @@ -10229,7 +10233,7 @@ void Plater::priv::on_action_print_plate(SimpleEvent&) m_select_machine_dlg->prepare(partplate_list.get_curr_plate_index()); m_select_machine_dlg->ShowModal(); } else { - q->send_gcode_legacy(PLATE_CURRENT_IDX, nullptr, true); + q->send_gcode_legacy(PLATE_CURRENT_IDX, nullptr); } } @@ -10330,7 +10334,7 @@ void Plater::priv::on_action_print_all(SimpleEvent&) m_select_machine_dlg->prepare(PLATE_ALL_IDX); m_select_machine_dlg->ShowModal(); } else { - q->send_gcode_legacy(PLATE_ALL_IDX, nullptr, true); + q->send_gcode_legacy(PLATE_ALL_IDX, nullptr); } } @@ -16093,7 +16097,7 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject & // and let the background processing start. this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); } -void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool use_3mf) +void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) { // if physical_printer is selected, send gcode for this printer // DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); @@ -16105,17 +16109,9 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us if (upload_job.empty()) return; - const auto host_type_opt = physical_printer_config->option>("host_type"); - const auto host_type = host_type_opt != nullptr ? host_type_opt->value : htElegooLink; - const auto* ff_serial_opt = physical_printer_config->option("flashforge_serial_number"); - const auto* ff_code_opt = physical_printer_config->option("printhost_apikey"); - const bool flashforge_local_api = - host_type == htFlashforge && - ff_serial_opt != nullptr && !ff_serial_opt->value.empty() && - ff_code_opt != nullptr && !ff_code_opt->value.empty(); - - if (flashforge_local_api) - use_3mf = true; + // Orca: the use_3mf printer option makes us send a .gcode.3mf to the printer + const auto* use_3mf_opt = physical_printer_config->option("use_3mf"); + const bool use_3mf = use_3mf_opt != nullptr && use_3mf_opt->value; upload_job.upload_data.use_3mf = use_3mf; @@ -16139,7 +16135,8 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us } default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); if (use_3mf) { - default_output_file.replace_extension("3mf"); + // Orca: a gcode-in-3mf bundle is named ".gcode.3mf" (matching "Export plate sliced file") + default_output_file.replace_extension(".gcode.3mf"); } // Repetier specific: Query the server for the list of file groups. @@ -16166,6 +16163,13 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us auto preset_bundle = wxGetApp().preset_bundle; auto config = get_app_config(); + const auto host_type_opt = physical_printer_config->option>("host_type"); + const auto host_type = host_type_opt != nullptr ? host_type_opt->value : htElegooLink; + const auto* ff_serial_opt = physical_printer_config->option("flashforge_serial_number"); + const auto* ff_code_opt = physical_printer_config->option("printhost_apikey"); + const bool flashforge_local_api = host_type == htFlashforge && ff_serial_opt != nullptr && !ff_serial_opt->value.empty() && + ff_code_opt != nullptr && !ff_code_opt->value.empty(); + std::unique_ptr pDlg; if (host_type == htElegooLink) { pDlg = std::make_unique(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 18254383cd..176f0fdb8e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -522,7 +522,7 @@ public: /* -1: send current gcode if not specified * -2: send all gcode to target machine */ int send_gcode(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); - void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr, bool use_3mf = false); + void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); int export_config_3mf(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); //BBS jump to nonitor after print job finished void send_calibration_job_finished(wxCommandEvent &evt); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 978528f3b3..a1dc626fc7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4477,6 +4477,7 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor"); optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer"); optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host"); + optgroup->append_single_option_line("use_3mf"); optgroup->append_single_option_line("scan_first_layer" , "printer_basic_information_advanced#scan-first-layer"); optgroup->append_single_option_line("enable_power_loss_recovery", "printer_basic_information_advanced#power-loss-recovery"); //option = optgroup->get_option("wrapping_exclude_area");