mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Merge upstream/main into cad-mainline (530 commits)
Catches the fork up from449a4cf9fc(2026-06-28) tod6cb667b89(2026-07-24). Upstream touched 2326 files; 17 of them overlap the 164 this branch touches. 16 of the 17 auto-merged, including all three CMakeLists.txt, the build_all.yml CI workflow, and every GUI file. The CAD core never conflicts: CadDocument, SketchEngine, SketchSolver, McpControl and test_caddocument are files this fork adds, so upstream does not touch them. The one conflict, tests/libslic3r/test_3mf.cpp, was purely additive in all three hunks and is resolved as a union: our test pinning that store_bbs_3mf embeds the CAD recipe as Metadata/SnapOrca_cad.bin, upstream's multi-nozzle plate-metadata round-trip tests, and both sets of includes. All three were verified present after resolution rather than assumed. NOT BUILD-VERIFIED, for a reason that predates this merge and is not caused by it: this fork cannot be configured on nativedev at all. Its CMakeLists has required Eigen3 5.0.1 since before the merge (line 592 pre-merge), while the only deps image on the machine is snaporca-deps, built for snaporca's find_package(Eigen3 3.3). CMake fails at configure, so nothing compiles. That means this fork's Catch2 suite has never run. Every "suite green" figure recorded for M1-M8 was snaporca's suite; the ports were verified by patch-apply plus the CAD sources being byte-identical to snaporca's. Building an orca_cad deps image with Eigen 5.0.1 is what would finally close that gap. Pre-merge state is preserved at branch cad-mainline-pre-upstream-2026-07-25 (30d54f0074). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -230,8 +230,8 @@ static void getNamedSolids(const TopLoc_Location& location,
|
||||
}
|
||||
|
||||
//bool load_step(const char *path, Model *model, bool& is_cancel,
|
||||
// double linear_defletion/*=0.003*/,
|
||||
// double angle_defletion/*= 0.5*/,
|
||||
// double linear_deflection/*=0.003*/,
|
||||
// double angle_deflection/*= 0.5*/,
|
||||
// bool isSplitCompound,
|
||||
// ImportStepProgressFn stepFn, StepIsUtf8Fn isUtf8Fn, long& mesh_face_num)
|
||||
//{
|
||||
@@ -288,7 +288,7 @@ static void getNamedSolids(const TopLoc_Location& location,
|
||||
// stl.resize(namedSolids.size());
|
||||
// tbb::parallel_for(tbb::blocked_range<size_t>(0, namedSolids.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||
// for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
// BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true);
|
||||
// BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_deflection, false, angle_deflection, true);
|
||||
// // BBS: calculate total number of the nodes and triangles
|
||||
// int aNbNodes = 0;
|
||||
// int aNbTriangles = 0;
|
||||
@@ -511,8 +511,8 @@ Step::Step_Status Step::load()
|
||||
Step::Step_Status Step::mesh(Model* model,
|
||||
bool& is_cancel,
|
||||
bool isSplitCompound,
|
||||
double linear_defletion/*=0.003*/,
|
||||
double angle_defletion/*= 0.5*/)
|
||||
double linear_deflection/*=0.003*/,
|
||||
double angle_deflection/*= 0.5*/)
|
||||
|
||||
{
|
||||
bool task_result = false;
|
||||
@@ -544,7 +544,7 @@ Step::Step_Status Step::mesh(Model* model,
|
||||
stl.resize(namedSolids.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, namedSolids.size()), [&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_defletion, false, angle_defletion, true);
|
||||
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, linear_deflection, false, angle_deflection, true);
|
||||
// BBS: calculate total number of the nodes and triangles
|
||||
int aNbNodes = 0;
|
||||
int aNbTriangles = 0;
|
||||
@@ -689,15 +689,15 @@ void Step::clean_mesh_data()
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Step::get_triangle_num(double linear_defletion, double angle_defletion)
|
||||
unsigned int Step::get_triangle_num(double linear_deflection, double angle_deflection)
|
||||
{
|
||||
unsigned int tri_num = 0;
|
||||
try {
|
||||
Handle(StepProgressIncdicator) progress = new StepProgressIncdicator(m_stop_mesh);
|
||||
clean_mesh_data();
|
||||
IMeshTools_Parameters param;
|
||||
param.Deflection = linear_defletion;
|
||||
param.Angle = angle_defletion;
|
||||
param.Deflection = linear_deflection;
|
||||
param.Angle = angle_deflection;
|
||||
param.InParallel = true;
|
||||
for (int i = 0; i < m_name_solids.size(); ++i) {
|
||||
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, param, progress->Start());
|
||||
@@ -719,7 +719,7 @@ unsigned int Step::get_triangle_num(double linear_defletion, double angle_deflet
|
||||
return tri_num;
|
||||
}
|
||||
|
||||
unsigned int Step::get_triangle_num_tbb(double linear_defletion, double angle_defletion)
|
||||
unsigned int Step::get_triangle_num_tbb(double linear_deflection, double angle_deflection)
|
||||
{
|
||||
unsigned int tri_num = 0;
|
||||
clean_mesh_data();
|
||||
@@ -727,7 +727,7 @@ unsigned int Step::get_triangle_num_tbb(double linear_defletion, double angle_de
|
||||
[&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
unsigned int solids_tri_num = 0;
|
||||
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, linear_defletion, false, angle_defletion, true);
|
||||
BRepMesh_IncrementalMesh mesh(m_name_solids[i].solid, linear_deflection, false, angle_deflection, true);
|
||||
for (TopExp_Explorer anExpSF(m_name_solids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc);
|
||||
|
||||
@@ -38,8 +38,8 @@ struct NamedSolid
|
||||
//BBS: Load an step file into a provided model.
|
||||
extern bool load_step(const char *path, Model *model,
|
||||
bool& is_cancel,
|
||||
double linear_defletion = 0.003,
|
||||
double angle_defletion = 0.5,
|
||||
double linear_deflection = 0.003,
|
||||
double angle_deflection = 0.5,
|
||||
bool isSplitCompound = false,
|
||||
ImportStepProgressFn proFn = nullptr,
|
||||
StepIsUtf8Fn isUtf8Fn = nullptr,
|
||||
@@ -98,14 +98,14 @@ public:
|
||||
Step(std::string path, ImportStepProgressFn stepFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
|
||||
~Step();
|
||||
Step_Status load();
|
||||
unsigned int get_triangle_num(double linear_defletion, double angle_defletion);
|
||||
unsigned int get_triangle_num_tbb(double linear_defletion, double angle_defletion);
|
||||
unsigned int get_triangle_num(double linear_deflection, double angle_deflection);
|
||||
unsigned int get_triangle_num_tbb(double linear_deflection, double angle_deflection);
|
||||
void clean_mesh_data();
|
||||
Step_Status mesh(Model* model,
|
||||
bool& is_cancel,
|
||||
bool isSplitCompound,
|
||||
double linear_defletion = 0.003,
|
||||
double angle_defletion = 0.5);
|
||||
double linear_deflection = 0.003,
|
||||
double angle_deflection = 0.5);
|
||||
|
||||
std::atomic<bool> m_stop_mesh{false};
|
||||
void update_process(int load_stage, int current, int total, bool& cancel);
|
||||
|
||||
@@ -348,6 +348,7 @@ static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR = "other_laye
|
||||
static constexpr const char* SPIRAL_VASE_MODE = "spiral_mode";
|
||||
static constexpr const char* FILAMENT_MAP_MODE_ATTR = "filament_map_mode";
|
||||
static constexpr const char* FILAMENT_MAP_ATTR = "filament_maps";
|
||||
static constexpr const char* FILAMENT_VOL_MAP_ATTR = "filament_volume_maps";
|
||||
static constexpr const char* LIMIT_FILAMENT_MAP_ATTR = "limit_filament_maps";
|
||||
static constexpr const char* GCODE_FILE_ATTR = "gcode_file";
|
||||
static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file";
|
||||
@@ -700,6 +701,35 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
info.id = it->first;
|
||||
info.used_g = used_filament_g;
|
||||
info.used_m = used_filament_m;
|
||||
|
||||
// Stamp each filament's logical-nozzle assignment onto the saved 3mf so the device/monitor can
|
||||
// reconstruct it. This block runs for every print: reorder_extruders_for_minimum_flush_volume
|
||||
// runs unconditionally and stores a (non-null) 1-nozzle result even for a single-extruder print,
|
||||
// so result->nozzle_group_result is non-null here for single-nozzle printers too. The stamped
|
||||
// nozzle_diameter is the grouping result's rounded matching-key value; the 3mf writer decides the
|
||||
// final saved diameter (see has_multi_nozzle_extruder). group_id and volume_type are unaffected.
|
||||
if (result && result->nozzle_group_result) {
|
||||
auto nozzles_for_filament = result->nozzle_group_result->get_nozzles_for_filament(it->first);
|
||||
if (!nozzles_for_filament.empty()) {
|
||||
info.group_id.reserve(nozzles_for_filament.size());
|
||||
std::set<double> diameters;
|
||||
std::set<NozzleVolumeType> volume_types;
|
||||
for (const auto& nozzle : nozzles_for_filament) {
|
||||
info.group_id.emplace_back(nozzle.group_id);
|
||||
diameters.insert(string_to_double_decimal_point(nozzle.diameter));
|
||||
volume_types.insert(nozzle.volume_type);
|
||||
}
|
||||
std::sort(info.group_id.begin(), info.group_id.end());
|
||||
info.group_id.erase(std::unique(info.group_id.begin(), info.group_id.end()), info.group_id.end());
|
||||
if (!diameters.empty())
|
||||
info.nozzle_diameter = *diameters.begin();
|
||||
if (volume_types.size() > 1)
|
||||
info.nozzle_volume_type = get_nozzle_volume_type_string(nvtHybrid);
|
||||
else if (!volume_types.empty())
|
||||
info.nozzle_volume_type = get_nozzle_volume_type_string(*volume_types.begin());
|
||||
}
|
||||
}
|
||||
|
||||
auto model_volume_it = ps.model_volumes_per_extruder.find(it->first);
|
||||
auto support_volume_it = ps.support_volumes_per_extruder.find(it->first);
|
||||
info.used_for_object = model_volume_it != ps.model_volumes_per_extruder.end() && model_volume_it->second > EPSILON;
|
||||
@@ -707,6 +737,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
slice_filaments_info.push_back(info);
|
||||
}
|
||||
|
||||
// Carry the layer-aware grouping result into the plate so the 3mf writer can emit the <nozzle> tags
|
||||
// and the enable_filament_dynamic_map flag. Only a LayeredNozzleGroupResult (the slicer output) is
|
||||
// stored; a device-side StaticNozzleGroupResult loaded from a 3mf is not re-serialized here.
|
||||
auto layered_group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(result->nozzle_group_result);
|
||||
if (layered_group_result)
|
||||
nozzle_group_result = *layered_group_result;
|
||||
|
||||
/* only for test
|
||||
GCodeProcessorResult::SliceWarning sw;
|
||||
sw.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT;
|
||||
@@ -1284,6 +1321,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_warning();
|
||||
|
||||
bool _handle_start_config_nozzle(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_nozzle();
|
||||
|
||||
//BBS: add plater config parse functions
|
||||
bool _handle_start_config_plater(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_plater();
|
||||
@@ -1619,8 +1659,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
plate->is_label_object_enabled = it->second->is_label_object_enabled;
|
||||
plate->skipped_objects = it->second->skipped_objects;
|
||||
plate->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate->nozzles_info = it->second->nozzles_info;
|
||||
plate->printer_model_id = it->second->printer_model_id;
|
||||
plate->nozzle_diameters = it->second->nozzle_diameters;
|
||||
plate->nozzle_volume_types = it->second->nozzle_volume_types;
|
||||
plate->filament_maps = it->second->filament_maps;
|
||||
plate->filament_change_sequence = it->second->filament_change_sequence;
|
||||
plate->nozzle_change_sequence = it->second->nozzle_change_sequence;
|
||||
@@ -2298,9 +2340,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
plate_data_list[it->first-1]->is_support_used = it->second->is_support_used;
|
||||
plate_data_list[it->first-1]->is_label_object_enabled = it->second->is_label_object_enabled;
|
||||
plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info;
|
||||
plate_data_list[it->first-1]->nozzles_info = it->second->nozzles_info;
|
||||
plate_data_list[it->first-1]->skipped_objects = it->second->skipped_objects;
|
||||
plate_data_list[it->first-1]->printer_model_id = it->second->printer_model_id;
|
||||
plate_data_list[it->first-1]->nozzle_diameters = it->second->nozzle_diameters;
|
||||
plate_data_list[it->first-1]->nozzle_volume_types = it->second->nozzle_volume_types;
|
||||
plate_data_list[it->first-1]->filament_maps = it->second->filament_maps;
|
||||
plate_data_list[it->first-1]->filament_change_sequence = it->second->filament_change_sequence;
|
||||
plate_data_list[it->first-1]->nozzle_change_sequence = it->second->nozzle_change_sequence;
|
||||
@@ -3478,6 +3522,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_start_config_filament(attributes, num_attributes);
|
||||
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
|
||||
res = _handle_start_config_warning(attributes, num_attributes);
|
||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||
res = _handle_start_config_nozzle(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
||||
res = _handle_start_assemble(attributes, num_attributes);
|
||||
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
|
||||
@@ -3512,6 +3558,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
res = _handle_end_config_plater();
|
||||
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
||||
res = _handle_end_config_filament();
|
||||
else if (::strcmp(NOZZLE_TAG, name) == 0)
|
||||
res = _handle_end_config_nozzle();
|
||||
else if (::strcmp(INSTANCE_TAG, name) == 0)
|
||||
res = _handle_end_config_plater_instance();
|
||||
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
||||
@@ -4469,6 +4517,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
m_curr_plater->config.set_key_value("filament_map", new ConfigOptionInts(filament_map));
|
||||
}
|
||||
}
|
||||
else if (key == FILAMENT_VOL_MAP_ATTR) {
|
||||
if (m_curr_plater){
|
||||
auto filament_volume_map = get_vector_from_string(value);
|
||||
for (size_t idx = 0; idx < filament_volume_map.size(); ++idx) {
|
||||
// The map feeds per-filament slot resolution and grouping. Clamp any
|
||||
// higher volume-type back to Standard(0) on load: Hybrid(2) is only an
|
||||
// in-memory grouping seed that is never persisted, and TPU High Flow(3)
|
||||
// is clamped with the same information loss on every load.
|
||||
if (filament_volume_map[idx] > 1) {
|
||||
filament_volume_map[idx] = 0;
|
||||
}
|
||||
}
|
||||
m_curr_plater->config.set_key_value("filament_volume_map", new ConfigOptionInts(filament_volume_map));
|
||||
}
|
||||
}
|
||||
else if (key == GCODE_FILE_ATTR)
|
||||
{
|
||||
m_curr_plater->gcode_file = value;
|
||||
@@ -4578,6 +4641,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (m_curr_plater)
|
||||
m_curr_plater->printer_model_id = value;
|
||||
}
|
||||
else if (key == NOZZLE_VOLUME_TYPE_ATTR)
|
||||
{
|
||||
if (m_curr_plater)
|
||||
m_curr_plater->nozzle_volume_types = value;
|
||||
}
|
||||
else if (key == NOZZLE_DIAMETERS_ATTR)
|
||||
{
|
||||
if (m_curr_plater)
|
||||
@@ -4631,6 +4699,41 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_nozzle(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
// Read the per-plate <nozzle> tags. Older 3mf without <nozzle> tags leave nozzles_info
|
||||
// empty; load_nozzle_infos_with_compatibility then rebuilds the list from the per-filament
|
||||
// group_id / filament_map on the device side.
|
||||
if (m_curr_plater) {
|
||||
// id="0" extruder_id="1" nozzle_diameter="0.4" volume_type="Standard"
|
||||
std::string id = bbs_get_attribute_value_string(attributes, num_attributes, "id");
|
||||
std::string extruder_id = bbs_get_attribute_value_string(attributes, num_attributes, "extruder_id");
|
||||
std::string nozzle_diameter= bbs_get_attribute_value_string(attributes, num_attributes, "nozzle_diameter");
|
||||
std::string volume_type = bbs_get_attribute_value_string(attributes, num_attributes, "volume_type");
|
||||
|
||||
auto volume_type_str_to_enum = ConfigOptionEnum<NozzleVolumeType>::get_enum_values();
|
||||
|
||||
MultiNozzleUtils::NozzleInfo nozzle_info;
|
||||
nozzle_info.group_id = atoi(id.c_str());
|
||||
nozzle_info.extruder_id = atoi(extruder_id.c_str()) - 1;
|
||||
nozzle_info.diameter = nozzle_diameter;
|
||||
|
||||
if (volume_type_str_to_enum.count(volume_type))
|
||||
nozzle_info.volume_type = NozzleVolumeType(volume_type_str_to_enum.at(volume_type));
|
||||
else
|
||||
nozzle_info.volume_type = NozzleVolumeType::nvtStandard;
|
||||
|
||||
m_curr_plater->nozzles_info.push_back(nozzle_info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_end_config_nozzle()
|
||||
{
|
||||
// do nothing
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Importer::_handle_start_config_warning(const char** attributes, unsigned int num_attributes)
|
||||
{
|
||||
if (m_curr_plater) {
|
||||
@@ -5959,7 +6062,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
m_thumbnail_middle = iter->second;
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
std::string filename = std::string(store_params.path);
|
||||
std::string filename = store_params.path;
|
||||
boost::filesystem::remove(filename + ".tmp", ec);
|
||||
|
||||
bool result = _save_model_to_file(filename + ".tmp", *store_params.model, store_params.plate_data_list, store_params.project_presets, store_params.config,
|
||||
@@ -8008,6 +8111,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
ConfigOptionInts* filament_volume_maps_opt = plate_data->config.option<ConfigOptionInts>("filament_volume_map");
|
||||
if (filament_map_mode_opt != nullptr && filament_volume_maps_opt != nullptr) {
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FILAMENT_VOL_MAP_ATTR << "\" " << VALUE_ATTR << "=\"";
|
||||
const std::vector<int>& volume_values = filament_volume_maps_opt->values;
|
||||
for (int i = 0; i < volume_values.size(); ++i) {
|
||||
stream << volume_values[i];
|
||||
if (i != (volume_values.size() - 1))
|
||||
stream << " ";
|
||||
}
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
if (save_gcode)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << xml_escape(plate_data->gcode_file) << "\"/>\n";
|
||||
if (!plate_data->gcode_file.empty()) {
|
||||
@@ -8147,7 +8262,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
[](unsigned int filament_id) { return filament_id + 1; });
|
||||
|
||||
const std::string plate_key = "plate_" + std::to_string(idx + 1);
|
||||
sequence_json[plate_key]["sequence"] = filament_sequence;
|
||||
// Dynamic-map plates write the sequence under "filament_sequence"; every other plate (the
|
||||
// whole shipping fleet + H2C static mode) keeps the "sequence" key, so the saved 3mf is
|
||||
// byte-identical to the older format. The reader accepts both.
|
||||
const bool enable_dynamic_map = plate_data->nozzle_group_result && plate_data->nozzle_group_result->is_support_dynamic_nozzle_map();
|
||||
const std::string seq_key = enable_dynamic_map ? "filament_sequence" : "sequence";
|
||||
sequence_json[plate_key][seq_key] = filament_sequence;
|
||||
sequence_json[plate_key]["nozzle_sequence"] = plate_data->nozzle_change_sequence;
|
||||
sequence_json[plate_key]["optimal_assignment"] = plate_data->optimal_assignment;
|
||||
}
|
||||
@@ -8233,6 +8353,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (nozzle_diameter_option)
|
||||
nozzle_diameters_str = nozzle_diameter_option->serialize();
|
||||
|
||||
// True when any extruder carries a cluster of interchangeable nozzles (max nozzle count
|
||||
// > 1). Such an extruder's per-nozzle diameters are not expressible in the per-extruder
|
||||
// nozzle_diameter config, so the saved <filament>/<nozzle> diameters must come from the
|
||||
// grouping result. For a single-nozzle-per-extruder printer the true diameter is the raw
|
||||
// config value; the grouping result rounds it to the nearest of {0.2,0.4,0.6,0.8} for its
|
||||
// internal matching key, so reading that back would rewrite a non-standard nozzle
|
||||
// (e.g. 0.5 -> 0.4). Use this flag to keep the exact config value in that case.
|
||||
auto* extruder_max_nozzle_count_option = dynamic_cast<const ConfigOptionInts*>(config.option("extruder_max_nozzle_count"));
|
||||
const bool has_multi_nozzle_extruder = extruder_max_nozzle_count_option &&
|
||||
std::any_of(extruder_max_nozzle_count_option->values.begin(), extruder_max_nozzle_count_option->values.end(),
|
||||
[](int v) { return v > 1; });
|
||||
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PRINTER_MODEL_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->printer_model_id << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NOZZLE_DIAMETERS_ATTR << "\" " << VALUE_ATTR << "=\"" << nozzle_diameters_str << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TIMELAPSE_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"" << timelapse_type << "\"/>\n";
|
||||
@@ -8242,7 +8374,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OUTSIDE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->toolpath_outside << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SUPPORT_USED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_support_used << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LABEL_OBJECT_ENABLED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_label_object_enabled << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << false << "\"/>\n";
|
||||
// Report the plate's dynamic-map state from the grouping result. The result is present
|
||||
// for the whole fleet (a static single-nozzle result too), so this if-branch is normally
|
||||
// taken; is_support_dynamic_nozzle_map() is false for any non-dynamic (static /
|
||||
// single-extruder) result ⇒ byte-identical to the previously hard-coded value. The else
|
||||
// is a defensive fallback for a missing result.
|
||||
if (plate_data && plate_data->nozzle_group_result)
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << plate_data->nozzle_group_result->is_support_dynamic_nozzle_map() << "\"/>\n";
|
||||
else
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << false << "\"/>\n";
|
||||
{
|
||||
bool has_filament_switcher = config.has("has_filament_switcher") ? config.opt_bool("has_filament_switcher") : false;
|
||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << HAS_FILAMENT_SWITCHER_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << has_filament_switcher << "\"/>\n";
|
||||
@@ -8357,7 +8497,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
if (std::find(used_nozzle_groups.begin(), used_nozzle_groups.end(), nozzle_group_id) == used_nozzle_groups.end())
|
||||
used_nozzle_groups.push_back(nozzle_group_id);
|
||||
const std::string filament_nozzle_group_id = it->group_id.empty() ? std::to_string(nozzle_group_id) : join_int_list_comma(it->group_id);
|
||||
const double filament_nozzle_diameter = it->nozzle_diameter > 0.0 ? it->nozzle_diameter : get_nozzle_diameter(nozzle_group_id);
|
||||
// Single-nozzle extruders: exact config diameter; clusters keep the result's rounded
|
||||
// value (see has_multi_nozzle_extruder).
|
||||
const double filament_nozzle_diameter = (has_multi_nozzle_extruder && it->nozzle_diameter > 0.0)
|
||||
? it->nozzle_diameter : get_nozzle_diameter(nozzle_group_id);
|
||||
const std::string filament_nozzle_volume_type = it->nozzle_volume_type.empty() ? get_nozzle_volume_type(nozzle_group_id) : it->nozzle_volume_type;
|
||||
|
||||
stream << " <" << FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id + 1) << "\" "
|
||||
@@ -8377,12 +8520,26 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n";
|
||||
}
|
||||
|
||||
for (int nozzle_group_id : used_nozzle_groups) {
|
||||
stream << " <" << NOZZLE_TAG << " "
|
||||
<< "id=\"" << nozzle_group_id << "\" "
|
||||
<< "extruder_id=\"" << nozzle_group_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << get_nozzle_diameter_str(nozzle_group_id) << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type(nozzle_group_id) << "\"/>\n";
|
||||
// Emit the <nozzle> tags from the grouping result. Single-nozzle-per-extruder printers
|
||||
// override the diameter with the exact config value (see has_multi_nozzle_extruder); the
|
||||
// else is a defensive fallback for a missing result.
|
||||
if (plate_data->nozzle_group_result) {
|
||||
auto used_nozzle_list = plate_data->nozzle_group_result->get_used_nozzles_in_extruder();
|
||||
for (auto& used_nozzle : used_nozzle_list) {
|
||||
if (!has_multi_nozzle_extruder && nozzle_diameter_option &&
|
||||
used_nozzle.extruder_id >= 0 && used_nozzle.extruder_id < (int) nozzle_diameter_option->values.size()) {
|
||||
used_nozzle.diameter = get_nozzle_diameter_str(used_nozzle.extruder_id);
|
||||
}
|
||||
stream << " <" << NOZZLE_TAG << " " << used_nozzle.serialize() << "/>\n";
|
||||
}
|
||||
} else {
|
||||
for (int nozzle_group_id : used_nozzle_groups) {
|
||||
stream << " <" << NOZZLE_TAG << " "
|
||||
<< "id=\"" << nozzle_group_id << "\" "
|
||||
<< "extruder_id=\"" << nozzle_group_id + 1 << "\" "
|
||||
<< "nozzle_diameter=\"" << get_nozzle_diameter_str(nozzle_group_id) << "\" "
|
||||
<< "volume_type=\"" << get_nozzle_volume_type(nozzle_group_id) << "\"/>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!plate_data->layer_filaments.empty()) {
|
||||
@@ -9016,7 +9173,7 @@ bool store_bbs_3mf(StoreParams& store_params)
|
||||
// All export should use "C" locales for number formatting.
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
if (store_params.path == nullptr || store_params.model == nullptr)
|
||||
if (store_params.path.empty() || store_params.model == nullptr)
|
||||
return false;
|
||||
|
||||
_BBS_3MF_Exporter exporter;
|
||||
|
||||
@@ -73,6 +73,7 @@ struct PlateData
|
||||
std::map<int, std::pair<int, int>> obj_inst_map;
|
||||
std::string printer_model_id;
|
||||
std::string nozzle_diameters;
|
||||
std::string nozzle_volume_types;
|
||||
std::string gcode_file;
|
||||
std::string gcode_file_md5;
|
||||
std::string thumbnail_file;
|
||||
@@ -102,6 +103,13 @@ struct PlateData
|
||||
std::vector<unsigned int> nozzle_change_sequence;
|
||||
std::vector<int> optimal_assignment;
|
||||
|
||||
// Multi-nozzle grouping surface. nozzles_info accumulates the <nozzle> tags read from a
|
||||
// gcode.3mf; nozzle_group_result is the slicer's per-filament→nozzle assignment carried into the
|
||||
// saved 3mf metadata (write) and reconstructed on load. Both are empty/nullopt for single-nozzle
|
||||
// prints, so the saved-3mf output for single-nozzle printers is byte-identical.
|
||||
std::vector<MultiNozzleUtils::NozzleInfo> nozzles_info;
|
||||
std::optional<MultiNozzleUtils::LayeredNozzleGroupResult> nozzle_group_result;
|
||||
|
||||
// Hexadecimal number,
|
||||
// the 0th digit corresponds to extruder 1
|
||||
// the 1th digit corresponds to extruder 2
|
||||
@@ -226,7 +234,7 @@ typedef std::map<int, PlateData*> PlateDataMaps;
|
||||
|
||||
struct StoreParams
|
||||
{
|
||||
const char* path;
|
||||
std::string path;
|
||||
Model* model = nullptr;
|
||||
PlateDataPtrs plate_data_list;
|
||||
int export_plate_idx = -1;
|
||||
|
||||
Reference in New Issue
Block a user