Merge branch '2.2.3' into dev_upgrade_alves

This commit is contained in:
alves
2026-01-22 18:45:38 +08:00
42 changed files with 8112 additions and 1866 deletions

View File

@@ -2399,6 +2399,100 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const
{
//BBS: use convex_hull for toolpath outside check
m_contained_in_bed = build_volume.all_paths_inside(gcode_result, m_paths_bounding_box);
// BBS: Enhanced Travel move checking with smart filtering
// Skip initial setup moves (G28, G29) and only check moves during actual printing
if (m_contained_in_bed) {
// Find first extrusion move to determine where actual printing starts
size_t first_print_move = 0;
for (size_t i = 0; i < gcode_result.moves.size(); ++i) {
if (gcode_result.moves[i].type == EMoveType::Extrude &&
gcode_result.moves[i].position.z() > 0.1) { // Above first layer
first_print_move = i;
break;
}
}
// Only check moves after printing starts (skip G28/G29 initialization)
if (first_print_move > 0) {
bool has_travel_violations = false;
int violation_count = 0;
// Use same epsilon as BuildVolume::all_paths_inside() for consistency
static constexpr const double epsilon = BuildVolume::BedEpsilon;
// Clear existing violations and populate with detailed info
auto& gcode_result_writable = const_cast<GCodeProcessorResult&>(gcode_result);
gcode_result_writable.boundary_violations.clear();
for (size_t i = first_print_move; i < gcode_result.moves.size(); ++i) {
const auto& move = gcode_result.moves[i];
// Only check Travel moves (Extrude already checked by all_paths_inside)
if (move.type == EMoveType::Travel) {
// Quick rectangle bed check with tolerance
auto bbox = build_volume.bounding_volume();
if (move.position.x() < bbox.min.x() - epsilon ||
move.position.x() > bbox.max.x() + epsilon ||
move.position.y() < bbox.min.y() - epsilon ||
move.position.y() > bbox.max.y() + epsilon) {
// Create detailed violation info
GCodeProcessorResult::BoundaryViolationInfo violation;
violation.violation_type = BoundaryValidator::ViolationType::TravelMove;
violation.position = Vec3d(move.position.x(), move.position.y(), move.position.z());
violation.print_z = move.position.z();
violation.component_name = "Travel";
// Determine which boundary was exceeded
double dist_x_min = bbox.min.x() - move.position.x();
double dist_x_max = move.position.x() - bbox.max.x();
double dist_y_min = bbox.min.y() - move.position.y();
double dist_y_max = move.position.y() - bbox.max.y();
if (dist_x_min > 0) {
violation.direction = BoundaryValidator::BoundaryDirection::X_Min;
violation.distance_out = dist_x_min;
} else if (dist_x_max > 0) {
violation.direction = BoundaryValidator::BoundaryDirection::X_Max;
violation.distance_out = dist_x_max;
} else if (dist_y_min > 0) {
violation.direction = BoundaryValidator::BoundaryDirection::Y_Min;
violation.distance_out = dist_y_min;
} else if (dist_y_max > 0) {
violation.direction = BoundaryValidator::BoundaryDirection::Y_Max;
violation.distance_out = dist_y_max;
}
gcode_result_writable.boundary_violations.push_back(violation);
violation_count++;
if (violation_count <= 3) { // Log first 3
std::string dir_str;
switch (violation.direction) {
case BoundaryValidator::BoundaryDirection::X_Min: dir_str = "X_min"; break;
case BoundaryValidator::BoundaryDirection::X_Max: dir_str = "X_max"; break;
case BoundaryValidator::BoundaryDirection::Y_Min: dir_str = "Y_min"; break;
case BoundaryValidator::BoundaryDirection::Y_Max: dir_str = "Y_max"; break;
default: dir_str = "Unknown"; break;
}
BOOST_LOG_TRIVIAL(warning) << "Travel move #" << i
<< " outside bounds: " << violation.component_name << " " << dir_str
<< " at pos=(" << move.position.x()
<< ", " << move.position.y() << ", " << move.position.z() << ")";
}
has_travel_violations = true;
}
}
}
if (has_travel_violations) {
BOOST_LOG_TRIVIAL(warning) << "Found " << violation_count
<< " Travel moves outside build volume";
m_contained_in_bed = false;
}
}
}
if (m_contained_in_bed) {
//PartPlateList& partplate_list = wxGetApp().plater()->get_partplate_list();
//PartPlate* plate = partplate_list.get_curr_plate();

View File

@@ -849,6 +849,12 @@ public:
//BBS: add only gcode mode
bool is_only_gcode_in_preview() const { return m_only_gcode_in_preview; }
// Snapmaker: Get boundary violations from gcode_result
const std::vector<GCodeProcessorResult::BoundaryViolationInfo>& get_boundary_violations() const {
static const std::vector<GCodeProcessorResult::BoundaryViolationInfo> empty_violations;
return (m_gcode_result != nullptr) ? m_gcode_result->boundary_violations : empty_violations;
}
EViewType get_view_type() const { return m_view_type; }
void set_view_type(EViewType type, bool reset_feature_type_visible = true) {
if (type == EViewType::Count)

View File

@@ -9687,7 +9687,102 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
}
case EWarning::ObjectOutside: text = _u8L("An object is laid over the plate boundaries."); break;
case EWarning::ToolHeightOutside: text = _u8L("A G-code path goes beyond the max print height."); error = ErrorType::SLICING_ERROR; break;
case EWarning::ToolpathOutside: text = _u8L("A G-code path goes beyond the plate boundaries."); error = ErrorType::SLICING_ERROR; break;
case EWarning::ToolpathOutside: {
error = ErrorType::SLICING_ERROR;
// Snapmaker: Enhanced boundary violation reporting with detailed information
static std::string prevBoundaryText;
text = prevBoundaryText;
// Helper function to get localized violation type name
auto get_localized_type_string = [](BoundaryValidator::ViolationType type) -> std::string {
switch (type) {
case BoundaryValidator::ViolationType::TravelMove: return _u8L("Travel Move");
case BoundaryValidator::ViolationType::ExtrudeMove: return _u8L("Extrude Move");
case BoundaryValidator::ViolationType::SpiralLift: return _u8L("Spiral Lift");
case BoundaryValidator::ViolationType::LazyLift: return _u8L("Lazy Lift");
case BoundaryValidator::ViolationType::WipeTower: return _u8L("Wipe Tower");
case BoundaryValidator::ViolationType::Skirt: return _u8L("Skirt");
case BoundaryValidator::ViolationType::Brim: return _u8L("Brim");
case BoundaryValidator::ViolationType::Support: return _u8L("Support");
case BoundaryValidator::ViolationType::ArcMove: return _u8L("Arc Move");
default: return _u8L("Unknown");
}
};
// Helper function to get localized direction string
auto get_localized_direction_string = [](BoundaryValidator::BoundaryDirection dir) -> std::string {
switch (dir) {
case BoundaryValidator::BoundaryDirection::X_Min: return _u8L("beyond X minimum");
case BoundaryValidator::BoundaryDirection::X_Max: return _u8L("beyond X maximum");
case BoundaryValidator::BoundaryDirection::Y_Min: return _u8L("beyond Y minimum");
case BoundaryValidator::BoundaryDirection::Y_Max: return _u8L("beyond Y maximum");
case BoundaryValidator::BoundaryDirection::Z_Max: return _u8L("above Z maximum");
case BoundaryValidator::BoundaryDirection::Radius: return _u8L("beyond bed radius");
default: return _u8L("outside boundaries");
}
};
// Try to get detailed violation information from gcode_result
const auto& violations = m_gcode_viewer.get_boundary_violations();
if (!violations.empty()) {
// Group violations by type for better summary
std::map<BoundaryValidator::ViolationType, int> violation_counts;
std::map<BoundaryValidator::ViolationType, std::string> type_names;
for (const auto& v : violations) {
violation_counts[v.violation_type]++;
if (type_names.find(v.violation_type) == type_names.end()) {
type_names[v.violation_type] = get_localized_type_string(v.violation_type);
}
}
// Build detailed message
std::string msg = _u8L("G-code boundary violations detected:\n\n");
int total_count = 0;
for (const auto& [type, count] : violation_counts) {
msg += "" + type_names[type] + ": " + std::to_string(count) + " " + _u8L("violation(s)") + "\n";
total_count += count;
}
if (total_count > 1) {
msg += "\n" + _u8L("Total") + ": " + std::to_string(total_count) + " " + _u8L("violations");
}
// Show details of first few violations
if (!violations.empty()) {
msg += "\n\n" + _u8L("Details") + ":\n";
int show_count = std::min((int)violations.size(), 5);
for (int i = 0; i < show_count; ++i) {
const auto& v = violations[i];
// Build localized description
std::string desc;
if (!v.component_name.empty()) {
desc += v.component_name + " - ";
}
desc += get_localized_type_string(v.violation_type);
desc += " " + get_localized_direction_string(v.direction);
msg += " " + std::to_string(i + 1) + ". " + desc;
if (v.distance_out > 0.001) {
msg += " (" + (boost::format(_u8L("%.2f mm out")) % v.distance_out).str() + ")";
}
if (v.print_z > 0) {
msg += " " + _u8L("at Z") + "=" + (boost::format("%.1f") % v.print_z).str();
}
msg += "\n";
}
if ((int)violations.size() > show_count) {
msg += " " + _u8L("... and more");
}
}
text = msg;
prevBoundaryText = text;
} else {
// Fallback to generic message if no detailed info available
text = _u8L("A G-code path goes beyond the plate boundaries.");
}
break;
}
// BBS: remove _u8L() for SLA
case EWarning::SlaSupportsOutside: text = ("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break;
case EWarning::SomethingNotShown: text = _u8L("Only the object being edited is visible."); break;

View File

@@ -1150,12 +1150,15 @@ void PlaterPresetComboBox::update()
for (auto iter = machine_filaments.begin(); iter != machine_filaments.end();) {
std::string filament_name = iter->second.first;
auto item_iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_name, this](auto& f) { return f.name == filament_name; });
// First match: exact name + compatibility check
auto item_iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_name, this](auto& f) { return f.name == filament_name && f.is_compatible; });
// Second match: if first fails, try with @U1 suffix + compatibility check
if (item_iter == filaments.end()) {
item_iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_name, this](auto& f) { return f.name == filament_name + " @U1"; });
[&filament_name, this](auto& f) { return f.name == filament_name + " @U1" && f.is_compatible; });
}
if (item_iter != filaments.end()) {
@@ -1721,12 +1724,15 @@ void TabPresetComboBox::update()
for (auto iter = machine_filaments.begin(); iter != machine_filaments.end();) {
std::string filament_name = iter->second.first;
auto item_iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_name, this](auto& f) { return f.name == filament_name; });
// First match: exact name + compatibility check
auto item_iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_name, this](auto& f) { return f.name == filament_name && f.is_compatible; });
// Second match: if first fails, try with @U1 suffix + compatibility check
if (item_iter == filaments.end()) {
item_iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_name, this](auto& f) { return f.name == filament_name + " @U1"; });
[&filament_name, this](auto& f) { return f.name == filament_name + " @U1" && f.is_compatible; });
}
if (item_iter != filaments.end()) {

View File

@@ -68,7 +68,7 @@ void PrintHostSendDialog::init()
const AppConfig* app_config = wxGetApp().app_config;
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Please do not include the special characters #, *, and ;in filenames."));
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Please do not include the special characters #, *, ;, \\, /, :, \", <, >, or | in filenames."));
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
content_sizer->Add(txt_filename, 0, wxEXPAND);
@@ -135,9 +135,9 @@ void PrintHostSendDialog::init()
// .gcode suffix control
auto validate_path = [this](const wxString &path) -> bool {
// BBS: 检查文件名是否包含特殊字符
if (path.find_first_of("#*;") != wxString::npos) {
if (path.find_first_of("#*;\\/:\"<>|") != wxString::npos) {
MessageDialog msg_window(this,
wxString::Format(_L("The filename '%s' contains special characters (#, *, or ;) which may cause issues.Do you wish to continue?"), path),
wxString::Format(_L("The filename '%s' contains special characters (#, *, ;, \\, /, :, \", <, >, or |) which may cause issues.Do you wish to continue?"), path),
wxString(SLIC3R_APP_NAME), wxYES | wxNO | wxICON_WARNING);
if (msg_window.ShowModal() == wxID_NO)
return false;

View File

@@ -1901,14 +1901,17 @@ void Tab::apply_config_from_cache()
was_applied = static_cast<TabPrinter*>(this)->apply_extruder_cnt_from_cache();
if (!m_cache_config.empty()) {
// Apply to edited preset (官方版本的简单实现)
m_presets->get_edited_preset().config.apply(m_cache_config);
m_cache_config.clear();
was_applied = true;
}
if (was_applied)
if (was_applied) {
update_dirty();
// 标记为 dirty 以保留修改
m_presets->get_edited_preset().is_dirty = true;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<<boost::format(": exit, was_applied=%1%")%was_applied;
}
@@ -2411,6 +2414,8 @@ void TabPrint::build()
optgroup->append_single_option_line("prime_tower_width", "multimaterial_settings_prime_tower#width");
optgroup->append_single_option_line("prime_volume", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("prime_tower_brim_width", "multimaterial_settings_prime_tower#brim-width");
optgroup->append_single_option_line("prime_tower_brim_chamfer", "multimaterial_settings_prime_tower#brim-chamfer");
optgroup->append_single_option_line("prime_tower_brim_chamfer_max_width", "multimaterial_settings_prime_tower#brim-chamfer-max-width");
optgroup->append_single_option_line("wipe_tower_rotation_angle", "multimaterial_settings_prime_tower#wipe-tower-rotation-angle");
optgroup->append_single_option_line("wipe_tower_bridging", "multimaterial_settings_prime_tower#maximal-bridging-distance");
optgroup->append_single_option_line("wipe_tower_extra_spacing", "multimaterial_settings_prime_tower#wipe-tower-purge-lines-spacing");
@@ -5338,37 +5343,34 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
// check if there is something in the cache to move to the new selected preset
apply_config_from_cache();
size_t old_filament_count = m_preset_bundle->filament_presets.size();
load_current_preset();
// Orca: update presets for the selected printer
if (m_type == Preset::TYPE_PRINTER && wxGetApp().app_config->get_bool("remember_printer_config")) {
/*m_preset_bundle->update_selections(*wxGetApp().app_config);
int extruders_count =
m_preset_bundle->printers.get_edited_preset().config.opt<ConfigOptionFloats>("nozzle_diameter")->values.size();
bool support_multi_material =
m_preset_bundle->printers.get_edited_preset().config.opt<ConfigOptionBool>("single_extruder_multi_material")->getBool();
if (!support_multi_material) {
m_preset_bundle->set_num_filaments(extruders_count);
}
wxGetApp().plater()->sidebar().on_filaments_change(m_preset_bundle->filament_presets.size());*/
if (preset_name.find("Snapmaker U1") != std::string::npos) {
// 在 update_selections() 改变耗材数量之前先保存旧数量和颜色
size_t old_filament_count = m_preset_bundle->filament_presets.size();
std::vector<std::string> old_filament_colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
std::vector<std::string> old_filament_presets = m_preset_bundle->filament_presets;
m_preset_bundle->update_selections(*wxGetApp().app_config);
if (old_filament_count > m_preset_bundle->filament_presets.size()) {
m_preset_bundle->filament_presets.resize(old_filament_count, m_preset_bundle->filament_presets[0]);
}
// 恢复耗材预设到原来的数量和预设名称(保持类型自适应)
m_preset_bundle->filament_presets = old_filament_presets;
// 恢复原来的颜色,保持用户设置的耗材颜色不变
wxGetApp().preset_bundle->project_config.option<ConfigOptionStrings>("filament_colour")->values = old_filament_colors;
// 重要:立即保存颜色到配置文件,这样下次切换时也会保持
std::string filament_colors_str = boost::algorithm::join(old_filament_colors, ",");
wxGetApp().app_config->set_printer_setting(preset_name, "filament_colors", filament_colors_str);
wxGetApp().plater()->sidebar().on_filaments_change(m_preset_bundle->filament_presets.size());
} else {
// 非 U1 机型:使用默认行为
m_preset_bundle->update_selections(*wxGetApp().app_config);
wxGetApp().plater()->sidebar().on_filaments_change(m_preset_bundle->filament_presets.size());
}
}
load_current_preset();
if (delete_third_printer) {
wxGetApp().CallAfter([filament_presets, process_presets]() {
@@ -5400,6 +5402,8 @@ bool Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
});
}
// Trigger the on_presets_changed event to apply cached config for dependent tabs
on_presets_changed();
}
if (technology_changed)
@@ -5416,11 +5420,6 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr
if (presets == nullptr) presets = m_presets;
UnsavedChangesDialog dlg(m_type, presets, new_printer_name, no_transfer);
if (dlg.getUpdateItemCount() == 0) {
// no need to save
return true;
}
if (dlg.ShowModal() == wxID_CANCEL)
return false;