mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-26 12:22:32 +00:00
Merge branch '2.2.3' into dev_upgrade_alves
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user