Merge branch 'master_27x'

This commit is contained in:
Lukas Matena 2023-12-13 15:55:13 +01:00
commit 4ccc259c5a
72 changed files with 36745 additions and 36110 deletions

View File

@ -3,11 +3,11 @@
#
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignAfterOpenBracket: BlockIndent
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignOperands: false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
@ -25,7 +25,7 @@ BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
@ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 140
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true

View File

@ -1,8 +1,8 @@
set(LibBGCode_SOURCE_DIR "" CACHE PATH "Optionally specify local LibBGCode source directory")
set(_source_dir_line
URL https://github.com/prusa3d/libbgcode/archive/04556c4f64d4b7a5942d8d193d1eb87fc7e1005f.zip
URL_HASH SHA256=f0745b2dae95f0a49ae75bfbe4d775c751499fc4245864675e2dab06c13b2c8f
URL https://github.com/prusa3d/libbgcode/archive/bc390aab4427589a6402b4c7f65cf4d0a8f987ec.zip
URL_HASH SHA256=0c86cb67232089728233014f937e2a07d133a61e31dd8811a9c905e563a49f24
)
if (LibBGCode_SOURCE_DIR)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,9 @@
min_slic3r_version = 2.7.0-beta1
1.11.7 Updated binary g-code settings for PrusaSlicer 2.7.1. Binary g-code is not used by default in PrusaSlicer 2.7.0.
1.11.6 Updated FW version notification (XL/MINI/MK4/MK3.9). Updated print profiles for 0.25mm nozzle (XLIS/MK4IS/MK3.9).
1.11.5 Updated printer names and resources for XL/MINI Input Shaper (final firmware 5.1.0 was released).
1.11.4 Added filament profile for Prusament rPLA. Updated start g-code for XL Multi-Tool.
1.11.3 Updated support material settings (XL 0.6). Added new print profiles for MK4/MK3.9 IS 0.6. Updated start g-code (XL/MK4). Updated selected filament profiles. Updated FW version notification for MINI IS and XL IS (5.1.0-BETA). Decreased first layer speed for Prusa MINI IS.
1.11.2 Bumped up version for 2.7.0-rc1. Added Prusament Resin Model Neutral Beige.
1.11.2-beta0 Bumped up version for beta. Updated start/end g-code for Prusa MINI Input Shaper.
min_slic3r_version = 2.7.0-alpha2
@ -21,6 +26,10 @@ min_slic3r_version = 2.6.2-alpha0
1.11.0-alpha1 Updated ramming parameters. Updated start-gcode for XL Multi-Tool. Updated output filename format.
1.11.0-alpha0 Binary g-code, arc fitting, QOI/PNG thumbnails, 90degree XL tower, XL specific filament variants.
min_slic3r_version = 2.6.1-rc2
1.10.9 Updated FW version notification (XL/MINI/MK4/MK3.9). Updated print profiles for 0.25mm nozzle (XLIS/MK4IS/MK3.9).
1.10.8 Updated printer names and resources for XL/MINI Input Shaper (final firmware 5.1.0 was released).
1.10.7 Added filament profile for Prusament rPLA. Updated start g-code for XL Multi-Tool.
1.10.6 Enabled toolchange ramming for PLA. Added new print profiles for MK4/MK3.9 IS 0.6. Updated support material settings (0.6 nozzle). Updated FW version notification for MINI IS and XL IS. Added Prusament Resin Model Neutral Beige.
1.10.5 Updated FW version notification (MK2.5/MK3/MK3.9/MK4 family). Updated start/end g-code for Prusa MINI Input Shaper (Alpha).
1.10.4 Reduced nozzle temperature in selected filament profiles (Prusa XL 0.6mm nozzle).
1.10.3 Added additional printer checks for Prusa XL Input Shaper (Alpha).

File diff suppressed because one or more lines are too long

View File

@ -141,6 +141,9 @@ void AppConfig::set_defaults()
if (get("auto_toolbar_size").empty())
set("auto_toolbar_size", "100");
if (get("use_binary_gcode_when_supported").empty())
set("use_binary_gcode_when_supported", "1");
if (get("notify_release").empty())
set("notify_release", "all"); // or "none" or "release"
@ -686,25 +689,7 @@ bool AppConfig::update_skein_dir(const std::string &dir)
return false; // do not save "shapes gallery" directory
return this->set("recent", "skein_directory", dir);
}
/*
std::string AppConfig::get_last_output_dir(const std::string &alt) const
{
const auto it = m_storage.find("");
if (it != m_storage.end()) {
const auto it2 = it->second.find("last_output_path");
const auto it3 = it->second.find("remember_output_path");
if (it2 != it->second.end() && it3 != it->second.end() && ! it2->second.empty() && it3->second == "1")
return it2->second;
}
return alt;
}
void AppConfig::update_last_output_dir(const std::string &dir)
{
this->set("", "last_output_path", dir);
}
*/
std::string AppConfig::get_last_output_dir(const std::string& alt, const bool removable) const
{
std::string s1 = (removable ? "last_output_path_removable" : "last_output_path");

View File

@ -192,6 +192,10 @@ set(SLIC3R_SOURCES
GCode/GCodeProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp
GCode/AvoidCrossingPerimeters.hpp
GCode/Travels.cpp
GCode/Travels.hpp
GCode/LayerChanges.cpp
GCode/LayerChanges.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp

View File

@ -34,6 +34,8 @@
#include "GCode/Thumbnails.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/WipeTowerIntegration.hpp"
#include "GCode/Travels.hpp"
#include "GCode/LayerChanges.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "PrintConfig.hpp"
@ -776,8 +778,10 @@ namespace DoExport {
print_statistics.total_used_filament += used_filament;
print_statistics.total_extruded_volume += extruded_volume;
print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
print_statistics.total_wipe_tower_filament_weight += has_wipe_tower ? (extruded_volume - extruder.extruded_volume()) * extruder.filament_density() * 0.001 : 0.;
print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
}
if (!export_binary_data) {
filament_stats_string_out += out_filament_used_mm.first;
filament_stats_string_out += "\n" + out_filament_used_cm3.first;
@ -866,7 +870,7 @@ static inline GCode::SmoothPathCache smooth_path_interpolate_global(const Print&
void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb)
{
const bool export_to_binary_gcode = print.full_print_config().option<ConfigOptionBool>("gcode_binary")->value;
const bool export_to_binary_gcode = print.full_print_config().option<ConfigOptionBool>("binary_gcode")->value;
// if exporting gcode in binary format:
// we generate here the data to be passed to the post-processor, who is responsible to export them to file
// 1) generate the thumbnails
@ -895,67 +899,18 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
// config data
encode_full_config(print, binary_data.slicer_metadata.raw_data);
// printer data
binary_data.printer_metadata.raw_data.emplace_back("printer_model", print.config().printer_model.value); // duplicated into config data
std::string filament_types_str;
for (size_t i = 0; i < print.config().filament_type.values.size(); ++i) {
filament_types_str += print.config().filament_type.values[i];
if (i < print.config().filament_type.values.size() - 1)
filament_types_str += ";";
}
binary_data.printer_metadata.raw_data.emplace_back("filament_type", filament_types_str); // duplicated into config data
char buf[1024];
std::string nozzle_diameters_str;
for (size_t i = 0; i < print.config().nozzle_diameter.values.size(); ++i) {
sprintf(buf, i < print.config().nozzle_diameter.values.size() - 1 ? "%.2g," : "%.2g", print.config().nozzle_diameter.values[i]);
nozzle_diameters_str += buf;
}
binary_data.printer_metadata.raw_data.emplace_back("nozzle_diameter", nozzle_diameters_str); // duplicated into config data
std::string bed_temperatures_str;
for (size_t i = 0; i < print.config().bed_temperature.values.size(); ++i) {
sprintf(buf, i < print.config().bed_temperature.values.size() - 1 ? "%d," : "%d", print.config().bed_temperature.values[i]);
bed_temperatures_str += buf;
}
binary_data.printer_metadata.raw_data.emplace_back("bed_temperature", bed_temperatures_str); // duplicated into config data
const DynamicPrintConfig& cfg = print.full_print_config();
if (auto opt = cfg.option("brim_width"); opt != nullptr) {
sprintf(buf, "%.2g", dynamic_cast<const ConfigOptionFloat*>(opt)->value);
binary_data.printer_metadata.raw_data.emplace_back("brim_width", buf); // duplicated into config data
}
if (auto opt = cfg.option("fill_density"); opt != nullptr) {
sprintf(buf, "%.2g%%", dynamic_cast<const ConfigOptionPercent*>(opt)->value);
binary_data.printer_metadata.raw_data.emplace_back("fill_density", buf); // duplicated into config data
}
if (auto opt = cfg.option("layer_height"); opt != nullptr) {
sprintf(buf, "%.2g", dynamic_cast<const ConfigOptionFloat*>(opt)->value);
binary_data.printer_metadata.raw_data.emplace_back("layer_height", buf); // duplicated into config data
}
if (auto opt = cfg.option("temperature"); opt != nullptr) {
auto values = dynamic_cast<const ConfigOptionInts*>(opt)->values;
std::string temperatures_str;
for (size_t i = 0; i < values.size(); ++i) {
sprintf(buf, i < values.size() - 1 ? "%d," : "%d", values[i]);
temperatures_str += buf;
}
binary_data.printer_metadata.raw_data.emplace_back("temperature", temperatures_str); // duplicated into config data
}
if (auto opt = cfg.option("ironing"); opt != nullptr)
binary_data.printer_metadata.raw_data.emplace_back("ironing", dynamic_cast<const ConfigOptionBool*>(opt)->value ? "1" : "0"); // duplicated into config data
if (auto opt = cfg.option("support_material"); opt != nullptr)
binary_data.printer_metadata.raw_data.emplace_back("support_material", dynamic_cast<const ConfigOptionBool*>(opt)->value ? "1" : "0"); // duplicated into config data
if (auto opt = cfg.option("extruder_colour"); opt != nullptr) {
auto values = dynamic_cast<const ConfigOptionStrings*>(opt)->values;
std::string extruder_colours_str;
if (values.size() == 1 && values.front().empty())
extruder_colours_str = "\"\"";
else {
for (size_t i = 0; i < values.size(); ++i) {
sprintf(buf, i < values.size() - 1 ? "%s;" : "%s", values[i].c_str());
extruder_colours_str += buf;
}
}
binary_data.printer_metadata.raw_data.emplace_back("extruder_colour", extruder_colours_str); // duplicated into config data
// printer data - this section contains duplicates from the slicer metadata
// that we just created. Find and copy the entries that we want to duplicate.
const auto& slicer_metadata = binary_data.slicer_metadata.raw_data;
const std::vector<std::string> keys_to_duplicate = { "printer_model", "filament_type", "nozzle_diameter", "bed_temperature",
"brim_width", "fill_density", "layer_height", "temperature", "ironing", "support_material", "extruder_colour" };
assert(std::is_sorted(slicer_metadata.begin(), slicer_metadata.end(),
[](const auto& a, const auto& b) { return a.first < b.first; }));
for (const std::string& key : keys_to_duplicate) {
auto it = std::lower_bound(slicer_metadata.begin(), slicer_metadata.end(), std::make_pair(key, 0),
[](const auto& a, const auto& b) { return a.first < b.first; });
if (it != slicer_metadata.end() && it->first == key)
binary_data.printer_metadata.raw_data.emplace_back(*it);
}
}
@ -1414,6 +1369,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
file.write("\n");
file.write_format(PrintStatistics::TotalFilamentUsedGValueMask.c_str(), print.m_print_statistics.total_weight);
file.write_format(PrintStatistics::TotalFilamentCostValueMask.c_str(), print.m_print_statistics.total_cost);
file.write_format(PrintStatistics::TotalFilamentUsedWipeTowerValueMask.c_str(), print.m_print_statistics.total_wipe_tower_filament_weight);
if (print.m_print_statistics.total_toolchanges > 0)
file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
@ -2105,6 +2061,26 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector<unsigned int>&
return false;
}
namespace GCode::Impl {
AABBTreeLines::LinesDistancer<Linef> get_previous_layer_distancer(
const GCodeGenerator::ObjectsLayerToPrint& objects_to_print,
const ExPolygons& slices
) {
std::vector<Linef> lines;
for (const GCodeGenerator::ObjectLayerToPrint &object_to_print : objects_to_print) {
for (const PrintInstance& instance: object_to_print.object()->instances()) {
for (const ExPolygon& polygon : slices) {
for (const Line& line : polygon.lines()) {
lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}));
}
}
}
}
return AABBTreeLines::LinesDistancer{std::move(lines)};
}
}
// In sequential mode, process_layer is called once per each object and its copy,
// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
@ -2203,10 +2179,10 @@ LayerResult GCodeGenerator::process_layer(
print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
+ "\n";
}
gcode += this->change_layer(previous_layer_z, print_z); // this will increase m_layer_index
gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable); // this will increase m_layer_index
m_layer = &layer;
if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) {
this->m_previous_layer_distancer = GCode::Impl::get_expolygons_distancer(m_layer->lower_layer->lslices);
this->m_previous_layer_distancer = GCode::Impl::get_previous_layer_distancer(layers, layer.lower_layer->lslices);
}
m_object_layer_over_raft = false;
if (! print.config().layer_gcode.value.empty()) {
@ -2628,54 +2604,6 @@ std::string GCodeGenerator::preamble()
return gcode;
}
namespace GCode::Impl {
Polygon generate_regular_polygon(
const Point& centroid,
const Point& start_point,
const unsigned points_count
) {
Points points;
points.reserve(points_count);
const double part_angle{2*M_PI / points_count};
for (unsigned i = 0; i < points_count; ++i) {
const double current_angle{i * part_angle};
points.emplace_back(scaled(std::cos(current_angle)), scaled(std::sin(current_angle)));
}
Polygon regular_polygon{points};
const Vec2d current_vector{unscaled(regular_polygon.points.front())};
const Vec2d expected_vector{unscaled(start_point) - unscaled(centroid)};
const double current_scale = current_vector.norm();
const double expected_scale = expected_vector.norm();
regular_polygon.scale(expected_scale / current_scale);
regular_polygon.rotate(angle(current_vector, expected_vector));
regular_polygon.translate(centroid);
return regular_polygon;
}
Bed::Bed(const std::vector<Vec2d>& shape, const double padding):
inner_offset(get_inner_offset(shape, padding)),
centroid(unscaled(inner_offset.centroid()))
{}
bool Bed::contains_within_padding(const Vec2d& point) const {
return inner_offset.contains(scaled(point));
}
Polygon Bed::get_inner_offset(const std::vector<Vec2d>& shape, const double padding) {
Points shape_scaled;
shape_scaled.reserve(shape.size());
using std::begin, std::end, std::back_inserter, std::transform;
transform(begin(shape), end(shape), back_inserter(shape_scaled), [](const Vec2d& point){
return scaled(point);
});
return shrink({Polygon{shape_scaled}}, scaled(padding)).front();
}
}
std::optional<std::string> GCodeGenerator::get_helical_layer_change_gcode(
const coordf_t previous_layer_z,
@ -2692,23 +2620,20 @@ std::optional<std::string> GCodeGenerator::get_helical_layer_change_gcode(
const Point n_gon_start_point{this->last_pos()};
static GCode::Impl::Bed bed{
GCode::Impl::LayerChanges::Bed bed{
this->m_config.bed_shape.values,
circle_radius
circle_radius * 2
};
if (!bed.contains_within_padding(this->point_to_gcode(n_gon_start_point))) {
return std::nullopt;
}
const Point n_gon_centeroid{
n_gon_start_point
+ scaled(Vec2d{
(bed.centroid - unscaled(n_gon_start_point)).normalized()
* circle_radius
})
};
const Vec2crd n_gon_vector{scaled(Vec2d{
(bed.centroid - this->point_to_gcode(n_gon_start_point)).normalized() * circle_radius
})};
const Point n_gon_centeroid{n_gon_start_point + n_gon_vector};
const Polygon n_gon{GCode::Impl::generate_regular_polygon(
const Polygon n_gon{GCode::Impl::LayerChanges::generate_regular_polygon(
n_gon_centeroid,
n_gon_start_point,
n_gon_points_count
@ -2717,7 +2642,7 @@ std::optional<std::string> GCodeGenerator::get_helical_layer_change_gcode(
const double n_gon_circumference = unscaled(n_gon.length());
const double z_change{print_z - previous_layer_z};
Points3 helix{GCode::Impl::generate_elevated_travel(
Points3 helix{GCode::Impl::Travels::generate_elevated_travel(
n_gon.points,
{},
previous_layer_z,
@ -2732,8 +2657,11 @@ std::optional<std::string> GCodeGenerator::get_helical_layer_change_gcode(
}
// called by GCodeGenerator::process_layer()
std::string GCodeGenerator::change_layer(coordf_t previous_layer_z, coordf_t print_z)
{
std::string GCodeGenerator::change_layer(
coordf_t previous_layer_z,
coordf_t print_z,
const bool spiral_vase_enabled
) {
std::string gcode;
if (m_layer_count > 0)
// Increment a progress bar indicator.
@ -2744,15 +2672,17 @@ std::string GCodeGenerator::change_layer(coordf_t previous_layer_z, coordf_t pri
const std::string comment{"move to next layer (" + std::to_string(m_layer_index) + ")"};
bool helical_layer_change{
(!this->m_spiral_vase || !this->m_spiral_vase->is_enabled())
bool do_helical_layer_change{
!spiral_vase_enabled
&& print_z > previous_layer_z
&& EXTRUDER_CONFIG(retract_layer_change)
&& EXTRUDER_CONFIG(retract_length) > 0
&& EXTRUDER_CONFIG(travel_ramping_lift)
&& EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90
};
const std::optional<std::string> helix_gcode{
helical_layer_change ?
do_helical_layer_change ?
this->get_helical_layer_change_gcode(
m_config.z_offset.value + previous_layer_z,
m_config.z_offset.value + print_z,
@ -3274,219 +3204,6 @@ std::string GCodeGenerator::_extrude(
return gcode;
}
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
Points3 result;
result.reserve(xy_path.size() - 1);
for (const Point& point : xy_path.subspan(1)) {
result.emplace_back(point.x(), point.y(), scaled(elevation));
}
return result;
}
Vec2d place_at_segment(const Vec2d& current_point, const Vec2d& previous_point, const double distance) {
Vec2d direction = (current_point - previous_point).normalized();
return previous_point + direction * distance;
}
namespace GCode::Impl {
std::vector<DistancedPoint> slice_xy_path(tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances) {
assert(xy_path.size() >= 2);
std::vector<DistancedPoint> result;
result.reserve(xy_path.size() + sorted_distances.size());
double total_distance{0};
result.emplace_back(DistancedPoint{xy_path.front(), 0});
Point previous_point = result.front().point;
std::size_t offset{0};
for (const Point& point : xy_path.subspan(1)) {
Vec2d unscaled_point{unscaled(point)};
Vec2d unscaled_previous_point{unscaled(previous_point)};
const double current_segment_length = (unscaled_point - unscaled_previous_point).norm();
for (const double distance_to_add : sorted_distances.subspan(offset)) {
if (distance_to_add <= total_distance + current_segment_length) {
Point to_place = scaled(place_at_segment(
unscaled_point,
unscaled_previous_point,
distance_to_add - total_distance
));
if (to_place != previous_point && to_place != point) {
result.emplace_back(DistancedPoint{to_place, distance_to_add});
}
++offset;
} else {
break;
}
}
total_distance += current_segment_length;
result.emplace_back(DistancedPoint{point, total_distance});
previous_point = point;
}
return result;
}
struct ElevatedTravelParams {
double lift_height{};
double slope_end{};
};
struct ElevatedTravelFormula {
double operator()(double distance_from_start) const {
if (distance_from_start < this->params.slope_end) {
const double lift_percent = distance_from_start / this->params.slope_end;
return lift_percent * this->params.lift_height;
} else {
return this->params.lift_height;
}
}
ElevatedTravelParams params{};
};
Points3 generate_elevated_travel(
const tcb::span<const Point> xy_path,
const std::vector<double>& ensure_points_at_distances,
const double initial_elevation,
const std::function<double(double)>& elevation
) {
Points3 result{};
std::vector<DistancedPoint> extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances);
result.reserve(extended_xy_path.size());
for (const DistancedPoint& point : extended_xy_path) {
result.emplace_back(point.point.x(), point.point.y(), scaled(initial_elevation + elevation(point.distance_from_start)));
}
return result;
}
AABBTreeLines::LinesDistancer<Linef> get_expolygons_distancer(const ExPolygons& polygons) {
std::vector<Linef> lines;
for (const ExPolygon& polygon : polygons) {
for (const Line& line : polygon.lines()) {
lines.emplace_back(unscaled(line.a), unscaled(line.b));
}
}
return AABBTreeLines::LinesDistancer{std::move(lines)};
}
std::optional<double> get_first_crossed_line_distance(
tcb::span<const Line> xy_path,
const AABBTreeLines::LinesDistancer<Linef>& distancer
) {
assert(!xy_path.empty());
if (xy_path.empty()) {
return {};
}
double traversed_distance = 0;
for (const Line& line : xy_path) {
const Linef unscaled_line = {unscaled(line.a), unscaled(line.b)};
auto intersections = distancer.intersections_with_line<true>(unscaled_line);
if (!intersections.empty()) {
const Vec2d intersection = intersections.front().first;
const double distance = traversed_distance + (unscaled_line.a - intersection).norm();
if (distance > EPSILON) {
return distance;
} else if (intersections.size() >= 2) { // Edge case
const Vec2d second_intersection = intersections[1].first;
return traversed_distance + (unscaled_line.a - second_intersection).norm();
}
}
traversed_distance += (unscaled_line.a - unscaled_line.b).norm();
}
return {};
}
std::optional<double> get_obstacle_adjusted_slope_end(
const Lines& xy_path,
const std::optional<AABBTreeLines::LinesDistancer<Linef>>& previous_layer_distancer
) {
if (!previous_layer_distancer) {
return std::nullopt;
}
std::optional<double> first_obstacle_distance = get_first_crossed_line_distance(
xy_path, *previous_layer_distancer
);
if (!first_obstacle_distance) {
return std::nullopt;
}
return *first_obstacle_distance;
}
ElevatedTravelParams get_elevated_traval_params(
const Lines& xy_path,
const FullPrintConfig& config,
const unsigned extruder_id,
const std::optional<AABBTreeLines::LinesDistancer<Linef>>& previous_layer_distancer
) {
ElevatedTravelParams elevation_params{};
if (!config.travel_ramping_lift.get_at(extruder_id)) {
elevation_params.slope_end = 0;
elevation_params.lift_height = config.retract_lift.get_at(extruder_id);
return elevation_params;
}
elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id);
const double slope_deg = config.travel_slope.get_at(extruder_id);
if (slope_deg >= 90 || slope_deg <= 0) {
elevation_params.slope_end = 0;
} else {
const double slope_rad = slope_deg * (M_PI / 180); // rad
elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad);
}
std::optional<double> obstacle_adjusted_slope_end{get_obstacle_adjusted_slope_end(
xy_path,
previous_layer_distancer
)};
if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) {
elevation_params.slope_end = *obstacle_adjusted_slope_end;
}
return elevation_params;
}
Points3 generate_travel_to_extrusion(
const Polyline& xy_path,
const FullPrintConfig& config,
const unsigned extruder_id,
const double initial_elevation,
const std::optional<AABBTreeLines::LinesDistancer<Linef>>& previous_layer_distancer
) {
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
const double lower_limit = config.retract_lift_above.get_at(extruder_id);
if (
(lower_limit > 0 && initial_elevation < lower_limit)
|| (upper_limit > 0 && initial_elevation > upper_limit)
) {
return generate_flat_travel(xy_path.points, initial_elevation);
}
ElevatedTravelParams elevation_params{get_elevated_traval_params(
xy_path.lines(),
config,
extruder_id,
previous_layer_distancer
)};
const std::vector<double> ensure_points_at_distances{elevation_params.slope_end};
Points3 result{generate_elevated_travel(
xy_path.points,
ensure_points_at_distances,
initial_elevation,
ElevatedTravelFormula{elevation_params}
)};
result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation));
return result;
}
}
std::string GCodeGenerator::generate_travel_gcode(
const Points3& travel,
const std::string& comment
@ -3602,8 +3319,6 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st
const Point start_point = this->last_pos();
using namespace GCode::Impl;
// check whether a straight travel move would need retraction
bool could_be_wipe_disabled {false};
@ -3641,13 +3356,14 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st
const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value;
const Points3 travel = (
can_be_flat ?
generate_flat_travel(xy_path.points, initial_elevation) :
GCode::Impl::generate_travel_to_extrusion(
GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) :
GCode::Impl::Travels::generate_travel_to_extrusion(
xy_path,
this->m_config,
extruder_id,
initial_elevation,
this->m_previous_layer_distancer
this->m_previous_layer_distancer,
scaled(m_origin)
)
);

View File

@ -89,112 +89,6 @@ struct LayerResult {
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
};
namespace GCode::Impl {
struct DistancedPoint {
Point point;
double distance_from_start;
};
/**
* @brief Takes a path described as a list of points and adds points to it.
*
* @param xy_path A list of points describing a path in xy.
* @param sorted_distances A sorted list of distances along the path.
* @return Sliced path.
*
* The algorithm travels along the path segments and adds points to
* the segments in such a way that the points have specified distances
* from the xy_path start. **Any distances over the xy_path end will
* be simply ignored.**
*
* Example usage - simplified for clarity:
* @code
* std::vector<double> distances{0.5, 1.5};
* std::vector<Points> xy_path{{0, 0}, {1, 0}};
* // produces
* {{0, 0}, {0, 0.5}, {1, 0}}
* // notice that 1.5 is omitted
* @endcode
*/
std::vector<DistancedPoint> slice_xy_path(tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances);
/**
* @brief Take xy_path and genrate a travel acording to elevation.
*
* @param xy_path A list of points describing a path in xy.
* @param ensure_points_at_distances See slice_xy_path sorted_distances.
* @param elevation A function taking current distance in mm as input and returning elevation in mm as output.
*
* **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are in
* scaled coordinates.
*/
Points3 generate_elevated_travel(
const tcb::span<const Point> xy_path,
const std::vector<double>& ensure_points_at_distances,
const double initial_elevation,
const std::function<double(double)>& elevation
);
/**
* @brief Takes a list o polygons and builds a AABBTree over all unscaled lines.
*
* @param polygons A list of polygons.
* @return AABB Tree over all lines of the polygons.
*
* Unscales the lines in the process!
*/
AABBTreeLines::LinesDistancer<Linef> get_expolygons_distancer(const ExPolygons& polygons);
/**
* @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start.
*
* @param xy_path A path in 2D.
* @param distancer AABB Tree over lines.
* @return Distance to the first intersection if there is one.
*
* **Ignores intersection with xy_path starting point.**
*/
std::optional<double> get_first_crossed_line_distance(
tcb::span<const Line> xy_path,
const AABBTreeLines::LinesDistancer<Linef>& distancer
);
/**
* Generates a regular polygon - all angles are the same (e.g. typical hexagon).
*
* @param centroid Central point.
* @param start_point The polygon point are ordered. This is the first point.
* @param points_count Amount of nodes of the polygon (e.g. 6 for haxagon).
*
* Distance between centroid and start point sets the scale of the polygon.
*/
Polygon generate_regular_polygon(
const Point& centroid,
const Point& start_point,
const unsigned points_count
);
class Bed {
private:
Polygon inner_offset;
static Polygon get_inner_offset(const std::vector<Vec2d>& shape, const double padding);
public:
/**
* Bed shape with inner padding.
*/
Bed(const std::vector<Vec2d>& shape, const double padding);
Vec2d centroid;
/**
* Returns true if the point is within the bed shape including inner padding.
*/
bool contains_within_padding(const Vec2d& point) const;
};
}
class GCodeGenerator {
public:
@ -366,7 +260,11 @@ private:
const coordf_t print_z,
const std::string& comment
);
std::string change_layer(coordf_t previous_layer_z, coordf_t print_z);
std::string change_layer(
coordf_t previous_layer_z,
coordf_t print_z,
const bool spiral_vase_enabled
);
std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,

View File

@ -572,8 +572,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
{
m_parser.apply_config(config);
m_binarizer.set_enabled(config.gcode_binary);
m_result.is_binary_file = config.gcode_binary;
m_binarizer.set_enabled(config.binary_gcode);
m_result.is_binary_file = config.binary_gcode;
m_producer = EProducer::PrusaSlicer;
m_flavor = config.gcode_flavor;
@ -3695,6 +3695,8 @@ void GCodeProcessor::post_process()
filament_total_cost += filament_cost[id];
}
double total_g_wipe_tower = m_print->print_statistics().total_wipe_tower_filament_weight;
if (m_binarizer.is_enabled()) {
// update print metadata
auto stringify = [](const std::vector<double>& values) {
@ -3715,11 +3717,13 @@ void GCodeProcessor::post_process()
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentUsedG, stringify({ filament_total_g }));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentCost, stringify({ filament_total_cost }));
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentUsedWipeTower, stringify({ total_g_wipe_tower }));
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedMm, stringify(filament_mm)); // duplicated into print metadata
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedG, stringify(filament_g)); // duplicated into print metadata
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost)); // duplicated into print metadata
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedCm3, stringify(filament_cm3)); // duplicated into print metadata
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentUsedWipeTower, stringify({ total_g_wipe_tower })); // duplicated into print metadata
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = m_time_processor.machines[i];

View File

@ -81,7 +81,8 @@ void LabelObjects::init(const Print& print)
if (object_has_more_instances)
name += " (Instance " + std::to_string(instance_id) + ")";
if (m_flavor == gcfKlipper) {
const std::string banned = "-. \r\n\v\t\f";
// Disallow Klipper special chars, common illegal filename chars, etc.
const std::string banned = "\b\t\n\v\f\r \"#%&\'*-./:;<>\\";
std::replace_if(name.begin(), name.end(), [&banned](char c) { return banned.find(c) != std::string::npos; }, '_');
}
}
@ -111,7 +112,7 @@ std::string LabelObjects::all_objects_header() const
for (const auto& [print_instance, label] : label_data_sorted) {
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) {
char buffer[64];
out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name;
out += "EXCLUDE_OBJECT_DEFINE NAME='" + label.name + "'";
Polygon outline = instance_outline(print_instance);
assert(! outline.empty());
outline.douglas_peucker(50000.f);
@ -154,7 +155,7 @@ std::string LabelObjects::start_object(const PrintInstance& print_instance, Incl
}
out += "\n";
} else if (m_flavor == gcfKlipper)
out += "EXCLUDE_OBJECT_START NAME=" + label.name + "\n";
out += "EXCLUDE_OBJECT_START NAME='" + label.name + "'\n";
else {
// Not supported by / implemented for the other firmware flavors.
}
@ -178,7 +179,7 @@ std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware)
out += std::string("M486 S-1\n");
else if (m_flavor ==gcfKlipper)
out += "EXCLUDE_OBJECT_END NAME=" + label.name + "\n";
out += "EXCLUDE_OBJECT_END NAME='" + label.name + "'\n";
else {
// Not supported by / implemented for the other firmware flavors.
}

View File

@ -0,0 +1,53 @@
#include "LayerChanges.hpp"
#include "libslic3r/ClipperUtils.hpp"
namespace Slic3r::GCode::Impl::LayerChanges {
Polygon generate_regular_polygon(
const Point &centroid, const Point &start_point, const unsigned points_count
) {
Points points;
points.reserve(points_count);
const double part_angle{2 * M_PI / points_count};
for (unsigned i = 0; i < points_count; ++i) {
const double current_angle{i * part_angle};
points.emplace_back(scaled(std::cos(current_angle)), scaled(std::sin(current_angle)));
}
Polygon regular_polygon{points};
const Vec2d current_vector{unscaled(regular_polygon.points.front())};
const Vec2d expected_vector{unscaled(start_point) - unscaled(centroid)};
const double current_scale = current_vector.norm();
const double expected_scale = expected_vector.norm();
regular_polygon.scale(expected_scale / current_scale);
regular_polygon.rotate(angle(current_vector, expected_vector));
regular_polygon.translate(centroid);
return regular_polygon;
}
Bed::Bed(const std::vector<Vec2d> &shape, const double padding)
: inner_offset(get_inner_offset(shape, padding)), centroid(unscaled(inner_offset.centroid())) {}
bool Bed::contains_within_padding(const Vec2d &point) const {
return inner_offset.contains(scaled(point));
}
Polygon Bed::get_inner_offset(const std::vector<Vec2d> &shape, const double padding) {
Points shape_scaled;
shape_scaled.reserve(shape.size());
using std::begin, std::end, std::back_inserter, std::transform;
transform(begin(shape), end(shape), back_inserter(shape_scaled), [](const Vec2d &point) {
return scaled(point);
});
const Polygons inner_offset{shrink({Polygon{shape_scaled}}, scaled(padding))};
if (inner_offset.empty()) {
return Polygon{};
}
return inner_offset.front();
}
} // namespace Slic3r::GCode::Impl::LayerChanges

View File

@ -0,0 +1,52 @@
/**
* @file
* @brief Utility functions for layer change gcode generation.
*/
#ifndef slic3r_GCode_LayerChanges_hpp_
#define slic3r_GCode_LayerChanges_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
namespace Slic3r::GCode::Impl::LayerChanges {
/**
* Generates a regular polygon - all angles are the same (e.g. typical hexagon).
*
* @param centroid Central point.
* @param start_point The polygon point are ordered. This is the first point.
* @param points_count Amount of nodes of the polygon (e.g. 6 for haxagon).
*
* Distance between centroid and start point sets the scale of the polygon.
*/
Polygon generate_regular_polygon(
const Point &centroid, const Point &start_point, const unsigned points_count
);
/**
* @brief A representation of the bed shape with inner padding.
*
* Its purpose is to facilitate the bed boundary checking.
*/
class Bed
{
private:
Polygon inner_offset;
static Polygon get_inner_offset(const std::vector<Vec2d> &shape, const double padding);
public:
/**
* Bed shape with inner padding.
*/
Bed(const std::vector<Vec2d> &shape, const double padding);
Vec2d centroid;
/**
* Returns true if the point is within the bed shape including inner padding.
*/
bool contains_within_padding(const Vec2d &point) const;
};
} // namespace Slic3r::GCode::Impl::LayerChanges
#endif // slic3r_GCode_LayerChanges_hpp_

View File

@ -28,10 +28,6 @@ public:
m_enabled = en;
}
bool is_enabled() const {
return m_enabled;
}
std::string process_layer(const std::string &gcode);
private:

View File

@ -0,0 +1,205 @@
#include "Travels.hpp"
namespace Slic3r::GCode::Impl::Travels {
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation) {
Points3 result;
result.reserve(xy_path.size() - 1);
for (const Point &point : xy_path.subspan(1)) {
result.emplace_back(point.x(), point.y(), scaled(elevation));
}
return result;
}
Vec2d place_at_segment(
const Vec2d &current_point, const Vec2d &previous_point, const double distance
) {
Vec2d direction = (current_point - previous_point).normalized();
return previous_point + direction * distance;
}
std::vector<DistancedPoint> slice_xy_path(
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
) {
assert(xy_path.size() >= 2);
std::vector<DistancedPoint> result;
result.reserve(xy_path.size() + sorted_distances.size());
double total_distance{0};
result.emplace_back(DistancedPoint{xy_path.front(), 0});
Point previous_point = result.front().point;
std::size_t offset{0};
for (const Point &point : xy_path.subspan(1)) {
Vec2d unscaled_point{unscaled(point)};
Vec2d unscaled_previous_point{unscaled(previous_point)};
const double current_segment_length = (unscaled_point - unscaled_previous_point).norm();
for (const double distance_to_add : sorted_distances.subspan(offset)) {
if (distance_to_add <= total_distance + current_segment_length) {
Point to_place = scaled(place_at_segment(
unscaled_point, unscaled_previous_point, distance_to_add - total_distance
));
if (to_place != previous_point && to_place != point) {
result.emplace_back(DistancedPoint{to_place, distance_to_add});
}
++offset;
} else {
break;
}
}
total_distance += current_segment_length;
result.emplace_back(DistancedPoint{point, total_distance});
previous_point = point;
}
return result;
}
struct ElevatedTravelParams
{
double lift_height{};
double slope_end{};
};
struct ElevatedTravelFormula
{
double operator()(double distance_from_start) const {
if (distance_from_start < this->params.slope_end) {
const double lift_percent = distance_from_start / this->params.slope_end;
return lift_percent * this->params.lift_height;
} else {
return this->params.lift_height;
}
}
ElevatedTravelParams params{};
};
Points3 generate_elevated_travel(
const tcb::span<const Point> xy_path,
const std::vector<double> &ensure_points_at_distances,
const double initial_elevation,
const std::function<double(double)> &elevation
) {
Points3 result{};
std::vector<DistancedPoint> extended_xy_path = slice_xy_path(xy_path, ensure_points_at_distances);
result.reserve(extended_xy_path.size());
for (const DistancedPoint &point : extended_xy_path) {
result.emplace_back(
point.point.x(), point.point.y(),
scaled(initial_elevation + elevation(point.distance_from_start))
);
}
return result;
}
std::optional<double> get_first_crossed_line_distance(
tcb::span<const Line> xy_path, const AABBTreeLines::LinesDistancer<Linef> &distancer
) {
assert(!xy_path.empty());
if (xy_path.empty()) {
return {};
}
double traversed_distance = 0;
for (const Line &line : xy_path) {
const Linef unscaled_line = {unscaled(line.a), unscaled(line.b)};
auto intersections = distancer.intersections_with_line<true>(unscaled_line);
if (!intersections.empty()) {
const Vec2d intersection = intersections.front().first;
const double distance = traversed_distance + (unscaled_line.a - intersection).norm();
if (distance > EPSILON) {
return distance;
} else if (intersections.size() >= 2) { // Edge case
const Vec2d second_intersection = intersections[1].first;
return traversed_distance + (unscaled_line.a - second_intersection).norm();
}
}
traversed_distance += (unscaled_line.a - unscaled_line.b).norm();
}
return {};
}
std::optional<double> get_obstacle_adjusted_slope_end(
const Lines &xy_path,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer
) {
if (!previous_layer_distancer) {
return std::nullopt;
}
std::optional<double> first_obstacle_distance =
get_first_crossed_line_distance(xy_path, *previous_layer_distancer);
if (!first_obstacle_distance) {
return std::nullopt;
}
return *first_obstacle_distance;
}
ElevatedTravelParams get_elevated_traval_params(
const Lines &xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer
) {
ElevatedTravelParams elevation_params{};
if (!config.travel_ramping_lift.get_at(extruder_id)) {
elevation_params.slope_end = 0;
elevation_params.lift_height = config.retract_lift.get_at(extruder_id);
return elevation_params;
}
elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id);
const double slope_deg = config.travel_slope.get_at(extruder_id);
if (slope_deg >= 90 || slope_deg <= 0) {
elevation_params.slope_end = 0;
} else {
const double slope_rad = slope_deg * (M_PI / 180); // rad
elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad);
}
std::optional<double> obstacle_adjusted_slope_end{
get_obstacle_adjusted_slope_end(xy_path, previous_layer_distancer)};
if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) {
elevation_params.slope_end = *obstacle_adjusted_slope_end;
}
return elevation_params;
}
Points3 generate_travel_to_extrusion(
const Polyline &xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const double initial_elevation,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer,
const Point &xy_path_coord_origin
) {
const double upper_limit = config.retract_lift_below.get_at(extruder_id);
const double lower_limit = config.retract_lift_above.get_at(extruder_id);
if ((lower_limit > 0 && initial_elevation < lower_limit) ||
(upper_limit > 0 && initial_elevation > upper_limit)) {
return generate_flat_travel(xy_path.points, initial_elevation);
}
Lines global_xy_path;
for (const Line &line : xy_path.lines()) {
global_xy_path.emplace_back(line.a + xy_path_coord_origin, line.b + xy_path_coord_origin);
}
ElevatedTravelParams elevation_params{
get_elevated_traval_params(global_xy_path, config, extruder_id, previous_layer_distancer)};
const std::vector<double> ensure_points_at_distances{elevation_params.slope_end};
Points3 result{generate_elevated_travel(
xy_path.points, ensure_points_at_distances, initial_elevation,
ElevatedTravelFormula{elevation_params}
)};
result.emplace_back(xy_path.back().x(), xy_path.back().y(), scaled(initial_elevation));
return result;
}
} // namespace Slic3r::GCode::Impl::Travels

View File

@ -0,0 +1,101 @@
/**
* @file
* @brief Utility functions for travel gcode generation.
*/
#ifndef slic3r_GCode_Travels_hpp_
#define slic3r_GCode_Travels_hpp_
#include <vector>
#include <tcbspan/span.hpp>
#include <functional>
#include <optional>
#include "libslic3r/Line.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/AABBTreeLines.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r::GCode::Impl::Travels {
struct DistancedPoint
{
Point point;
double distance_from_start;
};
/**
* @brief Takes a path described as a list of points and adds points to it.
*
* @param xy_path A list of points describing a path in xy.
* @param sorted_distances A sorted list of distances along the path.
* @return Sliced path.
*
* The algorithm travels along the path segments and adds points to
* the segments in such a way that the points have specified distances
* from the xy_path start. **Any distances over the xy_path end will
* be simply ignored.**
*
* Example usage - simplified for clarity:
* @code
* std::vector<double> distances{0.5, 1.5};
* std::vector<Points> xy_path{{0, 0}, {1, 0}};
* // produces
* {{0, 0}, {0, 0.5}, {1, 0}}
* // notice that 1.5 is omitted
* @endcode
*/
std::vector<DistancedPoint> slice_xy_path(
tcb::span<const Point> xy_path, tcb::span<const double> sorted_distances
);
/**
* @brief Simply return the xy_path with z coord set to elevation.
*/
Points3 generate_flat_travel(tcb::span<const Point> xy_path, const float elevation);
/**
* @brief Take xy_path and genrate a travel acording to elevation.
*
* @param xy_path A list of points describing a path in xy.
* @param ensure_points_at_distances See slice_xy_path sorted_distances.
* @param elevation A function taking current distance in mm as input and returning elevation in mm
* as output.
*
* **Be aweare** that the elevation function operates in mm, while xy_path and returned travel are
* in scaled coordinates.
*/
Points3 generate_elevated_travel(
const tcb::span<const Point> xy_path,
const std::vector<double> &ensure_points_at_distances,
const double initial_elevation,
const std::function<double(double)> &elevation
);
/**
* @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start.
*
* @param xy_path A path in 2D.
* @param distancer AABB Tree over lines.
* @return Distance to the first intersection if there is one.
*
* **Ignores intersection with xy_path starting point.**
*/
std::optional<double> get_first_crossed_line_distance(
tcb::span<const Line> xy_path, const AABBTreeLines::LinesDistancer<Linef> &distancer
);
/**
* @brief Extract parameters and decide wheather the travel can be elevated.
* Then generate the whole travel 3D path - elevated if possible.
*/
Points3 generate_travel_to_extrusion(
const Polyline &xy_path,
const FullPrintConfig &config,
const unsigned extruder_id,
const double initial_elevation,
const std::optional<AABBTreeLines::LinesDistancer<Linef>> &previous_layer_distancer,
const Point &xy_path_coord_origin
);
} // namespace Slic3r::GCode::Impl::Travels
#endif // slic3r_GCode_Travels_hpp_

View File

@ -462,7 +462,7 @@ static std::vector<std::string> s_Preset_print_options {
"support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter", "support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall",
"support_tree_top_rate", "support_tree_branch_distance", "support_tree_tip_diameter",
"dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "gcode_binary", "perimeter_extruder",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "gcode_substitutions", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
@ -503,7 +503,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
static std::vector<std::string> s_Preset_printer_options {
"printer_technology", "autoemit_temperature_commands",
"bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances",
"bed_shape", "bed_custom_texture", "bed_custom_model", "binary_gcode", "z_offset", "gcode_flavor", "use_relative_e_distances",
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
//FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset.
"host_type", "print_host", "printhost_apikey", "printhost_cafile",

View File

@ -86,6 +86,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"bed_temperature",
"before_layer_gcode",
"between_objects_gcode",
"binary_gcode",
"bridge_acceleration",
"bridge_fan_speed",
"enable_dynamic_fan_speeds",
@ -139,7 +140,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"perimeter_acceleration",
"post_process",
"gcode_substitutions",
"gcode_binary",
"printer_notes",
"travel_ramping_lift",
"travel_initial_part_length",
@ -1598,7 +1598,15 @@ std::string Print::output_filename(const std::string &filename_base) const
DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders();
config.set_key_value("num_extruders", new ConfigOptionInt((int)m_config.nozzle_diameter.size()));
config.set_key_value("default_output_extension", new ConfigOptionString(".gcode"));
return this->PrintBase::output_filename(m_config.output_filename_format.value, ".gcode", filename_base, &config);
// Handle output_filename_format. There is a hack related to binary G-codes: gcode / bgcode substitution.
std::string output_filename_format = m_config.output_filename_format.value;
if (m_config.binary_gcode && boost::iends_with(output_filename_format, ".gcode"))
output_filename_format.insert(output_filename_format.end()-5, 'b');
if (! m_config.binary_gcode && boost::iends_with(output_filename_format, ".bgcode"))
output_filename_format.erase(output_filename_format.end()-6);
return this->PrintBase::output_filename(output_filename_format, ".gcode", filename_base, &config);
}
const std::string PrintStatistics::FilamentUsedG = "filament used [g]";
@ -1621,6 +1629,11 @@ const std::string PrintStatistics::TotalFilamentCost = "total filament
const std::string PrintStatistics::TotalFilamentCostMask = "; total filament cost =";
const std::string PrintStatistics::TotalFilamentCostValueMask = "; total filament cost = %.2lf\n";
const std::string PrintStatistics::TotalFilamentUsedWipeTower = "total filament used for wipe tower [g]";
const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; total filament used for wipe tower [g] = %.2lf\n";
DynamicConfig PrintStatistics::config() const
{
DynamicConfig config;

View File

@ -505,6 +505,7 @@ struct PrintStatistics
double total_weight;
double total_wipe_tower_cost;
double total_wipe_tower_filament;
double total_wipe_tower_filament_weight;
std::vector<unsigned int> printing_extruders;
unsigned int initial_extruder_id;
std::string initial_filament_type;
@ -526,6 +527,7 @@ struct PrintStatistics
total_weight = 0.;
total_wipe_tower_cost = 0.;
total_wipe_tower_filament = 0.;
total_wipe_tower_filament_weight = 0.;
initial_extruder_id = 0;
initial_filament_type.clear();
printing_filament_types.clear();
@ -547,6 +549,8 @@ struct PrintStatistics
static const std::string TotalFilamentCost;
static const std::string TotalFilamentCostMask;
static const std::string TotalFilamentCostValueMask;
static const std::string TotalFilamentUsedWipeTower;
static const std::string TotalFilamentUsedWipeTowerValueMask;
};
using PrintObjectPtrs = std::vector<PrintObject*>;

View File

@ -1510,12 +1510,6 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionStrings());
def = this->add("gcode_binary", coBool);
def->label = L("Export as binary G-code");
def->tooltip = L("Export G-code in binary format.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(0));
def = this->add("high_current_on_filament_swap", coBool);
def->label = L("High extruder current on filament swap");
def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange"
@ -1791,6 +1785,13 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("binary_gcode", coBool);
def->label = L("Supports binary G-code");
def->tooltip = L("Enable, if the firmware supports binary G-code format (bgcode). "
"To generate .bgcode files, make sure you have binary G-code enabled in Configuration->Preferences->Other.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("machine_limits_usage", coEnum);
def->label = L("How to apply limits");
def->full_label = L("Purpose of Machine Limits");
@ -4320,6 +4321,7 @@ static std::set<std::string> PrintConfigDef_ignore = {
"ensure_vertical_shell_thickness",
// Disabled in 2.6.0-alpha6, this option is problematic
"infill_only_where_needed",
"gcode_binary" // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode).
};
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)

View File

@ -703,6 +703,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, autoemit_temperature_commands))
((ConfigOptionString, before_layer_gcode))
((ConfigOptionString, between_objects_gcode))
((ConfigOptionBool, binary_gcode))
((ConfigOptionFloats, deretract_speed))
((ConfigOptionString, end_gcode))
((ConfigOptionStrings, end_filament_gcode))
@ -739,7 +740,6 @@ PRINT_CONFIG_CLASS_DEFINE(
// i - case insensitive
// w - whole word
((ConfigOptionStrings, gcode_substitutions))
((ConfigOptionBool, gcode_binary))
((ConfigOptionString, layer_gcode))
((ConfigOptionFloat, max_print_speed))
((ConfigOptionFloat, max_volumetric_speed))

View File

@ -154,26 +154,69 @@ void SliceConnection::print_info(const std::string &tag) const
std::cout << "covariance: " << covariance << std::endl;
}
Integrals::Integrals (const Polygons& polygons) {
Integrals::Integrals(const Polygon &polygon)
{
if (polygon.points.size() < 3) {
assert(false && "Polygon is expected to have non-zero area!");
*this = Integrals{};
return;
}
Vec2f p0 = unscaled(polygon.first_point()).cast<float>();
for (size_t i = 2; i < polygon.points.size(); i++) {
Vec2f p1 = unscaled(polygon.points[i - 1]).cast<float>();
Vec2f p2 = unscaled(polygon.points[i]).cast<float>();
float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f;
auto [area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2);
this->area += sign * area;
this->x_i += sign * first_moment_of_area;
this->x_i_squared += sign * second_moment_area;
this->xy += sign * second_moment_of_area_covariance;
}
}
Integrals::Integrals(const Polygons &polygons)
{
for (const Polygon &polygon : polygons) {
Vec2f p0 = unscaled(polygon.first_point()).cast<float>();
for (size_t i = 2; i < polygon.points.size(); i++) {
Vec2f p1 = unscaled(polygon.points[i - 1]).cast<float>();
Vec2f p2 = unscaled(polygon.points[i]).cast<float>();
*this = *this + Integrals{polygon};
}
}
float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f;
Integrals::Integrals(const Polylines& polylines, const std::vector<float>& widths) {
assert(polylines.size() == widths.size());
for (size_t i = 0; i < polylines.size(); ++i) {
Lines polyline{polylines[i].lines()};
float width{widths[i]};
for (const Line& line : polyline) {
Vec2f line_direction = unscaled(line.vector()).cast<float>();
Vec2f normal{line_direction.y(), -line_direction.x()};
normal.normalize();
auto [area, first_moment_of_area, second_moment_area,
second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2);
Vec2f line_a = unscaled(line.a).cast<float>();
Vec2f line_b = unscaled(line.b).cast<float>();
Vec2crd a = scaled(Vec2f{line_a + normal * width/2});
Vec2crd b = scaled(Vec2f{line_b + normal * width/2});
Vec2crd c = scaled(Vec2f{line_b - normal * width/2});
Vec2crd d = scaled(Vec2f{line_a - normal * width/2});
this->area += sign * area;
this->x_i += sign * first_moment_of_area;
this->x_i_squared += sign * second_moment_area;
this->xy += sign * second_moment_of_area_covariance;
const Polygon ractangle({a, b, c, d});
Integrals integrals{ractangle};
*this = *this + integrals;
}
}
}
Integrals::Integrals(float area, Vec2f x_i, Vec2f x_i_squared, float xy)
: area(area), x_i(std::move(x_i)), x_i_squared(std::move(x_i_squared)), xy(xy)
{}
Integrals operator+(const Integrals &a, const Integrals &b)
{
return Integrals{a.area + b.area, a.x_i + b.x_i, a.x_i_squared + b.x_i_squared, a.xy + b.xy};
}
SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer)
{
@ -473,18 +516,50 @@ ObjectPart::ObjectPart(
continue;
}
const Polygons polygons{collection->polygons_covered_by_width()};
for (const ExtrusionEntity* entity: collection->flatten()) {
Polylines polylines;
std::vector<float> widths;
const Integrals integrals{polygons};
const float volume = integrals.area * layer_height;
this->volume += volume;
this->volume_centroid_accumulator += to_3d(integrals.x_i, center_z * integrals.area) / integrals.area * volume;
if (
const auto* path = dynamic_cast<const ExtrusionPath*>(entity);
path != nullptr
) {
polylines.push_back(path->as_polyline());
widths.push_back(path->width());
} else if (
const auto* loop = dynamic_cast<const ExtrusionLoop*>(entity);
loop != nullptr
) {
for (const ExtrusionPath& path : loop->paths) {
polylines.push_back(path.as_polyline());
widths.push_back(path.width());
}
} else if (
const auto* multi_path = dynamic_cast<const ExtrusionMultiPath*>(entity);
multi_path != nullptr
) {
for (const ExtrusionPath& path : multi_path->paths) {
polylines.push_back(path.as_polyline());
widths.push_back(path.width());
}
} else {
throw std::runtime_error(
"Failed to construct object part from extrusions!"
" Unknown extrusion type."
);
}
if (this->connected_to_bed) {
this->sticking_area += integrals.area;
this->sticking_centroid_accumulator += to_3d(integrals.x_i, bottom_z * integrals.area);
this->sticking_second_moment_of_area_accumulator += integrals.x_i_squared;
this->sticking_second_moment_of_area_covariance_accumulator += integrals.xy;
const Integrals integrals{polylines, widths};
const float volume = integrals.area * layer_height;
this->volume += volume;
this->volume_centroid_accumulator += to_3d(integrals.x_i, center_z * integrals.area) / integrals.area * volume;
if (this->connected_to_bed) {
this->sticking_area += integrals.area;
this->sticking_centroid_accumulator += to_3d(integrals.x_i, bottom_z * integrals.area);
this->sticking_second_moment_of_area_accumulator += integrals.x_i_squared;
this->sticking_second_moment_of_area_covariance_accumulator += integrals.xy;
}
}
}

View File

@ -151,16 +151,28 @@ class Integrals{
* @param polygons List of polygons specifing the domain.
*/
explicit Integrals(const Polygons& polygons);
explicit Integrals(const Polygon& polygon);
/**
* Construct integral x_i int x_i^2 (i=1,2), xy and integral 1 (area) over
* a set of rectangles defined by a "thick" polyline.
*/
explicit Integrals(const Polylines& polylines, const std::vector<float>& widths);
// TODO refactor and delete the default constructor
Integrals() = default;
Integrals(float area, Vec2f x_i, Vec2f x_i_squared, float xy);
float area{};
Vec2f x_i{Vec2f::Zero()};
Vec2f x_i_squared{Vec2f::Zero()};
float xy{};
private:
void add(const Integrals& other);
};
Integrals operator+(const Integrals& a, const Integrals& b);
float compute_second_moment(
const Integrals& integrals,
const Vec2f& axis_direction

View File

@ -373,6 +373,8 @@ std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
std::pair<double, double> ret;
auto& [near_z, far_z] = ret;
set_distance(DefaultDistance);
// box in eye space
const BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
near_z = -eye_box.max.z();
@ -396,15 +398,6 @@ std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
near_z += delta;
far_z += delta;
}
// The following is commented out because it causes flickering of the 3D scene GUI
// when the bounding box of the scene gets large enough
// We need to introduce some smarter code to move the camera back and forth in such case
// else if (near_z > 2.0 * FrustrumMinNearZ && m_distance > DefaultDistance) {
// float delta = m_distance - DefaultDistance;
// set_distance(DefaultDistance);
// near_z -= delta;
// far_z -= delta;
// }
return ret;
}

View File

@ -205,9 +205,11 @@ wxString Field::get_tooltip_text(const wxString& default_string)
opt_id += "]";
}
bool newline_after_name = boost::iends_with(opt_id, "_gcode") && opt_id != "binary_gcode";
return from_u8(m_opt.tooltip) + "\n" + _L("default value") + "\t: " +
(boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string +
(boost::iends_with(opt_id, "_gcode") ? "" : "\n") +
(newline_after_name ? "\n" : "") + default_string +
(newline_after_name ? "" : "\n") +
_L("parameter name") + "\t: " + opt_id;
}

View File

@ -90,7 +90,7 @@ static const Slic3r::ColorRGBA ERROR_BG_DARK_COLOR = { 0.478f, 0.192f, 0.039f
static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f, 1.0f };
// Number of floats
static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB
static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB
#define SHOW_IMGUI_DEMO_WINDOW
#ifdef SHOW_IMGUI_DEMO_WINDOW
@ -2019,7 +2019,7 @@ void GLCanvas3D::render()
}
#if ENABLE_BINARIZED_GCODE_DEBUG_WINDOW
if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() != ptSLA && fff_print()->config().gcode_binary)
if (wxGetApp().plater()->is_view3D_shown() && current_printer_technology() != ptSLA && fff_print()->config().binary_gcode)
show_binary_gcode_debug_window();
#endif // ENABLE_BINARIZED_GCODE_DEBUG_WINDOW
@ -6847,14 +6847,22 @@ void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume)
_3DScene::extrusionentity_to_verts(print->brim(), print_zs[i], Point(0, 0), init_data);
_3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), init_data);
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
if (init_data.vertices_size_bytes() > MAX_VERTEX_BUFFER_SIZE) {
if (init_data.vertices_size_bytes() >= MAX_VERTEX_BUFFER_SIZE) {
volume->model.init_from(std::move(init_data));
GLVolume &vol = *volume;
volume = m_volumes.new_toolpath_volume(vol.color);
volume->is_outside = !contains(build_volume, volume->model);
volume = m_volumes.new_toolpath_volume(volume->color);
init_data = GLModel::Geometry();
}
}
volume->model.init_from(std::move(init_data));
volume->is_outside = !contains(build_volume, volume->model);
init_data = GLModel::Geometry();
if (init_data.is_empty()) {
delete volume;
m_volumes.volumes.pop_back();
}
else {
volume->model.init_from(std::move(init_data));
volume->is_outside = !contains(build_volume, volume->model);
}
}
void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector<std::string>& str_tool_colors, const std::vector<CustomGCode::Item>& color_print_values)

View File

@ -8,6 +8,7 @@
#include <GL/glew.h>
#include <algorithm>
#include <wx/progdlg.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"

View File

@ -220,7 +220,7 @@ bool GLGizmoSVG::create_volume(std::string_view svg_file, const Vec2d &mouse_pos
}
bool GLGizmoSVG::is_svg(const ModelVolume &volume) {
return volume.emboss_shape.has_value();
return volume.emboss_shape.has_value() && volume.emboss_shape->svg_file.has_value();
}
bool GLGizmoSVG::is_svg_object(const ModelVolume &volume) {
@ -1174,6 +1174,7 @@ void GLGizmoSVG::set_volume_by_selection()
// calculate scale for height and depth inside of scaled object instance
calculate_scale(); // must be before calculation of tesselation
// checking that exist is inside of function "is_svg"
EmbossShape &es = *volume->emboss_shape;
EmbossShape::SvgFile &svg_file = *es.svg_file;
if (svg_file.image == nullptr) {

View File

@ -251,6 +251,16 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, const t_link_cli
create(m_content, 84);
}
HtmlCapableRichMessageDialog::HtmlCapableRichMessageDialog(wxWindow *parent,
const wxString &msg,
const wxString &caption,
long style,
const std::function<void(const std::string &)> &on_link_clicked)
: RichMessageDialogBase(parent, HtmlContent{msg, false, true, on_link_clicked}, caption, style)
{}
// WarningDialog
WarningDialog::WarningDialog(wxWindow *parent,
@ -276,28 +286,40 @@ MessageDialog::MessageDialog(wxWindow* parent,
add_msg_content(this, content_sizer, HtmlContent{ get_wraped_wxString(message) });
finalize();
}
#endif
// RichMessageDialog
// RichMessageDialogBase
RichMessageDialog::RichMessageDialog(wxWindow* parent,
RichMessageDialogBase::RichMessageDialogBase(wxWindow* parent,
const wxString& message,
const wxString& caption/* = wxEmptyString*/,
long style/* = wxOK*/)
: RichMessageDialogBase(parent, HtmlContent{get_wraped_wxString(message)}, caption, style)
{}
RichMessageDialogBase::RichMessageDialogBase(wxWindow* parent, const HtmlContent& content, const wxString& caption, long style)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, style)
{
add_msg_content(this, content_sizer, HtmlContent{ get_wraped_wxString(message) });
m_content = content; // We need a copy for the on_link_clicked lambda.
add_msg_content(this, content_sizer, m_content);
#ifdef _WIN32 // See comment in the header where m_checkBox is defined.
m_checkBox = new ::CheckBox(this, m_checkBoxText);
#else
m_checkBox = new wxCheckBox(this, wxID_ANY, m_checkBoxText);
#endif
wxGetApp().UpdateDarkUI(m_checkBox);
m_checkBox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_checkBoxValue = m_checkBox->GetValue(); });
btn_sizer->Insert(0, m_checkBox, wxALIGN_CENTER_VERTICAL);
finalize();
finalize();
}
int RichMessageDialog::ShowModal()
int RichMessageDialogBase::ShowModal()
{
if (m_checkBoxText.IsEmpty())
m_checkBox->Hide();
@ -309,7 +331,7 @@ int RichMessageDialog::ShowModal()
return wxDialog::ShowModal();
}
#endif
// InfoDialog

View File

@ -108,58 +108,33 @@ public:
wxString get_wraped_wxString(const wxString& text_in, size_t line_len = 80);
#ifdef _WIN32
// Generic static line, used intead of wxStaticLine
class StaticLine: public wxTextCtrl
{
public:
StaticLine( wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxLI_HORIZONTAL,
const wxString& name = wxString::FromAscii(wxTextCtrlNameStr))
: wxTextCtrl(parent, id, wxEmptyString, pos, size!=wxDefaultSize ? size : (style == wxLI_HORIZONTAL ? wxSize(10, 1) : wxSize(1, 10)), wxSIMPLE_BORDER, wxDefaultValidator, name)
{
this->Enable(false);
}
~StaticLine() {}
};
// Generic message dialog, used intead of wxMessageDialog
class MessageDialog : public MsgDialog
{
public:
// NOTE! Don't change a signature of contsrucor. It have to be tha same as for wxMessageDialog
MessageDialog( wxWindow *parent,
const wxString& message,
const wxString& caption = wxEmptyString,
long style = wxOK);
MessageDialog(MessageDialog&&) = delete;
MessageDialog(const MessageDialog&) = delete;
MessageDialog &operator=(MessageDialog&&) = delete;
MessageDialog &operator=(const MessageDialog&) = delete;
virtual ~MessageDialog() = default;
};
// Generic rich message dialog, used intead of wxRichMessageDialog
class RichMessageDialog : public MsgDialog
class RichMessageDialogBase : public MsgDialog
{
// Using CheckBox causes some weird sizer-related issues on Linux and macOS. To get around the problem before
// we find a better fix, we will fallback to wxCheckBox in this dialog. This makes little difference for most dialogs,
// We currently only use this class as a base for HtmlCapableRichMessageDialog on Linux and macOS. The normal
// RichMessageDialog is just an alias for wxRichMessageDialog on these platforms.
#ifdef _WIN32
CheckBox* m_checkBox{ nullptr };
#else
wxCheckBox* m_checkBox{ nullptr };
#endif
wxString m_checkBoxText;
bool m_checkBoxValue{ false };
public:
// NOTE! Don't change a signature of contsrucor. It have to be tha same as for wxRichMessageDialog
RichMessageDialog( wxWindow *parent,
const wxString& message,
const wxString& caption = wxEmptyString,
long style = wxOK);
RichMessageDialog(RichMessageDialog&&) = delete;
RichMessageDialog(const RichMessageDialog&) = delete;
RichMessageDialog &operator=(RichMessageDialog&&) = delete;
RichMessageDialog &operator=(const RichMessageDialog&) = delete;
virtual ~RichMessageDialog() = default;
RichMessageDialogBase(wxWindow* parent, const wxString& message, const wxString& caption = wxEmptyString, long style = wxOK);
RichMessageDialogBase(wxWindow* parent, const HtmlContent& content, const wxString& caption = wxEmptyString, long style = wxOK);
RichMessageDialogBase(RichMessageDialogBase&&) = delete;
RichMessageDialogBase(const RichMessageDialogBase&) = delete;
RichMessageDialogBase &operator=(RichMessageDialogBase&&) = delete;
RichMessageDialogBase &operator=(const RichMessageDialogBase&) = delete;
virtual ~RichMessageDialogBase() = default;
int ShowModal() override;
@ -280,7 +255,47 @@ private:
m_ok,
m_cancel,
m_help;
HtmlContent m_content;
};
#ifdef _WIN32
// Generic static line, used intead of wxStaticLine
class StaticLine: public wxTextCtrl
{
public:
StaticLine( wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxLI_HORIZONTAL,
const wxString& name = wxString::FromAscii(wxTextCtrlNameStr))
: wxTextCtrl(parent, id, wxEmptyString, pos, size!=wxDefaultSize ? size : (style == wxLI_HORIZONTAL ? wxSize(10, 1) : wxSize(1, 10)), wxSIMPLE_BORDER, wxDefaultValidator, name)
{
this->Enable(false);
}
~StaticLine() {}
};
// Generic message dialog, used intead of wxMessageDialog
class MessageDialog : public MsgDialog
{
public:
// NOTE! Don't change a signature of contsrucor. It have to be tha same as for wxMessageDialog
MessageDialog(wxWindow *parent,
const wxString& message,
const wxString& caption = wxEmptyString,
long style = wxOK);
MessageDialog(MessageDialog &&) = delete;
MessageDialog(const MessageDialog &) = delete;
MessageDialog &operator=(MessageDialog &&) = delete;
MessageDialog &operator=(const MessageDialog &) = delete;
virtual ~MessageDialog() = default;
};
using RichMessageDialog = RichMessageDialogBase;
#else
// just a wrapper for wxStaticLine to use the same code on all platforms
class StaticLine : public wxStaticLine
@ -322,6 +337,16 @@ public:
};
#endif
class HtmlCapableRichMessageDialog : public RichMessageDialogBase
{
public:
HtmlCapableRichMessageDialog(wxWindow *parent, const wxString &msg, const wxString& caption, long style, const std::function<void(const std::string &)> &on_link_clicked);
~HtmlCapableRichMessageDialog() {}
private:
HtmlContent m_content;
};
// Generic info dialog, used for displaying exceptions
class InfoDialog : public MsgDialog
{

View File

@ -1833,6 +1833,11 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// bitmap of enum UpdateBackgroundProcessReturnState
unsigned int return_state = 0;
// Get the config ready. The binary gcode flag depends on Preferences, which the backend has no access to.
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config();
if (full_config.has("binary_gcode")) // needed for SLA
full_config.set("binary_gcode", bool(full_config.opt_bool("binary_gcode") & wxGetApp().app_config->get_bool("use_binary_gcode_when_supported")));
// If the update_background_process() was not called by the timer, kill the timer,
// so the update_restart_background_process() will not be called again in vain.
background_process_timer.Stop();
@ -1840,7 +1845,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
update_print_volume_state();
// Apply new config to the possibly running background task.
bool was_running = background_process.running();
Print::ApplyStatus invalidated = background_process.apply(q->model(), wxGetApp().preset_bundle->full_config());
Print::ApplyStatus invalidated = background_process.apply(q->model(), full_config);
// Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
if (view3D->is_layers_editing_enabled())
@ -5089,14 +5094,14 @@ static wxString check_binary_vs_ascii_gcode_extension(PrinterTechnology pt, cons
if (binary_output && ascii_extension) {
// TRN The placeholder %1% is the file extension the user has selected.
err_out = format_wxstr(_L("Cannot save binary G-code with %1% extension.\n\n"
"Use <a href=%2%>a different extension</a> or disable <a href=%3%>binary G-code export</a> "
"in Print Settings."), ext, "output_filename_format;print", "gcode_binary;print");
"Use a different extension or disable <a href=%2%>binary G-code export</a> "
"in Printer Settings."), ext, "binary_gcode;printer");
}
if (!binary_output && binary_extension) {
// TRN The placeholder %1% is the file extension the user has selected.
err_out = format_wxstr(_L("Cannot save ASCII G-code with %1% extension.\n\n"
"Use <a href=%2%>a different extension</a> or enable <a href=%3%>binary G-code export</a> "
"in Print Settings."), ext, "output_filename_format;print", "gcode_binary;print");
"Use a different extension or enable <a href=%2%>binary G-code export</a> "
"in Printer Settings."), ext, "binary_gcode;printer");
}
}
return err_out;
@ -5120,9 +5125,15 @@ static void alert_when_exporting_binary_gcode(bool binary_output, const std::str
const std::string option_key = "dont_warn_about_firmware_version_when_exporting_binary_gcode";
if (app_config->get(option_key) != "1") {
RichMessageDialog dialog(parent, _L("You are exporting binary G-code for a Prusa printer. Please, make sure that your printer "
"is running firmware version 5.1.0-alpha2 or later. Older firmwares are not able to handle binary G-codes."),
_L("Exporting binary G-code"), wxICON_WARNING | wxOK);
const wxString url = "https://prusa.io/binary-gcode";
HtmlCapableRichMessageDialog dialog(parent,
format_wxstr(_L("You are exporting binary G-code for a Prusa printer. "
"Binary G-code enables significantly faster uploads. "
"Ensure that your printer is running firmware version 5.1.0 or newer, as older versions do not support binary G-codes.\n\n"
"To learn more about binary G-code, visit <a href=%1%>%1%</a>."),
url),
_L("Warning"), wxOK,
[&url](const std::string&) { wxGetApp().open_browser_with_warning_dialog(url); });
dialog.ShowCheckBox(_L("Don't show again"));
if (dialog.ShowModal() == wxID_OK && dialog.IsCheckBoxChecked())
app_config->set(option_key, "1");
@ -5196,7 +5207,11 @@ void Plater::export_gcode(bool prefer_removable)
_L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\"";
return true;
}
err_out = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("gcode_binary"));
if (this->printer_technology() == ptFFF) {
bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode");
bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported");
err_out = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, supports_binary && uses_binary);
}
return !err_out.IsEmpty();
};
@ -5205,8 +5220,10 @@ void Plater::export_gcode(bool prefer_removable)
const t_link_clicked on_link_clicked = [](const std::string& key) -> void { wxGetApp().jump_to_option(key); };
ErrorDialog(this, error_str, on_link_clicked).ShowModal();
output_path.clear();
} else {
alert_when_exporting_binary_gcode(wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("gcode_binary"),
} else if (printer_technology() == ptFFF) {
bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode");
bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported");
alert_when_exporting_binary_gcode(supports_binary && uses_binary,
wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_notes"));
}
}
@ -5732,18 +5749,21 @@ void Plater::send_gcode()
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names);
if (dlg.ShowModal() == wxID_OK) {
{
if (printer_technology() == ptFFF) {
const std::string ext = boost::algorithm::to_lower_copy(dlg.filename().extension().string());
const bool binary_output = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("gcode_binary");
const bool binary_output = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode") &&
wxGetApp().app_config->get_bool("use_binary_gcode_when_supported");
const wxString error_str = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, binary_output);
if (! error_str.IsEmpty()) {
ErrorDialog(this, error_str, t_kill_focus([](const std::string& key) -> void { wxGetApp().jump_to_option(key); })).ShowModal();
return;
}
}
alert_when_exporting_binary_gcode(wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("gcode_binary"),
wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_notes"));
bool supports_binary = wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("binary_gcode");
bool uses_binary = wxGetApp().app_config->get_bool("use_binary_gcode_when_supported");
alert_when_exporting_binary_gcode(supports_binary && uses_binary,
wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_string("printer_notes"));
}
upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.post_action = dlg.post_action();

View File

@ -630,6 +630,11 @@ void PreferencesDialog::build()
};
append_bool_option(m_optgroup_other, "use_binary_gcode_when_supported", L("Use binary G-code when the printer supports it"),
L("If the 'Supports binary G-code' option is enabled in Printer Settings, "
"checking this option will result in the export of G-code in binary format."),
app_config->get_bool("use_binary_gcode_when_supported"));
append_bool_option(m_optgroup_other, "suppress_hyperlinks",
L("Suppress to open hyperlink in browser"),
L("If enabled, PrusaSlicer will not open a hyperlinks in your browser."),

View File

@ -928,9 +928,9 @@ const std::pair<Vec3d, double> Selection::get_bounding_sphere() const
const TriangleMesh* hull = volume.convex_hull();
const indexed_triangle_set& its = (hull != nullptr) ?
hull->its : m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh().its;
const Transform3d& matrix = volume.world_matrix();
const Transform3f matrix = volume.world_matrix().cast<float>();
for (const Vec3f& v : its.vertices) {
const Vec3d vv = matrix * v.cast<double>();
const Vec3f vv = matrix * v;
points.push_back(Point(vv.x(), vv.y(), vv.z()));
}
}

View File

@ -1711,34 +1711,6 @@ void TabPrint::build()
Option option = optgroup->get_option("output_filename_format");
option.opt.full_width = true;
optgroup->append_single_option_line(option);
optgroup->append_single_option_line("gcode_binary");
optgroup->on_change = [this](const t_config_option_key& opt_key, boost::any value)
{
if (opt_key == "gcode_binary") {
const bool is_binary = m_config->opt_bool("gcode_binary");
std::string output_filename_format = m_config->opt_string("output_filename_format");
bool modified = false;
if (is_binary && boost::iends_with(output_filename_format, ".gcode")) {
output_filename_format = output_filename_format.substr(0, output_filename_format.length() - 5) + "bgcode";
modified = true;
}
else if (!is_binary && boost::iends_with(output_filename_format, ".bgcode")) {
output_filename_format = output_filename_format.substr(0, output_filename_format.length() - 6) + "gcode";
modified = true;
}
if (modified) {
DynamicPrintConfig new_conf = *m_config;
auto off_option = static_cast<ConfigOptionString*>(m_config->option("output_filename_format")->clone());
off_option->value = output_filename_format;
new_conf.set_key_value("output_filename_format", off_option);
load_config(new_conf);
}
}
update_dirty();
update();
};
optgroup = page->new_optgroup(L("Other"));
@ -2733,6 +2705,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("silent_mode");
optgroup->append_single_option_line("remaining_times");
optgroup->append_single_option_line("binary_gcode");
optgroup->on_change = [this](t_config_option_key opt_key, boost::any value) {
wxTheApp->CallAfter([this, opt_key, value]() {

View File

@ -308,16 +308,12 @@ void ComboBox::keyDown(wxKeyEvent& event)
{
int key_code = event.GetKeyCode();
switch (key_code) {
case WXK_RETURN:
case WXK_RETURN: {
if (drop_down) {
drop.DismissAndNotify();
wxCommandEvent e(wxEVT_COMBOBOX);
e.SetEventObject(this);
e.SetId(GetId());
e.SetInt(GetSelection());
GetEventHandler()->ProcessEvent(e);
} else if (drop.HasDismissLongTime()) {
sendComboBoxEvent();
}
else if (drop.HasDismissLongTime()) {
drop.autoPosition();
drop_down = true;
drop.Popup();
@ -325,14 +321,19 @@ void ComboBox::keyDown(wxKeyEvent& event)
GetEventHandler()->ProcessEvent(e);
}
break;
}
case WXK_UP: {
if (GetSelection() > 0)
SetSelection(GetSelection() - 1);
if (!drop.IsShown())
sendComboBoxEvent();
break;
}
case WXK_DOWN: {
if (GetSelection() + 1 < int(texts.size()))
SetSelection(GetSelection() + 1);
if (!drop.IsShown())
sendComboBoxEvent();
break;
}
case WXK_LEFT: {

View File

@ -62,7 +62,7 @@ use Slic3r::Test qw(_eq);
{
my $config = Slic3r::Config->new;
$config->set('fill_density', 0); # just for making the test faster
$config->set('gcode_binary', 0);
$config->set('binary_gcode', 0);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
my @z = ();

View File

@ -13,6 +13,8 @@ add_executable(${_TEST_NAME}_tests
test_flow.cpp
test_gaps.cpp
test_gcode.cpp
test_gcode_travels.cpp
test_gcode_layer_changes.cpp
test_gcodefindreplace.cpp
test_gcodewriter.cpp
test_model.cpp

View File

@ -5,7 +5,6 @@
#include "libslic3r/GCode.hpp"
using namespace Slic3r;
using namespace Slic3r::GCode::Impl;
SCENARIO("Origin manipulation", "[GCode]") {
Slic3r::GCodeGenerator gcodegen;
@ -21,220 +20,3 @@ SCENARIO("Origin manipulation", "[GCode]") {
}
}
}
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
bool match(const Points& points) const override {
if (points.size() != expected.size()) {
return false;
}
for (auto i = 0u; i < points.size(); ++i) {
const Point& point = points[i];
const Point& expected_point = this->expected[i];
if (
std::abs(point.x() - expected_point.x()) > this->tolerance
|| std::abs(point.y() - expected_point.y()) > this->tolerance
) {
return false;
}
}
return true;
}
std::string describe() const override {
std::stringstream ss;
ss << std::endl;
for (const Point& point : expected) {
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
}
ss << "With tolerance: " << this->tolerance;
return "Equals " + ss.str();
}
private:
Points expected;
unsigned tolerance;
};
Points get_points(const std::vector<DistancedPoint>& result) {
Points result_points;
std::transform(
result.begin(),
result.end(),
std::back_inserter(result_points),
[](const DistancedPoint& point){
return point.point;
}
);
return result_points;
}
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
std::vector<double> result_distances;
std::transform(
result.begin(),
result.end(),
std::back_inserter(result_distances),
[](const DistancedPoint& point){
return point.distance_from_start;
}
);
return result_distances;
}
TEST_CASE("Place points at distances - expected use", "[GCode]") {
std::vector<Point> line{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{2, 1}),
scaled(Vec2f{2, 2})
};
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
scaled(Vec2f{0, 0}),
scaled(Vec2f{0.2, 0}),
scaled(Vec2f{0.5, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{1.5, 0.5}),
scaled(Vec2f{2, 1}),
scaled(Vec2f{2, 1.5}),
scaled(Vec2f{2, 2})
}, 5));
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
}));
}
TEST_CASE("Place points at distances - edge case", "[GCode]") {
std::vector<Point> line{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{2, 0})
};
std::vector<double> distances{0, 1, 1.5, 2};
Points result{get_points(slice_xy_path(line, distances))};
CHECK(result == Points{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{1.5, 0}),
scaled(Vec2f{2, 0})
});
}
TEST_CASE("Generate elevated travel", "[GCode]") {
std::vector<Point> xy_path{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
};
std::vector<double> ensure_points_at_distances{0.2, 0.5};
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
CHECK(result == Points3{
scaled(Vec3f{0, 0, 3.0}),
scaled(Vec3f{0.2, 0, 3.2}),
scaled(Vec3f{0.5, 0, 3.5}),
scaled(Vec3f{1, 0, 4.0})
});
}
TEST_CASE("Get first crossed line distance", "[GCode]") {
// A 2x2 square at 0, 0, with 1x1 square hole in its center.
ExPolygon square_with_hole{
{
scaled(Vec2f{-1, -1}),
scaled(Vec2f{1, -1}),
scaled(Vec2f{1, 1}),
scaled(Vec2f{-1, 1})
},
{
scaled(Vec2f{-0.5, -0.5}),
scaled(Vec2f{0.5, -0.5}),
scaled(Vec2f{0.5, 0.5}),
scaled(Vec2f{-0.5, 0.5})
}
};
// A 2x2 square above the previous square at (0, 3).
ExPolygon square_above{
{
scaled(Vec2f{-1, 2}),
scaled(Vec2f{1, 2}),
scaled(Vec2f{1, 4}),
scaled(Vec2f{-1, 4})
}
};
// Bottom-up travel intersecting the squares.
Lines travel{Polyline{
scaled(Vec2f{0, -2}),
scaled(Vec2f{0, -0.7}),
scaled(Vec2f{0, 0}),
scaled(Vec2f{0, 1}),
scaled(Vec2f{0, 1.3}),
scaled(Vec2f{0, 2.4}),
scaled(Vec2f{0, 4.5}),
scaled(Vec2f{0, 5}),
}.lines()};
// Try different cases by skipping lines in the travel.
AABBTreeLines::LinesDistancer<Linef> distancer = get_expolygons_distancer({square_with_hole, square_above});
CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer));
}
TEST_CASE("Generate regular polygon", "[GCode]") {
const unsigned points_count{32};
const Point centroid{scaled(Vec2d{5, -2})};
const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)};
const Point oposite_point{centroid * 2};
REQUIRE(result.size() == 32);
CHECK(result[16].x() == Approx(oposite_point.x()));
CHECK(result[16].y() == Approx(oposite_point.y()));
std::vector<double> angles;
angles.reserve(points_count);
for (unsigned index = 0; index < points_count; index++) {
const unsigned previous_index{index == 0 ? points_count - 1 : index - 1};
const unsigned next_index{index == points_count - 1 ? 0 : index + 1};
const Point previous_point = result.points[previous_index];
const Point current_point = result.points[index];
const Point next_point = result.points[next_index];
angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point}));
}
std::vector<double> expected;
angles.reserve(points_count);
std::generate_n(std::back_inserter(expected), points_count, [&](){
return angles.front();
});
CHECK_THAT(angles, Catch::Matchers::Approx(expected));
}
TEST_CASE("Square bed with padding", "[GCode]") {
const Bed bed{
{
Vec2d{0, 0},
Vec2d{100, 0},
Vec2d{100, 100},
Vec2d{0, 100}
},
10.0
};
CHECK(bed.centroid.x() == 50);
CHECK(bed.centroid.y() == 50);
CHECK(bed.contains_within_padding(Vec2d{10, 10}));
CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10}));
}

View File

@ -0,0 +1,55 @@
#include <catch2/catch.hpp>
#include <libslic3r/GCode/LayerChanges.hpp>
using namespace Slic3r;
using namespace Slic3r::GCode::Impl::LayerChanges;
TEST_CASE("Generate regular polygon", "[GCode]") {
const unsigned points_count{32};
const Point centroid{scaled(Vec2d{5, -2})};
const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)};
const Point oposite_point{centroid * 2};
REQUIRE(result.size() == 32);
CHECK(result[16].x() == Approx(oposite_point.x()));
CHECK(result[16].y() == Approx(oposite_point.y()));
std::vector<double> angles;
angles.reserve(points_count);
for (unsigned index = 0; index < points_count; index++) {
const unsigned previous_index{index == 0 ? points_count - 1 : index - 1};
const unsigned next_index{index == points_count - 1 ? 0 : index + 1};
const Point previous_point = result.points[previous_index];
const Point current_point = result.points[index];
const Point next_point = result.points[next_index];
angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point}));
}
std::vector<double> expected;
angles.reserve(points_count);
std::generate_n(std::back_inserter(expected), points_count, [&](){
return angles.front();
});
CHECK_THAT(angles, Catch::Matchers::Approx(expected));
}
TEST_CASE("Square bed with padding", "[GCode]") {
const Bed bed{
{
Vec2d{0, 0},
Vec2d{100, 0},
Vec2d{100, 100},
Vec2d{0, 100}
},
10.0
};
CHECK(bed.centroid.x() == 50);
CHECK(bed.centroid.y() == 50);
CHECK(bed.contains_within_padding(Vec2d{10, 10}));
CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10}));
}

View File

@ -0,0 +1,181 @@
#include <catch2/catch.hpp>
#include <libslic3r/GCode/Travels.hpp>
#include <libslic3r/ExPolygon.hpp>
using namespace Slic3r;
using namespace Slic3r::GCode::Impl::Travels;
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
bool match(const Points& points) const override {
if (points.size() != expected.size()) {
return false;
}
for (auto i = 0u; i < points.size(); ++i) {
const Point& point = points[i];
const Point& expected_point = this->expected[i];
if (
std::abs(point.x() - expected_point.x()) > this->tolerance
|| std::abs(point.y() - expected_point.y()) > this->tolerance
) {
return false;
}
}
return true;
}
std::string describe() const override {
std::stringstream ss;
ss << std::endl;
for (const Point& point : expected) {
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
}
ss << "With tolerance: " << this->tolerance;
return "Equals " + ss.str();
}
private:
Points expected;
unsigned tolerance;
};
Points get_points(const std::vector<DistancedPoint>& result) {
Points result_points;
std::transform(
result.begin(),
result.end(),
std::back_inserter(result_points),
[](const DistancedPoint& point){
return point.point;
}
);
return result_points;
}
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
std::vector<double> result_distances;
std::transform(
result.begin(),
result.end(),
std::back_inserter(result_distances),
[](const DistancedPoint& point){
return point.distance_from_start;
}
);
return result_distances;
}
TEST_CASE("Place points at distances - expected use", "[GCode]") {
std::vector<Point> line{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{2, 1}),
scaled(Vec2f{2, 2})
};
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
scaled(Vec2f{0, 0}),
scaled(Vec2f{0.2, 0}),
scaled(Vec2f{0.5, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{1.5, 0.5}),
scaled(Vec2f{2, 1}),
scaled(Vec2f{2, 1.5}),
scaled(Vec2f{2, 2})
}, 5));
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
}));
}
TEST_CASE("Place points at distances - edge case", "[GCode]") {
std::vector<Point> line{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{2, 0})
};
std::vector<double> distances{0, 1, 1.5, 2};
Points result{get_points(slice_xy_path(line, distances))};
CHECK(result == Points{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{1.5, 0}),
scaled(Vec2f{2, 0})
});
}
TEST_CASE("Generate elevated travel", "[GCode]") {
std::vector<Point> xy_path{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
};
std::vector<double> ensure_points_at_distances{0.2, 0.5};
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
CHECK(result == Points3{
scaled(Vec3f{0, 0, 3.0}),
scaled(Vec3f{0.2, 0, 3.2}),
scaled(Vec3f{0.5, 0, 3.5}),
scaled(Vec3f{1, 0, 4.0})
});
}
TEST_CASE("Get first crossed line distance", "[GCode]") {
// A 2x2 square at 0, 0, with 1x1 square hole in its center.
ExPolygon square_with_hole{
{
scaled(Vec2f{-1, -1}),
scaled(Vec2f{1, -1}),
scaled(Vec2f{1, 1}),
scaled(Vec2f{-1, 1})
},
{
scaled(Vec2f{-0.5, -0.5}),
scaled(Vec2f{0.5, -0.5}),
scaled(Vec2f{0.5, 0.5}),
scaled(Vec2f{-0.5, 0.5})
}
};
// A 2x2 square above the previous square at (0, 3).
ExPolygon square_above{
{
scaled(Vec2f{-1, 2}),
scaled(Vec2f{1, 2}),
scaled(Vec2f{1, 4}),
scaled(Vec2f{-1, 4})
}
};
// Bottom-up travel intersecting the squares.
Lines travel{Polyline{
scaled(Vec2f{0, -2}),
scaled(Vec2f{0, -0.7}),
scaled(Vec2f{0, 0}),
scaled(Vec2f{0, 1}),
scaled(Vec2f{0, 1.3}),
scaled(Vec2f{0, 2.4}),
scaled(Vec2f{0, 4.5}),
scaled(Vec2f{0, 5}),
}.lines()};
std::vector<Linef> lines;
for (const ExPolygon& polygon : {square_with_hole, square_above}) {
for (const Line& line : polygon.lines()) {
lines.emplace_back(unscale(line.a), unscale(line.b));
}
}
// Try different cases by skipping lines in the travel.
AABBTreeLines::LinesDistancer<Linef> distancer{std::move(lines)};
CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer));
}

View File

@ -5,23 +5,44 @@
using namespace Slic3r;
using namespace SupportSpotsGenerator;
namespace Rectangle {
const float width = 10;
const float height = 20;
const Polygon polygon = {
scaled(Vec2f{-width / 2, -height / 2}),
scaled(Vec2f{width / 2, -height / 2}),
scaled(Vec2f{width / 2, height / 2}),
scaled(Vec2f{-width / 2, height / 2})
};
}
TEST_CASE("Numerical integral calculation compared with exact solution.", "[SupportSpotsGenerator]") {
const float width = 10;
const float height = 20;
const Polygon polygon = {
scaled(Vec2f{-width / 2, -height / 2}),
scaled(Vec2f{width / 2, -height / 2}),
scaled(Vec2f{width / 2, height / 2}),
scaled(Vec2f{-width / 2, height / 2})
};
TEST_CASE("Numerical integral over polygon calculation compared with exact solution.", "[SupportSpotsGenerator]") {
const Integrals integrals{Rectangle::polygon};
const Integrals integrals{{polygon}};
CHECK(integrals.area == Approx(width * height));
CHECK(integrals.area == Approx(Rectangle::width * Rectangle::height));
CHECK(integrals.x_i.x() == Approx(0));
CHECK(integrals.x_i.y() == Approx(0));
CHECK(integrals.x_i_squared.x() == Approx(std::pow(width, 3) * height / 12));
CHECK(integrals.x_i_squared.y() == Approx(width * std::pow(height, 3) / 12));
CHECK(integrals.x_i_squared.x() == Approx(std::pow(Rectangle::width, 3) * Rectangle::height / 12));
CHECK(integrals.x_i_squared.y() == Approx(Rectangle::width * std::pow(Rectangle::height, 3) / 12));
}
TEST_CASE("Integrals over multiple polygons", "[SupportSpotsGenerator]") {
const Integrals integrals{{Rectangle::polygon, Rectangle::polygon}};
CHECK(integrals.area == Approx(2 * Rectangle::width * Rectangle::height));
}
TEST_CASE("Numerical integral over line calculation compared with exact solution.", "[SupportSpotsGenerator]") {
const float length = 10;
const float width = 20;
const Polyline polyline{scaled(Vec2f{-length/2.0f, 0.0f}), scaled(Vec2f{length/2.0f, 0.0f})};
const Integrals integrals{{polyline}, {width}};
CHECK(integrals.area == Approx(length * width));
CHECK(integrals.x_i.x() == Approx(0));
CHECK(integrals.x_i.y() == Approx(0));
CHECK(integrals.x_i_squared.x() == Approx(std::pow(length, 3) * width / 12));
CHECK(integrals.x_i_squared.y() == Approx(length * std::pow(width, 3) / 12));
}
TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
@ -37,7 +58,7 @@ TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
scaled(Vec2f{0, height})
};
const Integrals integrals{{polygon}};
const Integrals integrals{polygon};
const Vec2f x_axis{1, 0};
const float x_axis_moment = compute_second_moment(integrals, x_axis);
@ -55,7 +76,6 @@ TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
}
TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
Polygon polygon = {
scaled(Vec2f{6.362284076172198, 138.9674202217155}),
scaled(Vec2f{97.48779843751677, 106.08136606617076}),
@ -69,7 +89,7 @@ TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
scaled(Vec2f{77.56229640885199, 189.33057746591336})
};
Integrals integrals{{polygon}};
Integrals integrals{polygon};
// Meassured counterclockwise from (1, 0)
const float angle = 1.432f;
@ -130,7 +150,7 @@ TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart using extrusion col
std::nullopt
};
Integrals expected{{expected_polygon}};
Integrals expected{expected_polygon};
CHECK(part.connected_to_bed == true);
Vec3f volume_centroid{part.volume_centroid_accumulator / part.volume};