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

@@ -0,0 +1,215 @@
#include "BoundaryValidator.hpp"
#include "Geometry.hpp"
#include "libslic3r.h"
#include <cmath>
namespace Slic3r {
// ============================================================================
// BoundaryValidator static methods
// ============================================================================
std::string BoundaryValidator::violation_type_name(ViolationType type)
{
switch (type) {
case ViolationType::Unknown:
return "Unknown";
case ViolationType::TravelMove:
return "Travel Move";
case ViolationType::ExtrudeMove:
return "Extrude Move";
case ViolationType::SpiralLift:
return "Spiral Lift";
case ViolationType::LazyLift:
return "Lazy Lift";
case ViolationType::WipeTower:
return "Wipe Tower";
case ViolationType::Skirt:
return "Skirt";
case ViolationType::Brim:
return "Brim";
case ViolationType::Support:
return "Support";
case ViolationType::ArcMove:
return "Arc Move";
default:
return "Unknown Violation";
}
}
// ============================================================================
// BuildVolumeBoundaryValidator implementation
// ============================================================================
BuildVolumeBoundaryValidator::BuildVolumeBoundaryValidator(
const BuildVolume& build_volume,
double epsilon)
: m_build_volume(build_volume), m_epsilon(epsilon)
{
}
bool BuildVolumeBoundaryValidator::validate_point(const Vec3d& point) const
{
// Validate Z height first (if printable_height is set)
if (m_build_volume.printable_height() > 0.0) {
if (point.z() > m_build_volume.printable_height() + m_epsilon) {
return false;
}
}
// Validate XY position based on build volume type
return is_inside_2d(Vec2d(point.x(), point.y()));
}
bool BuildVolumeBoundaryValidator::validate_line(const Vec3d& from, const Vec3d& to) const
{
// For line validation, we sample multiple points along the line
// to ensure the entire segment is within boundaries
const int num_samples = 10;
for (int i = 0; i <= num_samples; ++i) {
double t = static_cast<double>(i) / num_samples;
Vec3d sample_point = from + t * (to - from);
if (!validate_point(sample_point)) {
return false;
}
}
return true;
}
bool BuildVolumeBoundaryValidator::validate_arc(
const Vec3d& center,
double radius,
double start_angle,
double end_angle,
double z_height) const
{
// Sample points along the arc and validate each
std::vector<Vec3d> arc_points = sample_arc_points(
center, radius, start_angle, end_angle, z_height
);
for (const Vec3d& point : arc_points) {
if (!validate_point(point)) {
return false;
}
}
return true;
}
bool BuildVolumeBoundaryValidator::validate_polygon(const Polygon& poly, double z_height) const
{
// Check if Z height is valid
if (m_build_volume.printable_height() > 0.0) {
if (z_height > m_build_volume.printable_height() + m_epsilon) {
return false;
}
}
// Check all polygon vertices
for (const Point& pt : poly.points) {
Vec2d unscaled_pt = unscale(pt);
if (!is_inside_2d(unscaled_pt)) {
return false;
}
}
return true;
}
std::vector<Vec3d> BuildVolumeBoundaryValidator::sample_arc_points(
const Vec3d& center,
double radius,
double start_angle,
double end_angle,
double z_height,
int num_samples) const
{
std::vector<Vec3d> points;
points.reserve(num_samples);
// Handle angle wrapping (e.g., from 350° to 10° should go through 360°/0°)
double angle_range = end_angle - start_angle;
// Normalize to handle wrapping
if (angle_range < 0) {
angle_range += 2 * PI;
}
for (int i = 0; i < num_samples; ++i) {
double t = static_cast<double>(i) / (num_samples - 1);
double angle = start_angle + t * angle_range;
double x = center.x() + radius * std::cos(angle);
double y = center.y() + radius * std::sin(angle);
points.emplace_back(x, y, z_height);
}
return points;
}
bool BuildVolumeBoundaryValidator::is_inside_2d(const Vec2d& point) const
{
const BuildVolume_Type type = m_build_volume.type();
switch (type) {
case BuildVolume_Type::Rectangle:
{
// Get the bounding box of the build volume
const BoundingBoxf& bbox = m_build_volume.bounding_volume2d();
BoundingBoxf inflated_bbox = bbox;
inflated_bbox.min -= Vec2d(m_epsilon, m_epsilon);
inflated_bbox.max += Vec2d(m_epsilon, m_epsilon);
return inflated_bbox.contains(point);
}
case BuildVolume_Type::Circle:
{
// Get circle parameters - circle.center is already in scaled coordinates
const Geometry::Circled& circle = m_build_volume.circle();
const Vec2d center_unscaled(unscale<double>(circle.center.x()),
unscale<double>(circle.center.y()));
const double radius = unscale<double>(circle.radius) + m_epsilon;
// Check distance from center
double dist_sq = (point - center_unscaled).squaredNorm();
return dist_sq <= radius * radius;
}
case BuildVolume_Type::Convex:
case BuildVolume_Type::Custom:
{
// For convex/custom volumes, use point-in-polygon test
// Get the convex hull decomposition - this returns pair<top, bottom>
const auto& decomp = m_build_volume.top_bottom_convex_hull_decomposition_bed();
const std::vector<Vec2d>& top_hull = decomp.first;
if (top_hull.empty()) {
return false;
}
// Check if point is inside the top convex hull
Point scaled_point = scaled<coord_t>(point);
// Build polygon from Vec2d points
Polygon hull_poly;
for (const Vec2d& pt : top_hull) {
hull_poly.points.push_back(scaled<coord_t>(pt));
}
return hull_poly.contains(scaled_point);
}
case BuildVolume_Type::Invalid:
default:
// If build volume type is invalid, allow everything (fail-safe)
return true;
}
}
} // namespace Slic3r

View File

@@ -0,0 +1,168 @@
#ifndef slic3r_BoundaryValidator_hpp_
#define slic3r_BoundaryValidator_hpp_
#include "Point.hpp"
#include "Polygon.hpp"
#include "BuildVolume.hpp"
#include <vector>
#include <string>
namespace Slic3r {
/**
* @brief Abstract interface for validating geometric elements against print boundaries
*
* This class provides a unified interface for boundary validation across different
* parts of the slicing pipeline. Implementations can validate points, lines, arcs,
* and polygons against the build volume.
*/
class BoundaryValidator {
public:
/**
* @brief Types of boundary violations that can occur
*/
enum class ViolationType {
Unknown = 0,
TravelMove, // Travel move exceeds boundaries
ExtrudeMove, // Extrude move exceeds boundaries
SpiralLift, // Spiral lift arc exceeds boundaries
LazyLift, // Lazy lift slope exceeds boundaries
WipeTower, // Wipe tower position exceeds boundaries
Skirt, // Skirt exceeds boundaries
Brim, // Brim exceeds boundaries
Support, // Support material exceeds boundaries
ArcMove, // G2/G3 arc move exceeds boundaries
Count // Sentinel value
};
/**
* @brief Direction of boundary violation
*/
enum class BoundaryDirection {
Unknown = 0,
X_Min, // Beyond X minimum boundary
X_Max, // Beyond X maximum boundary
Y_Min, // Beyond Y minimum boundary
Y_Max, // Beyond Y maximum boundary
Z_Max, // Above Z maximum boundary
Radius, // Beyond circular bed radius
Count // Sentinel value
};
/**
* @brief Describes a single boundary violation
*/
struct BoundaryViolation {
ViolationType type; // Type of violation
BoundaryDirection direction; // Direction of violation
std::string description; // Human-readable description
Vec3d position; // Position where violation occurs (unscaled)
double distance_out; // How far outside boundaries (unscaled)
double layer_z; // Z height of the layer (unscaled)
std::string object_name; // Name of related object (if applicable)
BoundaryViolation(ViolationType t, const std::string& desc,
const Vec3d& pos, double z, const std::string& obj = "",
BoundaryDirection dir = BoundaryDirection::Unknown, double dist = 0.0)
: type(t), direction(dir), description(desc), position(pos), distance_out(dist), layer_z(z), object_name(obj) {}
};
using BoundaryViolations = std::vector<BoundaryViolation>;
virtual ~BoundaryValidator() = default;
/**
* @brief Validate a single point against boundaries
* @param point Point to validate (unscaled coordinates)
* @return true if point is within boundaries, false otherwise
*/
virtual bool validate_point(const Vec3d& point) const = 0;
/**
* @brief Validate a line segment against boundaries
* @param from Start point (unscaled coordinates)
* @param to End point (unscaled coordinates)
* @return true if entire line is within boundaries, false otherwise
*/
virtual bool validate_line(const Vec3d& from, const Vec3d& to) const = 0;
/**
* @brief Validate an arc path against boundaries
* @param center Arc center point (unscaled coordinates)
* @param radius Arc radius (unscaled)
* @param start_angle Start angle in radians
* @param end_angle End angle in radians
* @param z_height Z height of the arc (unscaled)
* @return true if entire arc is within boundaries, false otherwise
*/
virtual bool validate_arc(const Vec3d& center, double radius,
double start_angle, double end_angle,
double z_height) const = 0;
/**
* @brief Validate a polygon against boundaries
* @param poly Polygon to validate (scaled coordinates)
* @param z_height Z height of the polygon (unscaled)
* @return true if entire polygon is within boundaries, false otherwise
*/
virtual bool validate_polygon(const Polygon& poly, double z_height = 0.0) const = 0;
/**
* @brief Get human-readable name for violation type
*/
static std::string violation_type_name(ViolationType type);
};
/**
* @brief Concrete implementation of BoundaryValidator based on BuildVolume
*
* This validator uses the BuildVolume class to perform boundary checks.
* It supports all build volume types (Rectangle, Circle, Convex, Custom).
*/
class BuildVolumeBoundaryValidator : public BoundaryValidator {
public:
/**
* @brief Construct validator from BuildVolume
* @param build_volume Reference to the build volume
* @param epsilon Tolerance for boundary checks (default: BedEpsilon)
*/
explicit BuildVolumeBoundaryValidator(const BuildVolume& build_volume,
double epsilon = BuildVolume::BedEpsilon);
bool validate_point(const Vec3d& point) const override;
bool validate_line(const Vec3d& from, const Vec3d& to) const override;
bool validate_arc(const Vec3d& center, double radius,
double start_angle, double end_angle,
double z_height) const override;
bool validate_polygon(const Polygon& poly, double z_height = 0.0) const override;
private:
const BuildVolume& m_build_volume;
double m_epsilon;
/**
* @brief Sample points along an arc for validation
* @param center Arc center (unscaled)
* @param radius Arc radius (unscaled)
* @param start_angle Start angle in radians
* @param end_angle End angle in radians
* @param z_height Z height (unscaled)
* @param num_samples Number of sample points (default: 16)
* @return Vector of sampled points
*/
std::vector<Vec3d> sample_arc_points(const Vec3d& center, double radius,
double start_angle, double end_angle,
double z_height,
int num_samples = 16) const;
/**
* @brief Check if a 2D point is inside the build volume
* @param point 2D point (unscaled)
* @return true if inside, false otherwise
*/
bool is_inside_2d(const Vec2d& point) const;
};
} // namespace Slic3r
#endif // slic3r_BoundaryValidator_hpp_

View File

@@ -8,6 +8,9 @@
#include "libslic3r.h"
#include "PrintConfig.hpp"
#include "Model.hpp"
#include "BoundaryValidator.hpp"
#include "BuildVolume.hpp"
#include "GCode/GCodeProcessor.hpp"
#include <algorithm>
#include <numeric>
#include <unordered_set>
@@ -987,7 +990,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
polygons_reverse(ex_poly_holes_reversed);
if (has_outer_brim) {
// BBS: inner and outer boundary are offset from the same polygon incase of round off error.
// Snapmaker: inner and outer boundary are offset from the same polygon incase of round off error.
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
ExPolygons outerExpoly;
if (use_brim_ears) {
@@ -1690,7 +1693,8 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
std::vector<unsigned int>& printExtruders)
std::vector<unsigned int>& printExtruders,
Print* print_ptr)
{
double brim_width_max = 0;
@@ -1738,13 +1742,68 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
for (size_t iia = 0; iia < islands_area.size(); ++iia)
islands_area[iia].translate(plate_shift);
// Snapmaker: Create BuildVolume and BoundaryValidator for brim boundary checking
BuildVolume build_volume(print.config().printable_area.values, print.config().printable_height);
BuildVolumeBoundaryValidator validator(build_volume);
double first_layer_height = print.skirt_first_layer_height();
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
// Snapmaker: Validate brim area against build volume boundaries
for (const ExPolygon& expoly : iter->second) {
if (!validator.validate_polygon(expoly.contour, first_layer_height)) {
// Record boundary violation
if (print_ptr) {
BoundingBox bbox = get_extents(expoly.contour);
Vec3d violation_pos(
unscale<double>(bbox.center().x()),
unscale<double>(bbox.center().y()),
first_layer_height
);
PrintObject* obj = const_cast<PrintObject*>(print.get_object(iter->first));
std::string obj_name = obj ? obj->model_object()->name : "Unknown";
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::Brim),
violation_pos,
first_layer_height,
obj_name
);
print_ptr->add_boundary_violation(violation);
BOOST_LOG_TRIVIAL(warning) << "Brim for object " << obj_name
<< " exceeds build volume boundaries at z=" << first_layer_height << " mm";
}
}
}
brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
};
}
for (auto iter = supportBrimAreaMap.begin(); iter != supportBrimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
// Snapmaker: Validate support brim area against build volume boundaries
for (const ExPolygon& expoly : iter->second) {
if (!validator.validate_polygon(expoly.contour, first_layer_height)) {
// Record boundary violation
if (print_ptr) {
BoundingBox bbox = get_extents(expoly.contour);
Vec3d violation_pos(
unscale<double>(bbox.center().x()),
unscale<double>(bbox.center().y()),
first_layer_height
);
PrintObject* obj = const_cast<PrintObject*>(print.get_object(iter->first));
std::string obj_name = obj ? obj->model_object()->name : "Unknown";
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::Brim),
violation_pos,
first_layer_height,
obj_name + " (support brim)"
);
print_ptr->add_boundary_violation(violation);
BOOST_LOG_TRIVIAL(warning) << "Support brim for object " << obj_name
<< " exceeds build volume boundaries at z=" << first_layer_height << " mm";
}
}
}
supportBrimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
};
}

View File

@@ -15,11 +15,13 @@ class ObjectID;
// Produce brim lines around those objects, that have the brim enabled.
// Collect islands_area to be merged into the final 1st layer convex hull.
// If print_ptr is provided (non-const), boundary violations will be reported.
void make_brim(const Print& print, PrintTryCancel try_cancel,
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
std::vector<unsigned int>& printExtruders);
std::vector<unsigned int>& printExtruders,
Print* print_ptr = nullptr);
// BBS: automatically make brim
ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area);

View File

@@ -347,7 +347,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun
const Vec2f c = unscaled<float>(m_circle.center);
const float r = unscaled<double>(m_circle.radius) + epsilon;
const float r2 = sqr(r);
return m_max_print_height == 0.0 ?
return m_max_print_height == 0.0 ?
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move)
{ return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) :
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move)
@@ -357,7 +357,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
case BuildVolume_Type::Custom:
return m_max_print_height == 0.0 ?
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move)
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move)
{ return ! move_valid(move) || Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast<double>()); }) :
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex &move)
{ return ! move_valid(move) || (Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast<double>()) && move.position.z() <= z); });

View File

@@ -75,6 +75,8 @@ set(lisbslic3r_sources
BlacklistedLibraryCheck.hpp
BoundingBox.cpp
BoundingBox.hpp
BoundaryValidator.cpp
BoundaryValidator.hpp
BridgeDetector.cpp
BridgeDetector.hpp
Brim.cpp

View File

@@ -21,6 +21,8 @@
#include "libslic3r/format.hpp"
#include "Time.hpp"
#include "GCode/ExtrusionProcessor.hpp"
#include "BoundaryValidator.hpp"
#include "BuildVolume.hpp"
#include <algorithm>
#include <cmath>
#include <cstdlib>
@@ -1864,6 +1866,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_writer.set_is_bbl_machine(is_bbl_printers);
// Snapmaker: Initialize boundary validator for arc path validation
BuildVolume build_volume(print.config().printable_area.values, print.config().printable_height);
BuildVolumeBoundaryValidator validator(build_volume);
m_writer.set_boundary_validator(&validator, &print);
// How many times will be change_layer() called?
// change_layer() in turn increments the progress bar status.
m_layer_count = 0;

View File

@@ -574,6 +574,7 @@ void GCodeProcessorResult::reset() {
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
time = 0;
boundary_violations.clear();
//BBS: add mutex for protection of gcode result
unlock();
@@ -607,6 +608,7 @@ void GCodeProcessorResult::reset() {
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
bed_match_result = BedMatchResult(true);
warnings.clear();
boundary_violations.clear();
//BBS: add mutex for protection of gcode result
unlock();

View File

@@ -6,6 +6,7 @@
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/CustomGCode.hpp"
#include "libslic3r/BoundaryValidator.hpp"
#include <cstdint>
#include <array>
@@ -104,18 +105,66 @@ class Print;
}
};
// Forward declaration for BoundaryValidator
class BoundaryValidator;
struct ConflictResult
{
// ===== Existing fields for object collision =====
std::string _objName1;
std::string _objName2;
double _height;
const void *_obj1; // nullptr means wipe tower
const void *_obj2;
int layer = -1;
// ===== New fields for boundary violations =====
enum class ConflictType {
ObjectCollision, // Original: collision between objects
BoundaryViolation // New: path exceeds build volume boundaries
};
ConflictType conflict_type = ConflictType::ObjectCollision;
// Only valid when conflict_type == BoundaryViolation
int violation_type_int = -1; // Stores BoundaryValidator::ViolationType as int
Vec3d violation_position{Vec3d::Zero()}; // Position where violation occurs (unscaled)
// ===== Constructors =====
ConflictResult(const std::string &objName1, const std::string &objName2, double height, const void *obj1, const void *obj2)
: _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2)
: _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2),
conflict_type(ConflictType::ObjectCollision)
{}
ConflictResult() = default;
// New: Static factory method for boundary violations
// Note: violation_type should be cast from BoundaryValidator::ViolationType
static ConflictResult create_boundary_violation(
int violation_type,
const Vec3d& pos,
double height,
const std::string& obj_name = ""
) {
ConflictResult result;
result.conflict_type = ConflictType::BoundaryViolation;
result.violation_type_int = violation_type;
result.violation_position = pos;
result._height = height;
result._objName1 = obj_name;
result.layer = -1; // Will be computed later if needed
return result;
}
// Helper method to check if this is a boundary violation
bool is_boundary_violation() const {
return conflict_type == ConflictType::BoundaryViolation;
}
// Helper method to check if this is an object collision
bool is_object_collision() const {
return conflict_type == ConflictType::ObjectCollision;
}
};
struct BedMatchResult
@@ -191,6 +240,18 @@ class Print;
std::vector<std::string> params; // extra msg info
};
// Snapmaker: Detailed boundary violation information for better user feedback
// Uses BoundaryValidator enums to avoid duplication
struct BoundaryViolationInfo {
BoundaryValidator::ViolationType violation_type{BoundaryValidator::ViolationType::Unknown};
BoundaryValidator::BoundaryDirection direction{BoundaryValidator::BoundaryDirection::Unknown};
Vec3d position{Vec3d::Zero()}; // Position where violation occurs (mm)
double distance_out{0.0}; // How far outside (mm)
std::string component_name; // e.g., "Skirt", "Brim", "Support", "Wipe Tower"
int layer_num{-1}; // Layer number (if applicable)
float print_z{-1.0f}; // Z height at violation (mm)
};
std::string filename;
unsigned int id;
std::vector<MoveVertex> moves;
@@ -222,6 +283,8 @@ class Print;
std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers;
//BBS
std::vector<SliceWarning> warnings;
// Snapmaker: Detailed boundary violation information
std::vector<BoundaryViolationInfo> boundary_violations;
int nozzle_hrc;
NozzleType nozzle_type;
BedType bed_type = BedType::btCount;
@@ -255,6 +318,7 @@ class Print;
custom_gcode_per_print_z = other.custom_gcode_per_print_z;
spiral_vase_layers = other.spiral_vase_layers;
warnings = other.warnings;
boundary_violations = other.boundary_violations;
bed_type = other.bed_type;
bed_match_result = other.bed_match_result;
#if ENABLE_GCODE_VIEWER_STATISTICS

View File

@@ -1,11 +1,15 @@
#include "GCodeWriter.hpp"
#include "CustomGCode.hpp"
#include "BoundaryValidator.hpp"
#include "BuildVolume.hpp"
#include "Print.hpp"
#include "GCode/GCodeProcessor.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <assert.h>
#include <GCode/GCodeProcessor.hpp>
#include <cmath>
#ifdef __APPLE__
#include <boost/spirit/include/karma.hpp>
@@ -545,29 +549,116 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
if (delta(2) > 0 && delta_no_z.norm() != 0.0f) {
//BBS: SpiralLift
if (m_to_lift_type == LiftType::SpiralLift && this->is_current_position_clear()) {
//BBS: todo: check the arc move all in bed area, if not, then use lazy lift
// Calculate the radius of the spiral arc
double radius = delta(2) / (2 * PI * atan(this->extruder()->travel_slope()));
// Calculate arc center and angles for precise boundary validation
Vec2d ij_offset = radius * delta_no_z.normalized();
ij_offset = { -ij_offset(1), ij_offset(0) };
slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z");
// Arc center is source + ij_offset (in unscaled coordinates)
Vec3d arc_center = source + Vec3d(ij_offset(0), ij_offset(1), 0);
// Calculate start and end angles
// ij_offset is perpendicular to delta_no_z, so the arc starts from -ij_offset direction
double start_angle = std::atan2(-ij_offset(1), -ij_offset(0));
double end_angle = start_angle + 2 * PI; // Full circle
// Snapmaker: Use BoundaryValidator for precise arc validation
bool arc_valid = true;
if (m_boundary_validator) {
arc_valid = m_boundary_validator->validate_arc(
arc_center, radius, start_angle, end_angle, source.z()
);
if (!arc_valid) {
// Record boundary violation
if (m_print_ptr) {
Vec3d violation_pos = arc_center + Vec3d(radius, 0, source.z());
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::SpiralLift),
violation_pos,
source.z(),
"Spiral Lift"
);
m_print_ptr->add_boundary_violation(violation);
}
BOOST_LOG_TRIVIAL(warning) << "Spiral lift arc exceeds build volume boundaries, "
<< "downgrading to lazy lift. Center: (" << arc_center.x() << ", " << arc_center.y()
<< "), Radius: " << radius << " mm";
// Fall through to LazyLift check below
m_to_lift_type = LiftType::LazyLift;
}
} else {
// Fallback: Simple radius check if validator not available
constexpr double MAX_SAFE_SPIRAL_RADIUS = 50.0; // mm
if (radius > MAX_SAFE_SPIRAL_RADIUS) {
BOOST_LOG_TRIVIAL(warning) << "Spiral lift radius (" << radius
<< " mm) exceeds safe limit (" << MAX_SAFE_SPIRAL_RADIUS
<< " mm), downgrading to lazy lift to prevent boundary violations";
m_to_lift_type = LiftType::LazyLift;
arc_valid = false;
}
}
if (arc_valid) {
slop_move = this->_spiral_travel_to_z(target(2), ij_offset, "spiral lift Z");
}
}
//BBS: LazyLift
else if (m_to_lift_type == LiftType::LazyLift &&
this->is_current_position_clear() &&
if (m_to_lift_type == LiftType::LazyLift &&
this->is_current_position_clear() &&
atan2(delta(2), delta_no_z.norm()) < this->extruder()->travel_slope()) {
//BBS: check whether we can make a travel like
// _____
// / to make the z list early to avoid to hit some warping place when travel is long.
// Calculate the slope top point
Vec2d temp = delta_no_z.normalized() * delta(2) / tan(this->extruder()->travel_slope());
Vec3d slope_top_point = Vec3d(temp(0), temp(1), delta(2)) + source;
GCodeG1Formatter w0;
w0.emit_xyz(slope_top_point);
w0.emit_f(travel_speed * 60.0);
//BBS
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
slop_move = w0.string();
// Snapmaker: Use BoundaryValidator for precise line validation
bool slope_valid = true;
if (m_boundary_validator) {
// Validate the entire slope line from source to slope_top_point
slope_valid = m_boundary_validator->validate_line(source, slope_top_point);
if (!slope_valid) {
// Record boundary violation
if (m_print_ptr) {
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::LazyLift),
slope_top_point,
source.z(),
"Lazy Lift"
);
m_print_ptr->add_boundary_violation(violation);
}
BOOST_LOG_TRIVIAL(warning) << "Lazy lift slope exceeds build volume boundaries, "
<< "downgrading to normal lift. Slope point: (" << slope_top_point.x()
<< ", " << slope_top_point.y() << ", " << slope_top_point.z() << ")";
// Fall through to NormalLift
m_to_lift_type = LiftType::NormalLift;
}
} else {
// Fallback: Simple distance check if validator not available
constexpr double MAX_SAFE_SLOPE_DISTANCE = 100.0; // mm
double slope_distance = temp.norm();
if (slope_distance > MAX_SAFE_SLOPE_DISTANCE) {
BOOST_LOG_TRIVIAL(warning) << "Lazy lift slope distance (" << slope_distance
<< " mm) exceeds safe limit (" << MAX_SAFE_SLOPE_DISTANCE
<< " mm), downgrading to normal lift to prevent boundary violations";
m_to_lift_type = LiftType::NormalLift;
slope_valid = false;
}
}
if (slope_valid) {
GCodeG1Formatter w0;
w0.emit_xyz(slope_top_point);
w0.emit_f(travel_speed * 60.0);
//BBS
w0.emit_comment(GCodeWriter::full_gcode_comment, comment);
slop_move = w0.string();
}
}
else if (m_to_lift_type == LiftType::NormalLift) {
if (m_to_lift_type == LiftType::NormalLift) {
slop_move = _travel_to_z(target.z(), "normal lift Z");
}
}
@@ -734,6 +825,59 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
//center_offset is I and J axis
std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& center_offset, double dE, const bool is_ccw, const std::string& comment, bool force_no_extrusion)
{
// Snapmaker: Validate arc path against build volume boundaries
if (m_boundary_validator) {
// Calculate arc center (center_offset is relative to start point)
Vec2d start_point = { m_pos(0) - m_x_offset, m_pos(1) - m_y_offset };
Vec3d arc_center = Vec3d(start_point(0) + center_offset(0), start_point(1) + center_offset(1), m_pos(2));
// Calculate radius from center offset
double radius = std::sqrt(center_offset(0) * center_offset(0) + center_offset(1) * center_offset(1));
// Calculate start and end angles
Vec2d start_vec = start_point - Vec2d(arc_center.x(), arc_center.y());
Vec2d end_vec = Vec2d(point(0) - m_x_offset, point(1) - m_y_offset) - Vec2d(arc_center.x(), arc_center.y());
double start_angle = std::atan2(start_vec(1), start_vec(0));
double end_angle = std::atan2(end_vec(1), end_vec(0));
// Handle CCW vs CW and angle wrapping
if (is_ccw) {
// For CCW, ensure end_angle > start_angle (wrapping if needed)
if (end_angle < start_angle) {
end_angle += 2 * PI;
}
} else {
// For CW, ensure end_angle < start_angle (wrapping if needed)
if (end_angle > start_angle) {
end_angle -= 2 * PI;
}
}
// Validate the arc
bool arc_valid = m_boundary_validator->validate_arc(
arc_center, radius, start_angle, end_angle, m_pos(2)
);
if (!arc_valid) {
// Record boundary violation
if (m_print_ptr) {
Vec3d violation_pos = arc_center + Vec3d(radius, 0, m_pos(2));
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::ArcMove),
violation_pos,
m_pos(2),
"Arc Extrusion"
);
m_print_ptr->add_boundary_violation(violation);
}
BOOST_LOG_TRIVIAL(warning) << "Arc extrusion path exceeds build volume boundaries. "
<< "Center: (" << arc_center.x() << ", " << arc_center.y()
<< "), Radius: " << radius << " mm, Z: " << m_pos(2) << " mm";
// Continue anyway (don't fail, just warn)
}
}
m_pos(0) = point(0);
m_pos(1) = point(1);
if (!force_no_extrusion)

View File

@@ -11,6 +11,10 @@
namespace Slic3r {
// Forward declarations
class BoundaryValidator;
class Print;
class GCodeWriter {
public:
GCodeConfig config;
@@ -119,6 +123,12 @@ public:
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
GCodeFlavor get_gcode_flavor() const { return config.gcode_flavor; }
// Snapmaker: Set boundary validator for arc path validation
void set_boundary_validator(const BoundaryValidator* validator, Print* print_ptr = nullptr) {
m_boundary_validator = validator;
m_print_ptr = print_ptr;
}
// Returns whether this flavor supports separate print and travel acceleration.
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
private:
@@ -170,6 +180,10 @@ public:
double m_current_speed;
bool m_is_first_layer = true;
// Snapmaker: Boundary validator for arc path validation
const BoundaryValidator* m_boundary_validator = nullptr;
Print* m_print_ptr = nullptr;
enum class Acceleration {
Travel,
Print

View File

@@ -36,6 +36,7 @@
#include "nlohmann/json.hpp"
#include "GCode/ConflictChecker.hpp"
#include "BoundaryValidator.hpp"
#include <codecvt>
@@ -1285,6 +1286,45 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
}
}
}
// Snapmaker: Critical fix - Validate wipe tower position is within build volume boundaries
// This addresses vulnerability #3: Wipe tower position was not validated against bed boundaries
{
const size_t plate_index = this->get_plate_index();
const Vec3d plate_origin = this->get_plate_origin();
const float x = m_config.wipe_tower_x.get_at(plate_index) + plate_origin(0);
const float y = m_config.wipe_tower_y.get_at(plate_index) + plate_origin(1);
const float width = m_config.prime_tower_width.value;
const float brim_width = m_config.prime_tower_brim_width.value;
const float depth = this->wipe_tower_data(extruders.size()).depth;
// Check all four corners of wipe tower (including brim)
// Create a simple bounding box from printable_area config
BoundingBoxf bed_bbox;
for (const Vec2d& pt : m_config.printable_area.values) {
bed_bbox.merge(pt);
}
bool tower_outside = false;
// Check all corners
if (x - brim_width < bed_bbox.min.x() || x + width + brim_width > bed_bbox.max.x() ||
y - brim_width < bed_bbox.min.y() || y + depth + brim_width > bed_bbox.max.y()) {
tower_outside = true;
}
if (tower_outside) {
const float total_width = width + 2 * brim_width;
const float total_depth = depth + 2 * brim_width;
return StringObjectException{
Slic3r::format(_u8L("The prime tower at position (%.2f, %.2f) with dimensions %.2f x %.2f mm "
"(including %.2f mm brim) exceeds the bed boundaries. "
"Please adjust the prime tower position in the configuration."),
x, y, total_width, total_depth, brim_width),
nullptr,
"wipe_tower_x"
};
}
}
}
{
@@ -2146,7 +2186,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (this->has_brim()) {
Polygons islands_area;
make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap,
m_supportBrimMap, objPrintVec, printExtruders);
m_supportBrimMap, objPrintVec, printExtruders, this);
for (Polygon& poly_ex : islands_area)
poly_ex.douglas_peucker(SCALED_RESOLUTION);
for (Polygon &poly : union_(this->first_layer_islands(), islands_area))
@@ -2245,6 +2285,37 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
//BBS
result->conflict_result = m_conflict_result;
// Snapmaker: Copy boundary violations from Print to GCodeProcessorResult
// This allows detailed violation information to be displayed in the GUI
if (!m_boundary_violations.empty()) {
result->boundary_violations.clear();
result->boundary_violations.reserve(m_boundary_violations.size());
for (const auto& conflict : m_boundary_violations) {
if (conflict.is_boundary_violation()) {
GCodeProcessorResult::BoundaryViolationInfo info;
// Directly copy the violation type (enums are now unified)
info.violation_type = static_cast<BoundaryValidator::ViolationType>(conflict.violation_type_int);
// Copy position and height
info.position = conflict.violation_position;
info.print_z = static_cast<float>(conflict._height);
info.layer_num = conflict.layer;
// Direction is not directly available in ConflictResult, leave as Unknown
info.direction = BoundaryValidator::BoundaryDirection::Unknown;
info.distance_out = 0.0;
result->boundary_violations.push_back(info);
}
}
BOOST_LOG_TRIVIAL(info) << "Copied " << result->boundary_violations.size()
<< " boundary violations from Print to GCodeProcessorResult";
}
return path.c_str();
}
@@ -2341,6 +2412,11 @@ void Print::_make_skirt()
// Draw outlines from outside to inside.
// Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
std::vector<coordf_t> extruded_length(extruders.size(), 0.);
// Create BuildVolume and BoundaryValidator for skirt boundary checking
BuildVolume build_volume(m_config.printable_area.values, m_config.printable_height);
BuildVolumeBoundaryValidator validator(build_volume);
if (m_config.skirt_type == stCombined) {
for (size_t i = m_config.skirt_loops, extruder_idx = 0; i > 0; -- i) {
this->throw_if_canceled();
@@ -2356,6 +2432,28 @@ void Print::_make_skirt()
break;
loop = loops.front();
}
// Snapmaker: Validate skirt loop against build volume boundaries
if (!validator.validate_polygon(loop, initial_layer_print_height)) {
// Record boundary violation
BoundingBox loop_bbox = get_extents(loop);
Vec3d violation_pos(
unscale<double>(loop_bbox.center().x()),
unscale<double>(loop_bbox.center().y()),
initial_layer_print_height
);
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::Skirt),
violation_pos,
initial_layer_print_height,
"Skirt"
);
this->add_boundary_violation(violation);
BOOST_LOG_TRIVIAL(warning) << "Skirt loop exceeds build volume boundaries at z="
<< initial_layer_print_height << " mm";
// Continue with remaining loops but record the violation
}
// Extrude the skirt loop.
ExtrusionLoop eloop(elrSkirt);
eloop.paths.emplace_back(ExtrusionPath(
@@ -2414,6 +2512,26 @@ void Print::_make_skirt()
loop = loops.front();
}
// Snapmaker: Validate per-object skirt loop against build volume boundaries
if (!validator.validate_polygon(loop, initial_layer_print_height)) {
// Record boundary violation
BoundingBox loop_bbox = get_extents(loop);
Vec3d violation_pos(
unscale<double>(loop_bbox.center().x()),
unscale<double>(loop_bbox.center().y()),
initial_layer_print_height
);
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::Skirt),
violation_pos,
initial_layer_print_height,
object->model_object()->name
);
this->add_boundary_violation(violation);
BOOST_LOG_TRIVIAL(warning) << "Per-object skirt loop for " << object->model_object()->name
<< " exceeds build volume boundaries at z=" << initial_layer_print_height << " mm";
}
// Extrude the skirt loop.
ExtrusionLoop eloop(elrSkirt);
eloop.paths.emplace_back(ExtrusionPath(

View File

@@ -970,6 +970,23 @@ public:
static StringObjectException sequential_print_clearance_valid(const Print &print, Polygons *polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
ConflictResultOpt get_conflict_result() const { return m_conflict_result; }
// Snapmaker: boundary violations tracking
void add_boundary_violation(const ConflictResult& violation) {
m_boundary_violations.push_back(violation);
}
const std::vector<ConflictResult>& get_boundary_violations() const {
return m_boundary_violations;
}
void clear_boundary_violations() {
m_boundary_violations.clear();
}
bool has_boundary_violations() const {
return !m_boundary_violations.empty();
}
// Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim.
Points first_layer_wipe_tower_corners(bool check_wipe_tower_existance=true) const;
@@ -1061,6 +1078,8 @@ private:
int m_modified_count {0};
//BBS
ConflictResultOpt m_conflict_result;
//Snapmaker: boundary violations tracking
std::vector<ConflictResult> m_boundary_violations;
FakeWipeTower m_fake_wipe_tower;
//SoftFever: calibration

View File

@@ -2,6 +2,9 @@
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "BoundaryValidator.hpp"
#include "BuildVolume.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "ElephantFootCompensation.hpp"
#include "Geometry.hpp"
#include "I18N.hpp"
@@ -670,6 +673,57 @@ void PrintObject::generate_support_material()
this->_generate_support_material();
m_print->throw_if_canceled();
// Snapmaker: Validate support material against build volume boundaries
if (!m_support_layers.empty()) {
BuildVolume build_volume(m_print->config().printable_area.values, m_print->config().printable_height);
BuildVolumeBoundaryValidator validator(build_volume);
for (const SupportLayer* layer : m_support_layers) {
// Check support fills polygons
for (const Polygon& support_contour : layer->support_fills.polygons_covered_by_spacing()) {
// Apply instance transforms and check boundary
for (const PrintInstance& instance : this->instances()) {
Polygon translated_contour = support_contour;
translated_contour.translate(instance.shift);
// Convert to unscaled coordinates for validation
Vec3d plate_origin = m_print->get_plate_origin();
double z_height = layer->print_z;
// Check if any point of the contour exceeds boundaries
bool has_violation = false;
Vec3d violation_pos;
for (const Point& pt : translated_contour.points) {
Vec3d pt_unscaled(
unscale<double>(pt.x()) + plate_origin.x(),
unscale<double>(pt.y()) + plate_origin.y(),
z_height
);
if (!validator.validate_point(pt_unscaled)) {
has_violation = true;
violation_pos = pt_unscaled;
break;
}
}
if (has_violation) {
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::Support),
violation_pos,
z_height,
this->model_object()->name
);
m_print->add_boundary_violation(violation);
BOOST_LOG_TRIVIAL(warning) << "Support material for object " << this->model_object()->name
<< " exceeds build volume boundaries at z=" << z_height << " mm";
// Only report once per layer to avoid spam
break;
}
}
}
}
}
}
this->set_done(posSupportMaterial);
}

View File

@@ -8,6 +8,9 @@
#include "Geometry.hpp"
#include "Point.hpp"
#include "MutablePolygon.hpp"
#include "BoundaryValidator.hpp"
#include "BuildVolume.hpp"
#include "GCode/GCodeProcessor.hpp"
#include <cmath>
#include <memory>
@@ -581,6 +584,91 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
}
#endif /* SLIC3R_DEBUG */
// Snapmaker: Validate support polygons against build volume boundaries
// This addresses vulnerability #6: Support material boundary validation
BOOST_LOG_TRIVIAL(info) << "Support generator - Validating boundaries";
BuildVolume build_volume(object.print()->config().printable_area.values,
object.print()->config().printable_height);
BuildVolumeBoundaryValidator validator(build_volume);
int support_violations = 0;
for (const SupportLayer* layer : object.support_layers()) {
if (!layer)
continue;
// Check support extrusions
const ExtrusionEntityCollection& support_fills = layer->support_fills;
for (const ExtrusionEntity* entity : support_fills.entities) {
if (!entity)
continue;
// Check each extrusion path or loop
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(entity)) {
// Check each point in the polyline
for (const Point& pt : path->polyline.points) {
Vec3d pos(unscaled<double>(pt.x()), unscaled<double>(pt.y()), layer->print_z);
if (!validator.validate_point(pos)) {
support_violations++;
if (support_violations <= 5) { // Log first 5
BOOST_LOG_TRIVIAL(warning) << "Support path at z=" << layer->print_z
<< " exceeds build volume boundaries";
}
break; // Only record once per path
}
}
} else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(entity)) {
for (const ExtrusionPath& path : loop->paths) {
// Check each point in the polyline
for (const Point& pt : path.polyline.points) {
Vec3d pos(unscaled<double>(pt.x()), unscaled<double>(pt.y()), layer->print_z);
if (!validator.validate_point(pos)) {
support_violations++;
if (support_violations <= 5) { // Log first 5
BOOST_LOG_TRIVIAL(warning) << "Support loop path at z=" << layer->print_z
<< " exceeds build volume boundaries";
}
break; // Only record once per path
}
}
}
}
}
// Check support base polygons
for (const ExPolygon& expoly : layer->lslices) {
if (!validator.validate_polygon(expoly.contour, layer->print_z)) {
support_violations++;
if (support_violations <= 5) { // Log first 5
BOOST_LOG_TRIVIAL(warning) << "Support polygon at z=" << layer->print_z
<< " exceeds build volume boundaries";
}
}
// Check holes
for (const Polygon& hole : expoly.holes) {
if (!validator.validate_polygon(hole, layer->print_z)) {
support_violations++;
if (support_violations <= 5) { // Log first 5
BOOST_LOG_TRIVIAL(warning) << "Support hole polygon at z=" << layer->print_z
<< " exceeds build volume boundaries";
}
}
}
}
}
if (support_violations > 0) {
BOOST_LOG_TRIVIAL(warning) << "Found " << support_violations
<< " support polygons/paths exceeding build volume boundaries";
// Record violation
ConflictResult violation = ConflictResult::create_boundary_violation(
static_cast<int>(BoundaryValidator::ViolationType::Support),
Vec3d(object.center_offset().x(), object.center_offset().y(), 0.0),
0.0,
object.model_object()->name
);
object.print()->add_boundary_violation(violation);
}
BOOST_LOG_TRIVIAL(info) << "Support generator - End";
}