Feature boundary test lxy (#128)

* Add Boundary validator

* Boundary test ui

* refect & optimize boundary validation
This commit is contained in:
xiaoyeliu
2026-01-21 19:52:11 +08:00
committed by GitHub
parent 9cee21e0bf
commit a1769a2148
28 changed files with 6097 additions and 23 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;