mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 02:11:18 +00:00
Merge branch '2.2.3' into dev_upgrade_alves
This commit is contained in:
@@ -3152,6 +3152,12 @@ int CLI::run(int argc, char **argv)
|
||||
ConfigOptionFloat* volume_option = print_config.option<ConfigOptionFloat>("prime_volume", true);
|
||||
float wipe_volume = volume_option->value;
|
||||
|
||||
ConfigOptionFloat* brim_chamfer_max_width_option = print_config.option<ConfigOptionFloat>("prime_tower_brim_chamfer_max_width", true);
|
||||
float brim_chamfer_max_width = brim_chamfer_max_width_option->value;
|
||||
|
||||
ConfigOptionBool* brim_chamfer_option = print_config.option<ConfigOptionBool>("prime_tower_brim_chamfer", true);
|
||||
bool brim_chamfer = brim_chamfer_option->value;
|
||||
|
||||
Vec3d wipe_tower_size = plate->estimate_wipe_tower_size(print_config, plate_obj_size_info.wipe_width, wipe_volume, filaments_cnt);
|
||||
plate_obj_size_info.wipe_depth = wipe_tower_size(1);
|
||||
|
||||
@@ -3882,6 +3888,8 @@ int CLI::run(int argc, char **argv)
|
||||
ConfigOptionFloat* width_option = m_print_config.option<ConfigOptionFloat>("prime_tower_width", true);
|
||||
ConfigOptionFloat* rotation_angle_option = m_print_config.option<ConfigOptionFloat>("wipe_tower_rotation_angle", true);
|
||||
ConfigOptionFloat* volume_option = m_print_config.option<ConfigOptionFloat>("prime_volume", true);
|
||||
ConfigOptionFloat* brim_chamfer_max_width_option = m_print_config.option<ConfigOptionFloat>("prime_tower_brim_chamfer_max_width", true);
|
||||
ConfigOptionBool* brim_chamfer_option = m_print_config.option<ConfigOptionBool>("prime_tower_brim_chamfer", true);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("prime_tower_width %1% wipe_tower_rotation_angle %2% prime_volume %3%") % width_option->value % rotation_angle_option->value % volume_option->value;
|
||||
|
||||
@@ -4136,6 +4144,8 @@ int CLI::run(int argc, char **argv)
|
||||
ConfigOptionFloat* width_option = m_print_config.option<ConfigOptionFloat>("prime_tower_width", true);
|
||||
ConfigOptionFloat* rotation_angle_option = m_print_config.option<ConfigOptionFloat>("wipe_tower_rotation_angle", true);
|
||||
ConfigOptionFloat* volume_option = m_print_config.option<ConfigOptionFloat>("prime_volume", true);
|
||||
ConfigOptionFloat* brim_chamfer_max_width_option = m_print_config.option<ConfigOptionFloat>("prime_tower_brim_chamfer_max_width", true);
|
||||
ConfigOptionBool* brim_chamfer_option = m_print_config.option<ConfigOptionBool>("prime_tower_brim_chamfer", true);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("prime_tower_width %1% wipe_tower_rotation_angle %2% prime_volume %3%")%width_option->value %rotation_angle_option->value %volume_option->value ;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
+61
-2
@@ -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)));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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); });
|
||||
|
||||
@@ -75,6 +75,8 @@ set(lisbslic3r_sources
|
||||
BlacklistedLibraryCheck.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
BoundaryValidator.cpp
|
||||
BoundaryValidator.hpp
|
||||
BridgeDetector.cpp
|
||||
BridgeDetector.hpp
|
||||
Brim.cpp
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1255,6 +1255,8 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
m_wipe_tower_width(float(config.prime_tower_width)),
|
||||
m_wipe_tower_rotation_angle(float(config.wipe_tower_rotation_angle)),
|
||||
m_wipe_tower_brim_width(float(config.prime_tower_brim_width)),
|
||||
m_prime_tower_brim_chamfer(config.prime_tower_brim_chamfer),
|
||||
m_prime_tower_brim_chamfer_max_width(float(config.prime_tower_brim_chamfer_max_width)),
|
||||
m_wipe_tower_cone_angle(float(config.wipe_tower_cone_angle)),
|
||||
m_extra_flow(float(config.wipe_tower_extra_flow/100.)),
|
||||
m_extra_spacing_wipe(float(config.wipe_tower_extra_spacing/100. * config.wipe_tower_extra_flow/100.)),
|
||||
@@ -2081,27 +2083,53 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, false);
|
||||
}
|
||||
|
||||
// brim (first layer only)
|
||||
if (first_layer) {
|
||||
// brim with chamfer (gradual layer-by-layer reduction)
|
||||
int loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing;
|
||||
|
||||
// Apply chamfer reduction if feature is enabled and brim width is configured
|
||||
if (m_wipe_tower_brim_width > 0 && m_prime_tower_brim_chamfer) {
|
||||
if (!first_layer) {
|
||||
// Calculate distance from first layer with tool changes
|
||||
size_t current_idx = m_layer_info - m_plan.begin();
|
||||
int dist_to_1st = (int)current_idx - (int)m_first_layer_idx;
|
||||
|
||||
// Stop print chamfer if depth changes
|
||||
bool depth_changed = (m_layer_info->depth != m_plan[m_first_layer_idx].depth);
|
||||
if (depth_changed) {
|
||||
loops_num = 0;
|
||||
}
|
||||
else {
|
||||
// Limit max chamfer width to configured value
|
||||
int chamfer_loops_num = (int)(m_prime_tower_brim_chamfer_max_width / spacing);
|
||||
loops_num = std::min(loops_num, chamfer_loops_num) - dist_to_1st;
|
||||
// Ensure loops_num doesn't go negative
|
||||
if (loops_num < 0) loops_num = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loops_num > 0) {
|
||||
writer.append("; WIPE_TOWER_BRIM_START\n");
|
||||
size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing;
|
||||
|
||||
for (size_t i = 0; i < loops_num; ++ i) {
|
||||
|
||||
for (int i = 0; i < loops_num; ++i) {
|
||||
poly = offset(poly, scale_(spacing)).front();
|
||||
int cp = poly.closest_point_index(Point::new_scale(writer.x(), writer.y()));
|
||||
writer.travel(unscale(poly.points[cp]).cast<float>());
|
||||
for (int i=cp+1; true; ++i ) {
|
||||
if (i==int(poly.points.size()))
|
||||
i = 0;
|
||||
writer.extrude(unscale(poly.points[i]).cast<float>());
|
||||
if (i == cp)
|
||||
for (int j = cp+1; true; ++j) {
|
||||
if (j == int(poly.points.size()))
|
||||
j = 0;
|
||||
writer.extrude(unscale(poly.points[j]).cast<float>());
|
||||
if (j == cp)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.append("; WIPE_TOWER_BRIM_END\n");
|
||||
// Save actual brim width to be later passed to the Print object, which will use it
|
||||
// for skirt calculation and pass it to GLCanvas for precise preview box
|
||||
m_wipe_tower_brim_width_real = loops_num * spacing;
|
||||
|
||||
// Save actual brim width only on first layer
|
||||
if (first_layer) {
|
||||
m_wipe_tower_brim_width_real = loops_num * spacing;
|
||||
}
|
||||
}
|
||||
|
||||
// Now prepare future wipe.
|
||||
|
||||
@@ -189,6 +189,8 @@ private:
|
||||
float m_wipe_tower_cone_angle = 0.f;
|
||||
float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config
|
||||
float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation
|
||||
bool m_prime_tower_brim_chamfer = true; // Enable/disable brim chamfer
|
||||
float m_prime_tower_brim_chamfer_max_width = 4.f; // Max chamfer width (mm)
|
||||
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
|
||||
float m_internal_rotation = 0.f;
|
||||
float m_y_shift = 0.f; // y shift passed to writer
|
||||
|
||||
+159
-15
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -839,7 +839,7 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"skin_infill_line_width","skeleton_infill_line_width",
|
||||
"top_surface_line_width", "support_line_width", "infill_wall_overlap","top_bottom_infill_wall_overlap", "bridge_flow", "internal_bridge_flow",
|
||||
"elefant_foot_compensation", "elefant_foot_compensation_layers", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower",
|
||||
"prime_tower_width", "prime_tower_brim_width", "prime_volume",
|
||||
"prime_tower_width", "prime_tower_brim_width", "prime_volume", "prime_tower_brim_chamfer", "prime_tower_brim_chamfer_max_width",
|
||||
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits",
|
||||
"flush_into_infill", "flush_into_objects", "flush_into_support",
|
||||
"tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter",
|
||||
|
||||
+121
-1
@@ -36,6 +36,7 @@
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include "GCode/ConflictChecker.hpp"
|
||||
#include "BoundaryValidator.hpp"
|
||||
|
||||
#include <codecvt>
|
||||
|
||||
@@ -293,6 +294,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "wipe_tower_no_sparse_layers"
|
||||
|| opt_key == "flush_volumes_matrix"
|
||||
|| opt_key == "prime_volume"
|
||||
|| opt_key == "prime_tower_brim_chamfer"
|
||||
|| opt_key == "prime_tower_brim_chamfer_max_width"
|
||||
|| opt_key == "flush_into_infill"
|
||||
|| opt_key == "flush_into_support"
|
||||
|| opt_key == "initial_layer_infill_speed"
|
||||
@@ -1285,6 +1288,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 +2188,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 +2287,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 +2414,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 +2434,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 +2514,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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5743,6 +5743,24 @@ void PrintConfigDef::init_fff_params()
|
||||
def->min = 0.;
|
||||
def->set_default_value(new ConfigOptionFloat(3.));
|
||||
|
||||
def = this->add("prime_tower_brim_chamfer", coBool);
|
||||
def->label = L("Brim chamfer");
|
||||
def->tooltip = L("Enable gradual layer-by-layer reduction of the brim around the prime tower. "
|
||||
"This creates a chamfered/tapered effect, reducing material usage while "
|
||||
"maintaining first layer adhesion.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("prime_tower_brim_chamfer_max_width", coFloat);
|
||||
def->label = L("Max chamfer width");
|
||||
def->tooltip = L("Maximum width of the chamfer zone measured from the tower perimeter. "
|
||||
"The brim will reduce within this distance. Larger values create a more "
|
||||
"gradual taper but take more layers to complete.");
|
||||
def->sidetext = "mm"; // milimeters, don't need translation
|
||||
def->mode = comAdvanced;
|
||||
def->min = 0.;
|
||||
def->set_default_value(new ConfigOptionFloat(4.0));
|
||||
|
||||
def = this->add("wipe_tower_cone_angle", coFloat);
|
||||
def->label = L("Stabilization cone apex angle");
|
||||
def->tooltip = L("Angle at the apex of the cone that is used to stabilize the wipe tower. "
|
||||
|
||||
@@ -1386,6 +1386,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloat, wipe_tower_per_color_wipe))
|
||||
((ConfigOptionFloat, wipe_tower_rotation_angle))
|
||||
((ConfigOptionFloat, prime_tower_brim_width))
|
||||
((ConfigOptionBool, prime_tower_brim_chamfer))
|
||||
((ConfigOptionFloat, prime_tower_brim_chamfer_max_width))
|
||||
((ConfigOptionFloat, wipe_tower_bridging))
|
||||
((ConfigOptionPercent, wipe_tower_extra_flow))
|
||||
((ConfigOptionFloats, flush_volumes_matrix))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+27
-28
@@ -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