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
This commit is contained in:
SoftFever
2026-06-17 15:39:40 +08:00
committed by GitHub
parent 7ab3174f7c
commit 9eeb73b68b
26 changed files with 137 additions and 31 deletions
+49 -1
View File
@@ -17,6 +17,7 @@
#include <limits>
#include <stdexcept>
#include <iomanip>
#include <regex>
#include <boost/assign.hpp>
#include <boost/bimap.hpp>
@@ -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
// "<name>_id_<object index>_copy_<instance index>" (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<GCodeFlavor>("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_<obj>_copy_<inst>").
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";
}
+3 -1
View File
@@ -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();
+1 -1
View File
@@ -1346,7 +1346,7 @@ static std::vector<std::string> 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"
};
+2 -1
View File
@@ -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<std::string> steps_ignore;
+8
View File
@@ -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.");
+1
View File
@@ -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))