Merge branch 'master' into fs_svg_SPE-1517

This commit is contained in:
Filip Sykala - NTB T15p 2023-09-26 13:34:28 +02:00
commit 5da14c491e
19 changed files with 694 additions and 156 deletions

View File

@ -1,4 +1,5 @@
min_slic3r_version = 2.6.2-alpha0 min_slic3r_version = 2.6.2-alpha0
1.11.0-alpha4 Updated compatible printer conditions for specific filament profiles.
1.11.0-alpha3 Added new print profiles for Prusa MINI Input Shaper (Alpha). Updated MK4 IS profiles. 1.11.0-alpha3 Added new print profiles for Prusa MINI Input Shaper (Alpha). Updated MK4 IS profiles.
1.11.0-alpha2 Added MK3.9 and Prusa MINI Input Shaper (alpha). Enabled binary g-code, arc fitting and QOI/PNG for MINI and MINI IS. 1.11.0-alpha2 Added MK3.9 and Prusa MINI Input Shaper (alpha). Enabled binary g-code, arc fitting and QOI/PNG for MINI and MINI IS.
1.11.0-alpha1 Updated ramming parameters. Updated start-gcode for XL Multi-Tool. Updated output filename format. 1.11.0-alpha1 Updated ramming parameters. Updated start-gcode for XL Multi-Tool. Updated output filename format.

View File

@ -5,7 +5,7 @@
name = Prusa Research name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs. # Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded. # This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 1.11.0-alpha3 config_version = 1.11.0-alpha4
# Where to get the updates from? # Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/
changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -6399,18 +6399,18 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MK2S
[filament:Generic PETG @PG] [filament:Generic PETG @PG]
inherits = Generic PETG; *PETPG* inherits = Generic PETG; *PETPG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
[filament:Generic PETG @PG 0.6] [filament:Generic PETG @PG 0.6]
inherits = Generic PETG; *PET06PG* inherits = Generic PETG; *PET06PG*
filament_max_volumetric_speed = 17 filament_max_volumetric_speed = 17
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6
[filament:Generic PETG @PG 0.8] [filament:Generic PETG @PG 0.8]
inherits = Generic PETG; *PET08PG* inherits = Generic PETG; *PET08PG*
first_layer_temperature = 240 first_layer_temperature = 240
temperature = 250 temperature = 250
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8
[filament:Generic PETG @XL] [filament:Generic PETG @XL]
inherits = Generic PETG @PG; *PETXL* inherits = Generic PETG @PG; *PETXL*
@ -7563,18 +7563,18 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_notes!~/.*PG
[filament:Generic PLA @PG] [filament:Generic PLA @PG]
inherits = Generic PLA; *PLAPG* inherits = Generic PLA; *PLAPG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
[filament:Generic PLA @PG 0.6] [filament:Generic PLA @PG 0.6]
inherits = Generic PLA; *PLA06PG* inherits = Generic PLA; *PLA06PG*
filament_max_volumetric_speed = 15 filament_max_volumetric_speed = 15
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6
[filament:Generic PLA @PG 0.8] [filament:Generic PLA @PG 0.8]
inherits = Generic PLA; *PLA08PG* inherits = Generic PLA; *PLA08PG*
first_layer_temperature = 220 first_layer_temperature = 220
temperature = 220 temperature = 220
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8
[filament:Generic PLA @XL] [filament:Generic PLA @XL]
inherits = Generic PLA @PG; *PLAXL* inherits = Generic PLA @PG; *PLAXL*
@ -10376,16 +10376,16 @@ compatible_printers_condition = nozzle_diameter[0]!=0.6 and nozzle_diameter[0]!=
[filament:Prusa PETG @PG] [filament:Prusa PETG @PG]
inherits = Prusa PETG; *PETPG* inherits = Prusa PETG; *PETPG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
[filament:Prusa PETG @PG 0.6] [filament:Prusa PETG @PG 0.6]
inherits = Prusa PETG; *PET06PG* inherits = Prusa PETG; *PET06PG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6
[filament:Prusa PETG @PG 0.8] [filament:Prusa PETG @PG 0.8]
inherits = Prusa PETG; *PET08PG* inherits = Prusa PETG; *PET08PG*
temperature = 250 temperature = 250
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8
[filament:Prusa PETG @XL] [filament:Prusa PETG @XL]
inherits = Prusa PETG @PG; *PETXL* inherits = Prusa PETG @PG; *PETXL*
@ -10469,17 +10469,17 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=
[filament:Prusament PETG @PG] [filament:Prusament PETG @PG]
inherits = Prusament PETG; *PETPG* inherits = Prusament PETG; *PETPG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
[filament:Prusament PETG @PG 0.6] [filament:Prusament PETG @PG 0.6]
inherits = Prusament PETG; *PET06PG* inherits = Prusament PETG; *PET06PG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6
[filament:Prusament PETG @PG 0.8] [filament:Prusament PETG @PG 0.8]
inherits = Prusament PETG; *PET08PG* inherits = Prusament PETG; *PET08PG*
first_layer_temperature = 250 first_layer_temperature = 250
temperature = 260 temperature = 260
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8
[filament:Prusament PETG @XL] [filament:Prusament PETG @XL]
inherits = Prusament PETG @PG; *PETXL* inherits = Prusament PETG @PG; *PETXL*
@ -10790,16 +10790,16 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_notes!~/.*PG
[filament:Prusa PLA @PG] [filament:Prusa PLA @PG]
inherits = Prusa PLA; *PLAPG* inherits = Prusa PLA; *PLAPG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
[filament:Prusa PLA @PG 0.6] [filament:Prusa PLA @PG 0.6]
inherits = Prusa PLA; *PLA06PG* inherits = Prusa PLA; *PLA06PG*
filament_max_volumetric_speed = 15.5 filament_max_volumetric_speed = 15.5
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6
[filament:Prusa PLA @PG 0.8] [filament:Prusa PLA @PG 0.8]
inherits = Prusa PLA; *PLA08PG* inherits = Prusa PLA; *PLA08PG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.8 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.8
[filament:Prusa PLA @XL] [filament:Prusa PLA @XL]
inherits = Prusa PLA @PG; *PLAXL* inherits = Prusa PLA @PG; *PLAXL*
@ -12247,12 +12247,12 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_notes!~/.*PG
[filament:Prusament PLA @PG] [filament:Prusament PLA @PG]
inherits = Prusament PLA; *PLAPG* inherits = Prusament PLA; *PLAPG*
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]!=0.8 and nozzle_diameter[0]!=0.6
[filament:Prusament PLA @PG 0.6] [filament:Prusament PLA @PG 0.6]
inherits = Prusament PLA; *PLA06PG* inherits = Prusament PLA; *PLA06PG*
filament_max_volumetric_speed = 16 filament_max_volumetric_speed = 16
compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_model!="MK4IS" and nozzle_diameter[0]==0.6 compatible_printers_condition = printer_notes=~/.*MK4.*/ and printer_notes!~/.*MK4IS.*/ and nozzle_diameter[0]==0.6
[filament:Prusament PLA @PG 0.8] [filament:Prusament PLA @PG 0.8]
inherits = Prusament PLA; *PLA08PG* inherits = Prusament PLA; *PLA08PG*

View File

@ -160,6 +160,8 @@ set(SLIC3R_SOURCES
GCode/ExtrusionProcessor.hpp GCode/ExtrusionProcessor.hpp
GCode/FindReplace.cpp GCode/FindReplace.cpp
GCode/FindReplace.hpp GCode/FindReplace.hpp
GCode/LabelObjects.cpp
GCode/LabelObjects.hpp
GCode/GCodeWriter.cpp GCode/GCodeWriter.cpp
GCode/GCodeWriter.hpp GCode/GCodeWriter.hpp
GCode/PostProcessor.cpp GCode/PostProcessor.cpp

View File

@ -28,6 +28,7 @@
#include "Exception.hpp" #include "Exception.hpp"
#include "ExtrusionEntity.hpp" #include "ExtrusionEntity.hpp"
#include "Geometry/ConvexHull.hpp" #include "Geometry/ConvexHull.hpp"
#include "GCode/LabelObjects.hpp"
#include "GCode/PrintExtents.hpp" #include "GCode/PrintExtents.hpp"
#include "GCode/Thumbnails.hpp" #include "GCode/Thumbnails.hpp"
#include "GCode/WipeTower.hpp" #include "GCode/WipeTower.hpp"
@ -102,7 +103,6 @@ namespace Slic3r {
gcode += '\n'; gcode += '\n';
} }
// Return true if tch_prefix is found in custom_gcode // Return true if tch_prefix is found in custom_gcode
static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
{ {
@ -1144,6 +1144,10 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
// Emit machine envelope limits for the Marlin firmware. // Emit machine envelope limits for the Marlin firmware.
this->print_machine_envelope(file, print); this->print_machine_envelope(file, print);
// Label all objects so printer knows about them since the start.
m_label_objects.init(print);
file.write(m_label_objects.all_objects_header());
// Update output variables after the extruders were initialized. // Update output variables after the extruders were initialized.
m_placeholder_parser_integration.init(m_writer); m_placeholder_parser_integration.init(m_writer);
// Let the start-up script prime the 1st printing tool. // Let the start-up script prime the 1st printing tool.
@ -2336,9 +2340,8 @@ void GCodeGenerator::process_layer_single_object(
const bool print_wipe_extrusions) const bool print_wipe_extrusions)
{ {
bool first = true; bool first = true;
int object_id = 0;
// Delay layer initialization as many layers may not print with all extruders. // Delay layer initialization as many layers may not print with all extruders.
auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &object_id, &gcode]() { auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &gcode]() {
if (first) { if (first) {
first = false; first = false;
const PrintObject &print_object = print_instance.print_object; const PrintObject &print_object = print_instance.print_object;
@ -2354,14 +2357,7 @@ void GCodeGenerator::process_layer_single_object(
m_avoid_crossing_perimeters.use_external_mp_once(); m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy; m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset)); this->set_origin(unscale(offset));
if (this->config().gcode_label_objects) { gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No);
for (const PrintObject *po : print_object.print()->objects())
if (po == &print_object)
break;
else
++ object_id;
gcode += std::string("; printing object ") + print_object.model_object()->name + " id:" + std::to_string(object_id) + " copy " + std::to_string(print_instance.instance_id) + "\n";
}
} }
}; };
@ -2538,8 +2534,8 @@ void GCodeGenerator::process_layer_single_object(
} }
} }
} }
if (! first && this->config().gcode_label_objects) if (! first)
gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(object_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]);
} }
void GCodeGenerator::apply_print_config(const PrintConfig &print_config) void GCodeGenerator::apply_print_config(const PrintConfig &print_config)

View File

@ -28,6 +28,7 @@
#include "GCode/CoolingBuffer.hpp" #include "GCode/CoolingBuffer.hpp"
#include "GCode/FindReplace.hpp" #include "GCode/FindReplace.hpp"
#include "GCode/GCodeWriter.hpp" #include "GCode/GCodeWriter.hpp"
#include "GCode/LabelObjects.hpp"
#include "GCode/PressureEqualizer.hpp" #include "GCode/PressureEqualizer.hpp"
#include "GCode/RetractWhenCrossingPerimeters.hpp" #include "GCode/RetractWhenCrossingPerimeters.hpp"
#include "GCode/SmoothPath.hpp" #include "GCode/SmoothPath.hpp"
@ -353,6 +354,7 @@ private:
OozePrevention m_ooze_prevention; OozePrevention m_ooze_prevention;
GCode::Wipe m_wipe; GCode::Wipe m_wipe;
GCode::LabelObjects m_label_objects;
AvoidCrossingPerimeters m_avoid_crossing_perimeters; AvoidCrossingPerimeters m_avoid_crossing_perimeters;
JPSPathFinder m_avoid_crossing_curled_overhangs; JPSPathFinder m_avoid_crossing_curled_overhangs;
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;

View File

@ -49,6 +49,34 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
float flow_width, float flow_width,
float max_line_length = -1.0f) float max_line_length = -1.0f)
{ {
bool looped = input_points.front() == input_points.back();
std::function<int(int,size_t)> get_prev_index = [](int idx, size_t count) {
if (idx > 0) {
return idx - 1;
} else
return idx;
};
if (looped) {
get_prev_index = [](int idx, size_t count) {
if (idx == 0)
idx = count;
return --idx;
};
};
std::function<int(int,size_t)> get_next_index = [](int idx, size_t size) {
if (idx + 1 < size) {
return idx + 1;
} else
return idx;
};
if (looped) {
get_next_index = [](int idx, size_t count) {
if (++idx == count)
idx = 0;
return idx;
};
};
using P = typename POINTS::value_type; using P = typename POINTS::value_type;
using AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar; using AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar;
@ -156,69 +184,65 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
points = std::move(new_points); points = std::move(new_points);
} }
std::vector<float> angles_for_curvature(points.size()); float accumulated_distance = 0;
std::vector<float> distances_for_curvature(points.size()); std::vector<float> distances_for_curvature(points.size());
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
const ExtendedPoint &a = points[point_idx];
const ExtendedPoint &b = points[get_prev_index(point_idx, points.size())];
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) { distances_for_curvature[point_idx] = (b.position - a.position).norm();
ExtendedPoint &a = points[point_idx]; accumulated_distance += distances_for_curvature[point_idx];
size_t prev = prev_idx_modulo(point_idx, points.size());
size_t next = next_idx_modulo(point_idx, points.size());
int iter_limit = points.size();
while ((a.position - points[prev].position).squaredNorm() < 1 && iter_limit > 0) {
prev = prev_idx_modulo(prev, points.size());
iter_limit--;
}
while ((a.position - points[next].position).squaredNorm() < 1 && iter_limit > 0) {
next = next_idx_modulo(next, points.size());
iter_limit--;
}
distances_for_curvature[point_idx] = (points[prev].position - a.position).norm();
float alfa = angle(a.position - points[prev].position, points[next].position - a.position);
angles_for_curvature[point_idx] = alfa;
} }
if (std::accumulate(distances_for_curvature.begin(), distances_for_curvature.end(), 0) > EPSILON) if (accumulated_distance > EPSILON)
for (float window_size : {3.0f, 9.0f, 16.0f}) { for (float window_size : {3.0f, 9.0f, 16.0f}) {
size_t tail_point = 0; for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
float tail_window_acc = 0; ExtendedPoint &current = points[point_idx];
float tail_angle_acc = 0;
size_t head_point = 0; Vec2d back_position = current.position;
float head_window_acc = 0; {
float head_angle_acc = 0; size_t back_point_index = point_idx;
float dist_backwards = 0;
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) { while (dist_backwards < window_size * 0.5 && back_point_index != get_prev_index(back_point_index, points.size())) {
if (point_idx == 0) { float line_dist = distances_for_curvature[get_prev_index(back_point_index, points.size())];
while (tail_window_acc < window_size * 0.5) { if (dist_backwards + line_dist > window_size * 0.5) {
tail_window_acc += distances_for_curvature[tail_point]; back_position = points[back_point_index].position +
tail_angle_acc += angles_for_curvature[tail_point]; (window_size * 0.5 - dist_backwards) *
tail_point = prev_idx_modulo(tail_point, points.size()); (points[get_prev_index(back_point_index, points.size())].position -
points[back_point_index].position)
.normalized();
dist_backwards += window_size * 0.5 - dist_backwards + EPSILON;
} else {
dist_backwards += line_dist;
back_point_index = get_prev_index(back_point_index, points.size());
}
} }
} }
while (tail_window_acc - distances_for_curvature[next_idx_modulo(tail_point, points.size())] > window_size * 0.5) {
tail_point = next_idx_modulo(tail_point, points.size()); Vec2d front_position = current.position;
tail_window_acc -= distances_for_curvature[tail_point]; {
tail_angle_acc -= angles_for_curvature[tail_point]; size_t front_point_index = point_idx;
float dist_forwards = 0;
while (dist_forwards < window_size * 0.5 && front_point_index != get_next_index(front_point_index, points.size())) {
float line_dist = distances_for_curvature[front_point_index];
if (dist_forwards + line_dist > window_size * 0.5) {
front_position = points[front_point_index].position +
(window_size * 0.5 - dist_forwards) *
(points[get_next_index(front_point_index, points.size())].position -
points[front_point_index].position)
.normalized();
dist_forwards += window_size * 0.5 - dist_forwards + EPSILON;
} else {
dist_forwards += line_dist;
front_point_index = get_next_index(front_point_index, points.size());
}
}
} }
while (head_window_acc < window_size * 0.5) { float new_curvature = angle(current.position - back_position, front_position - current.position) / window_size;
head_point = next_idx_modulo(head_point, points.size()); if (abs(current.curvature) < abs(new_curvature)) {
head_window_acc += distances_for_curvature[head_point]; current.curvature = new_curvature;
head_angle_acc += angles_for_curvature[head_point];
} }
float curvature = (tail_angle_acc + head_angle_acc) / window_size;
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
points[point_idx].curvature = curvature;
}
tail_window_acc += distances_for_curvature[point_idx];
tail_angle_acc += angles_for_curvature[point_idx];
head_window_acc -= distances_for_curvature[point_idx];
head_angle_acc -= angles_for_curvature[point_idx];
} }
} }

View File

@ -595,8 +595,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
for (size_t i = 0; i < extruders_count; ++ i) { for (size_t i = 0; i < extruders_count; ++ i) {
m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast<float>().eval(), 0.f); m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast<float>().eval(), 0.f);
m_extruder_colors[i] = static_cast<unsigned char>(i); m_extruder_colors[i] = static_cast<unsigned char>(i);
m_extruder_temps_config[i] = static_cast<int>(config.temperature.get_at(i));
m_extruder_temps_first_layer_config[i] = static_cast<int>(config.first_layer_temperature.get_at(i)); m_extruder_temps_first_layer_config[i] = static_cast<int>(config.first_layer_temperature.get_at(i));
m_extruder_temps_config[i] = static_cast<int>(config.temperature.get_at(i));
if (m_extruder_temps_config[i] == 0) {
// This means the value should be ignored and first layer temp should be used.
m_extruder_temps_config[i] = m_extruder_temps_first_layer_config[i];
}
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i)); m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i)); m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
m_result.filament_cost[i] = static_cast<float>(config.filament_cost.get_at(i)); m_result.filament_cost[i] = static_cast<float>(config.filament_cost.get_at(i));

View File

@ -0,0 +1,190 @@
#include "LabelObjects.hpp"
#include "ClipperUtils.hpp"
#include "Model.hpp"
#include "Print.hpp"
#include "TriangleMeshSlicer.hpp"
namespace Slic3r::GCode {
namespace {
Polygon instance_outline(const PrintInstance* pi)
{
ExPolygons outline;
const ModelObject* mo = pi->model_instance->get_object();
const ModelInstance* mi = pi->model_instance;
for (const ModelVolume *v : mo->volumes) {
Polygons vol_outline;
vol_outline = project_mesh(v->mesh().its,
mi->get_matrix() * v->get_matrix(),
[] {});
switch (v->type()) {
case ModelVolumeType::MODEL_PART: outline = union_ex(outline, vol_outline); break;
case ModelVolumeType::NEGATIVE_VOLUME: outline = diff_ex(outline, vol_outline); break;
default:;
}
}
// The projection may contain multiple polygons, which is not supported by Klipper.
// When that happens, calculate and use a 2d convex hull instead.
if (outline.size() == 1u)
return outline.front().contour;
else
return pi->model_instance->get_object()->convex_hull_2d(pi->model_instance->get_matrix());
}
}; // anonymous namespace
void LabelObjects::init(const Print& print)
{
m_label_objects_style = print.config().gcode_label_objects;
m_flavor = print.config().gcode_flavor;
if (m_label_objects_style == LabelObjectsStyle::Disabled)
return;
std::map<const ModelObject*, std::vector<const PrintInstance*>> model_object_to_print_instances;
// Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which
// belong to the same ModelObject.
for (const PrintObject* po : print.objects())
for (const PrintInstance& pi : po->instances())
model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi);
// Now go through the map, assign a unique_id to each of the PrintInstances and get the indices of the
// respective ModelObject and ModelInstance so we can use them in the tags. This will maintain
// indices even in case that some instances are rotated (those end up in different PrintObjects)
// or when some are out of bed (these ModelInstances have no corresponding PrintInstances).
int unique_id = 0;
for (const auto& [model_object, print_instances] : model_object_to_print_instances) {
const ModelObjectPtrs& model_objects = model_object->get_model()->objects;
int object_id = int(std::find(model_objects.begin(), model_objects.end(), model_object) - model_objects.begin());
for (const PrintInstance* const pi : print_instances) {
bool object_has_more_instances = print_instances.size() > 1u;
int instance_id = int(std::find(model_object->instances.begin(), model_object->instances.end(), pi->model_instance) - model_object->instances.begin());
// Now compose the name of the object and define whether indexing is 0 or 1-based.
std::string name = model_object->name;
if (m_label_objects_style == LabelObjectsStyle::Octoprint) {
// use zero-based indexing for objects and instances, as we always have done
name += " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id);
}
else if (m_label_objects_style == LabelObjectsStyle::Firmware) {
// use one-based indexing for objects and instances so indices match what we see in PrusaSlicer.
++object_id;
++instance_id;
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";
std::replace_if(name.begin(), name.end(), [&banned](char c) { return banned.find(c) != std::string::npos; }, '_');
}
}
m_label_data.emplace(pi, LabelData{name, unique_id});
++unique_id;
}
}
}
std::string LabelObjects::all_objects_header() const
{
if (m_label_objects_style == LabelObjectsStyle::Disabled)
return std::string();
std::string out;
// Let's sort the values according to unique_id so they are in the same order in which they were added.
std::vector<std::pair<const PrintInstance*, LabelData>> label_data_sorted;
for (const auto& pi_and_label : m_label_data)
label_data_sorted.emplace_back(pi_and_label);
std::sort(label_data_sorted.begin(), label_data_sorted.end(), [](const auto& ld1, const auto& ld2) { return ld1.second.unique_id < ld2.second.unique_id; });
out += "\n";
for (const auto& [print_instance, label] : label_data_sorted) {
if (m_flavor == gcfKlipper) {
char buffer[64];
out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name;
Polygon outline = instance_outline(print_instance);
assert(! outline.empty());
outline.douglas_peucker(50000.f);
Point center = outline.centroid();
std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
out += buffer + std::string(" POLYGON=[");
for (const Point& point : outline) {
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
out += buffer;
}
out.pop_back();
out += "]\n";
} else {
out += start_object(*print_instance, IncludeName::Yes);
out += stop_object(*print_instance);
}
}
out += "\n";
return out;
}
std::string LabelObjects::start_object(const PrintInstance& print_instance, IncludeName include_name) const
{
if (m_label_objects_style == LabelObjectsStyle::Disabled)
return std::string();
const LabelData& label = m_label_data.at(&print_instance);
std::string out;
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
out += std::string("; printing object ") + label.name + "\n";
else if (m_label_objects_style == LabelObjectsStyle::Firmware) {
if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) {
out += std::string("M486 S") + std::to_string(label.unique_id) + "\n";
if (include_name == IncludeName::Yes) {
out += std::string("M486 A");
out += (m_flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + label.name + "\"") : label.name) + "\n";
}
} else if (m_flavor == gcfKlipper)
out += "EXCLUDE_OBJECT_START NAME=" + label.name + "\n";
else {
// Not supported by / implemented for the other firmware flavors.
}
}
return out;
}
std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
{
if (m_label_objects_style == LabelObjectsStyle::Disabled)
return std::string();
const LabelData& label = m_label_data.at(&print_instance);
std::string out;
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
out += std::string("; stop printing object ") + label.name + "\n";
else if (m_label_objects_style == LabelObjectsStyle::Firmware) {
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";
else {
// Not supported by / implemented for the other firmware flavors.
}
}
return out;
}
} // namespace Slic3r::GCode

View File

@ -0,0 +1,42 @@
#ifndef slic3r_GCode_LabelObjects_hpp_
#define slic3r_GCode_LabelObjects_hpp_
namespace Slic3r {
enum GCodeFlavor : unsigned char;
enum class LabelObjectsStyle;
struct PrintInstance;
class Print;
namespace GCode {
class LabelObjects {
public:
enum class IncludeName {
No,
Yes
};
void init(const Print& print);
std::string all_objects_header() const;
std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const;
std::string stop_object(const PrintInstance& print_instance) const;
private:
struct LabelData {
std::string name;
int unique_id;
};
LabelObjectsStyle m_label_objects_style;
GCodeFlavor m_flavor;
std::unordered_map<const PrintInstance*, LabelData> m_label_data;
};
} // namespace GCode
} // namespace Slic3r
#endif // slic3r_GCode_LabelObjects_hpp_

View File

@ -146,4 +146,83 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations, double* min_error)
return circle_best; return circle_best;
} }
template<typename Solver>
Circled circle_least_squares_by_solver(const Vec2ds &input, Solver solver)
{
Circled out;
if (input.size() < 3) {
out = Circled::make_invalid();
} else {
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic /* 3 */> A(input.size(), 3);
Eigen::VectorXd b(input.size());
for (size_t r = 0; r < input.size(); ++ r) {
const Vec2d &p = input[r];
A.row(r) = Vec3d(2. * p.x(), 2. * p.y(), - 1.);
b(r) = p.squaredNorm();
}
auto result = solver(A, b);
out.center = result.template head<2>();
double r2 = out.center.squaredNorm() - result(2);
if (r2 <= EPSILON)
out.make_invalid();
else
out.radius = sqrt(r2);
}
return out;
}
Circled circle_least_squares_svd(const Vec2ds &input)
{
return circle_least_squares_by_solver(input,
[](const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic /* 3 */> &A, const Eigen::VectorXd &b)
{ return A.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b).eval(); });
}
Circled circle_least_squares_qr(const Vec2ds &input)
{
return circle_least_squares_by_solver(input,
[](const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> &A, const Eigen::VectorXd &b)
{ return A.colPivHouseholderQr().solve(b).eval(); });
}
Circled circle_least_squares_normal(const Vec2ds &input)
{
Circled out;
if (input.size() < 3) {
out = Circled::make_invalid();
} else {
Eigen::Matrix<double, 3, 3> A = Eigen::Matrix<double, 3, 3>::Zero();
Eigen::Matrix<double, 3, 1> b = Eigen::Matrix<double, 3, 1>::Zero();
for (size_t i = 0; i < input.size(); ++ i) {
const Vec2d &p = input[i];
// Calculate right hand side of a normal equation.
b += p.squaredNorm() * Vec3d(2. * p.x(), 2. * p.y(), -1.);
// Calculate normal matrix (correlation matrix).
// Diagonal:
A(0, 0) += 4. * p.x() * p.x();
A(1, 1) += 4. * p.y() * p.y();
A(2, 2) += 1.;
// Off diagonal elements:
const double a = 4. * p.x() * p.y();
A(0, 1) += a;
A(1, 0) += a;
const double b = -2. * p.x();
A(0, 2) += b;
A(2, 0) += b;
const double c = -2. * p.y();
A(1, 2) += c;
A(2, 1) += c;
}
auto result = A.ldlt().solve(b).eval();
out.center = result.head<2>();
double r2 = out.center.squaredNorm() - result(2);
if (r2 <= EPSILON)
out.make_invalid();
else
out.radius = sqrt(r2);
}
return out;
}
} } // namespace Slic3r::Geometry } } // namespace Slic3r::Geometry

View File

@ -141,6 +141,13 @@ Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20);
// Find circle using RANSAC randomized algorithm. // Find circle using RANSAC randomized algorithm.
Circled circle_ransac(const Vec2ds& input, size_t iterations = 20, double* min_error = nullptr); Circled circle_ransac(const Vec2ds& input, size_t iterations = 20, double* min_error = nullptr);
// Least squares fitting with SVD. Most accurate, but slowest.
Circled circle_least_squares_svd(const Vec2ds &input);
// Least squares fitting with QR decomposition. Medium accuracy, medium speed.
Circled circle_least_squares_qr(const Vec2ds &input);
// Least squares fitting solving normal equations. Low accuracy, high speed.
Circled circle_least_squares_normal(const Vec2ds &input);
// Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon. // Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon.
template<typename Vector, typename Points> template<typename Vector, typename Points>
CircleSq<Vector> smallest_enclosing_circle2_welzl(const Points &points, const typename Vector::Scalar epsilon) CircleSq<Vector> smallest_enclosing_circle2_welzl(const Points &points, const typename Vector::Scalar epsilon)

View File

@ -229,6 +229,13 @@ static const t_config_enum_values s_keys_map_DraftShield = {
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield)
static const t_config_enum_values s_keys_map_LabelObjectsStyle = {
{ "disabled", int(LabelObjectsStyle::Disabled) },
{ "octoprint", int(LabelObjectsStyle::Octoprint) },
{ "firmware", int(LabelObjectsStyle::Firmware) }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(LabelObjectsStyle)
static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = {
{ "PNG", int(GCodeThumbnailsFormat::PNG) }, { "PNG", int(GCodeThumbnailsFormat::PNG) },
{ "JPG", int(GCodeThumbnailsFormat::JPG) }, { "JPG", int(GCodeThumbnailsFormat::JPG) },
@ -1493,13 +1500,20 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfRepRapSprinter)); def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfRepRapSprinter));
def = this->add("gcode_label_objects", coBool); def = this->add("gcode_label_objects", coEnum);
def->label = L("Label objects"); def->label = L("Label objects");
def->tooltip = L("Enable this to add comments into the G-Code labeling print moves with what object they belong to," def->tooltip = L("Selects whether labels should be exported at object boundaries and in what format.\n"
" which is useful for the Octoprint CancelObject plugin. This settings is NOT compatible with " " OctoPrint = comments to be consumed by OctoPrint CancelObject plugin.\n"
"Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."); " Firmware = firmware specific G-code (it will be chosen based on firmware flavor and it can end up to be empty).\n\n"
"This settings is NOT compatible with Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill.");
def->set_enum<LabelObjectsStyle>({
{ "disabled", L("Disabled") },
{ "octoprint", L("OctoPrint comments") },
{ "firmware", L("Firmware-specific") }
});
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(0)); def->set_default_value(new ConfigOptionEnum<LabelObjectsStyle>(LabelObjectsStyle::Disabled));
def = this->add("gcode_substitutions", coStrings); def = this->add("gcode_substitutions", coStrings);
def->label = L("G-code substitutions"); def->label = L("G-code substitutions");
@ -4335,6 +4349,10 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} else if (opt_key == "draft_shield" && (value == "1" || value == "0")) { } else if (opt_key == "draft_shield" && (value == "1" || value == "0")) {
// draft_shield used to be a bool, it was turned into an enum in PrusaSlicer 2.4.0. // draft_shield used to be a bool, it was turned into an enum in PrusaSlicer 2.4.0.
value = value == "1" ? "enabled" : "disabled"; value = value == "1" ? "enabled" : "disabled";
} else if (opt_key == "gcode_label_objects" && (value == "1" || value == "0")) {
// gcode_label_objects used to be a bool (the behavior was nothing or "octoprint"), it is
// and enum since PrusaSlicer 2.6.2.
value = value == "1" ? "octoprint" : "disabled";
} else if (opt_key == "octoprint_host") { } else if (opt_key == "octoprint_host") {
opt_key = "print_host"; opt_key = "print_host";
} else if (opt_key == "octoprint_cafile") { } else if (opt_key == "octoprint_cafile") {

View File

@ -147,6 +147,10 @@ enum DraftShield {
dsDisabled, dsLimited, dsEnabled dsDisabled, dsLimited, dsEnabled
}; };
enum class LabelObjectsStyle {
Disabled, Octoprint, Firmware
};
enum class PerimeterGeneratorType enum class PerimeterGeneratorType
{ {
// Classic perimeter generator using Clipper offsets with constant extrusion width. // Classic perimeter generator using Clipper offsets with constant extrusion width.
@ -183,6 +187,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLASupportTreeType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLASupportTreeType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(LabelObjectsStyle)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
@ -729,7 +734,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionFloats, filament_multitool_ramming_flow))
((ConfigOptionBool, gcode_comments)) ((ConfigOptionBool, gcode_comments))
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor)) ((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
((ConfigOptionBool, gcode_label_objects)) ((ConfigOptionEnum<LabelObjectsStyle>, gcode_label_objects))
// Triples of strings: "search pattern", "replace with pattern", "attribs" // Triples of strings: "search pattern", "replace with pattern", "attribs"
// where "attribs" are one of: // where "attribs" are one of:
// r - regular expression // r - regular expression

View File

@ -166,7 +166,7 @@ struct SliceConnection
this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator;
} }
void print_info(const std::string &tag) void print_info(const std::string &tag) const
{ {
Vec3f centroid = centroid_accumulator / area; Vec3f centroid = centroid_accumulator / area;
Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>()));
@ -179,6 +179,27 @@ struct SliceConnection
} }
}; };
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>();
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;
}
}
}
SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer)
{ {
SliceConnection connection; SliceConnection connection;
@ -200,22 +221,11 @@ SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer)
Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb), Polygons overlap = intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(slice_polys, below_bb),
ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb)); ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb));
for (const Polygon &poly : overlap) { const Integrals integrals{overlap};
Vec2f p0 = unscaled(poly.first_point()).cast<float>(); connection.area += integrals.area;
for (size_t i = 2; i < poly.points.size(); i++) { connection.centroid_accumulator += Vec3f(integrals.x_i.x(), integrals.x_i.y(), layer->print_z * integrals.area);
Vec2f p1 = unscaled(poly.points[i - 1]).cast<float>(); connection.second_moment_of_area_accumulator += integrals.x_i_squared;
Vec2f p2 = unscaled(poly.points[i]).cast<float>(); connection.second_moment_of_area_covariance_accumulator += integrals.xy;
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);
connection.area += sign * area;
connection.centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area);
connection.second_moment_of_area_accumulator += sign * second_moment_area;
connection.second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance;
}
}
return connection; return connection;
}; };
@ -450,6 +460,48 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
} }
} }
/**
* Calculates the second moment of area over an arbitrary polygon.
*
* Important note: The calculated moment is for an axis with origin at
* the polygon centroid!
*
* @param integrals Integrals over the polygon area.
* @param axis_direction Direction of the rotation axis going through centroid.
*/
float compute_second_moment(
const Integrals& integrals,
const Vec2f& axis_direction
) {
// Second moment of area for any axis intersecting coordinate system origin
// can be evaluated using the second moments of area calculated for the coordinate
// system axis and the moment product (int xy).
// The equation is derived appling known formulas for the moment of inertia
// to a plannar problem. One can reason about second moment
// of area by by setting density to 1 in the moment of inertia formulas.
const auto area = integrals.area;
const auto I_xx = integrals.x_i_squared.y();
const auto I_yy = integrals.x_i_squared.x();
const auto I_xy = -integrals.xy;
const Vec2f centroid = integrals.x_i / area;
Matrix2f moment_tensor{};
moment_tensor <<
I_xx, I_xy,
I_xy, I_yy;
const float moment_at_0_0 = axis_direction.transpose() * moment_tensor * axis_direction;
// Apply parallel axis theorem to move the moment to centroid
using line_alg::distance_to_infinite_squared;
const Linef axis_at_0_0 = {{0, 0}, axis_direction.cast<double>()};
const double distance = distance_to_infinite_squared(axis_at_0_0, centroid.cast<double>());
return moment_at_0_0 - area * distance;
}
class ObjectPart class ObjectPart
{ {
public: public:
@ -482,43 +534,22 @@ public:
this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y(); this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y();
} }
float compute_directional_xy_variance(const Vec2f &line_dir,
const Vec3f &centroid_accumulator,
const Vec2f &second_moment_of_area_accumulator,
const float &second_moment_of_area_covariance_accumulator,
const float &area) const
{
assert(area > 0);
Vec3f centroid = centroid_accumulator / area;
Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>()));
float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y();
// Var(aX+bY)=a^2*Var(X)+b^2*Var(Y)+2*a*b*Cov(X,Y)
float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() + line_dir.y() * line_dir.y() * variance.y() +
2.0f * line_dir.x() * line_dir.y() * covariance;
#ifdef DETAILED_DEBUG_LOGS
BOOST_LOG_TRIVIAL(debug) << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z();
BOOST_LOG_TRIVIAL(debug) << "variance: " << variance.x() << " " << variance.y();
BOOST_LOG_TRIVIAL(debug) << "covariance: " << covariance;
BOOST_LOG_TRIVIAL(debug) << "directional_xy_variance: " << directional_xy_variance;
#endif
return directional_xy_variance;
}
float compute_elastic_section_modulus(const Vec2f &line_dir, float compute_elastic_section_modulus(
const Vec3f &extreme_point, const Vec2f &line_dir,
const Vec3f &centroid_accumulator, const Vec3f &extreme_point,
const Vec2f &second_moment_of_area_accumulator, const Integrals& integrals
const float &second_moment_of_area_covariance_accumulator, ) const {
const float &area) const float second_moment_of_area = compute_second_moment(integrals, Vec2f{-line_dir.y(), line_dir.x()});
{
float directional_xy_variance = compute_directional_xy_variance(line_dir, centroid_accumulator, second_moment_of_area_accumulator, if (second_moment_of_area < EPSILON) { return 0.0f; }
second_moment_of_area_covariance_accumulator, area);
if (directional_xy_variance < EPSILON) { return 0.0f; } Vec2f centroid = integrals.x_i / integrals.area;
Vec3f centroid = centroid_accumulator / area;
float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast<double>(), float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast<double>(),
(centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast<double>()), (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast<double>()),
extreme_point.head<2>().cast<double>()); extreme_point.head<2>().cast<double>());
float elastic_section_modulus = area * directional_xy_variance / extreme_fiber_dist;
float elastic_section_modulus = second_moment_of_area / extreme_fiber_dist;
#ifdef DETAILED_DEBUG_LOGS #ifdef DETAILED_DEBUG_LOGS
BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist; BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist;
@ -534,6 +565,12 @@ public:
float layer_z, float layer_z,
const Params &params) const const Params &params) const
{ {
// Note that exteme point is calculated for the current layer, while it should
// be computed for the first layer. The shape of the first layer however changes a lot,
// during support points additions (for organic supports it is not even clear how)
// and during merging. Using the current layer is heuristics and also small optimization,
// as the AABB tree for it is calculated anyways. This heuristic should usually be
// on the safe side.
Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized();
const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume;
float mass = this->volume * params.filament_density; float mass = this->volume * params.filament_density;
@ -548,19 +585,19 @@ public:
{ {
if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart};
Integrals integrals;
integrals.area = this->sticking_area;
integrals.x_i = this->sticking_centroid_accumulator.head<2>();
integrals.x_i_squared = this->sticking_second_moment_of_area_accumulator;
integrals.xy = this->sticking_second_moment_of_area_covariance_accumulator;
Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area;
float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator, float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, integrals) * params.get_bed_adhesion_yield_strength();
this->sticking_second_moment_of_area_accumulator,
this->sticking_second_moment_of_area_covariance_accumulator,
this->sticking_area) *
params.get_bed_adhesion_yield_strength();
Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>());
float bed_weight_arm_len = bed_weight_arm.norm(); float bed_weight_arm_len = bed_weight_arm.norm();
float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, this->sticking_centroid_accumulator,
this->sticking_second_moment_of_area_accumulator, float bed_weight_dir_xy_variance = compute_second_moment(integrals, {-bed_weight_arm.y(), bed_weight_arm.x()}) / this->sticking_area;
this->sticking_second_moment_of_area_covariance_accumulator,
this->sticking_area);
float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f;
float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight;
@ -600,11 +637,14 @@ public:
Vec3f conn_centroid = connection.centroid_accumulator / connection.area; Vec3f conn_centroid = connection.centroid_accumulator / connection.area;
if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; } if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; }
float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, connection.centroid_accumulator,
connection.second_moment_of_area_accumulator, Integrals integrals;
connection.second_moment_of_area_covariance_accumulator, integrals.area = connection.area;
connection.area) * integrals.x_i = connection.centroid_accumulator.head<2>();
params.material_yield_strength; integrals.x_i_squared = connection.second_moment_of_area_accumulator;
integrals.xy = connection.second_moment_of_area_covariance_accumulator;
float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, integrals) * params.material_yield_strength;
float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm();
if (layer_z - conn_centroid.z() < 30.0) { if (layer_z - conn_centroid.z() < 30.0) {

View File

@ -147,6 +147,33 @@ struct PartialObject
bool connected_to_bed; bool connected_to_bed;
}; };
/**
* Unsacled values of integrals over a polygonal domain.
*/
class Integrals{
public:
/**
* Construct integral x_i int x_i^2 (i=1,2), xy and integral 1 (area).
*
* @param polygons List of polygons specifing the domain.
*/
explicit Integrals(const Polygons& polygons);
// TODO refactor and delete the default constructor
Integrals() = default;
float area{};
Vec2f x_i{Vec2f::Zero()};
Vec2f x_i_squared{Vec2f::Zero()};
float xy{};
};
float compute_second_moment(
const Integrals& integrals,
const Vec2f& axis_direction
);
using PartialObjects = std::vector<PartialObject>; using PartialObjects = std::vector<PartialObject>;
// Both support points and partial objects are sorted from the lowest z to the highest // Both support points and partial objects are sorted from the lowest z to the highest

View File

@ -277,7 +277,6 @@ void EditGCodeDialog::add_selected_value_to_gcode()
if (val.IsEmpty()) if (val.IsEmpty())
return; return;
const long pos = m_gcode_editor->GetInsertionPoint();
m_gcode_editor->WriteText(m_gcode_editor->GetInsertionPoint() == m_gcode_editor->GetLastPosition() ? "\n" + val : val); m_gcode_editor->WriteText(m_gcode_editor->GetInsertionPoint() == m_gcode_editor->GetLastPosition() ? "\n" + val : val);
if (val.Last() == ']') { if (val.Last() == ']') {

View File

@ -4735,6 +4735,7 @@ void SubstitutionManager::update_from_config()
if (m_substitutions == subst && m_grid_sizer->IsShown(1)) { if (m_substitutions == subst && m_grid_sizer->IsShown(1)) {
// just update visibility for chb_match_single_lines // just update visibility for chb_match_single_lines
int subst_id = 0; int subst_id = 0;
assert(m_chb_match_single_lines.size() == size_t(subst.size()/4));
for (size_t i = 0; i < subst.size(); i += 4) { for (size_t i = 0; i < subst.size(); i += 4) {
const std::string& params = subst[i + 2]; const std::string& params = subst[i + 2];
const bool regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr; const bool regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr;
@ -4772,8 +4773,10 @@ void SubstitutionManager::delete_all()
m_config->option<ConfigOptionStrings>("gcode_substitutions")->values.clear(); m_config->option<ConfigOptionStrings>("gcode_substitutions")->values.clear();
call_ui_update(); call_ui_update();
if (!m_grid_sizer->IsEmpty()) if (!m_grid_sizer->IsEmpty()) {
m_grid_sizer->Clear(true); m_grid_sizer->Clear(true);
m_chb_match_single_lines.clear();
}
m_parent->GetParent()->Layout(); m_parent->GetParent()->Layout();
} }
@ -4839,6 +4842,7 @@ wxSizer* TabPrint::create_substitutions_widget(wxWindow* parent)
m_subst_manager.init(m_config, parent, grid_sizer); m_subst_manager.init(m_config, parent, grid_sizer);
m_subst_manager.set_cb_edited_substitution([this]() { m_subst_manager.set_cb_edited_substitution([this]() {
update_dirty(); update_dirty();
Layout();
wxGetApp().mainframe->on_config_changed(m_config); // invalidate print wxGetApp().mainframe->on_config_changed(m_config); // invalidate print
}); });
m_subst_manager.set_cb_hide_delete_all_btn([this]() { m_subst_manager.set_cb_hide_delete_all_btn([this]() {

View File

@ -39,6 +39,7 @@ add_executable(${_TEST_NAME}_tests
test_astar.cpp test_astar.cpp
test_anyptr.cpp test_anyptr.cpp
test_jump_point_search.cpp test_jump_point_search.cpp
test_support_spots_generator.cpp
../data/prusaparts.cpp ../data/prusaparts.cpp
../data/prusaparts.hpp ../data/prusaparts.hpp
) )

View File

@ -0,0 +1,97 @@
#include "libslic3r/Point.hpp"
#include <catch2/catch.hpp>
#include <libslic3r/SupportSpotsGenerator.hpp>
using namespace Slic3r;
using namespace SupportSpotsGenerator;
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})
};
const Integrals integrals{{polygon}};
CHECK(integrals.area == Approx(width * 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));
}
TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
const float width = 40;
const float height = 2;
// Moments are calculated at centroid.
// Polygon centroid must not be (0, 0).
const Polygon polygon = {
scaled(Vec2f{0, 0}),
scaled(Vec2f{width, 0}),
scaled(Vec2f{width, height}),
scaled(Vec2f{0, height})
};
const Integrals integrals{{polygon}};
const Vec2f x_axis{1, 0};
const float x_axis_moment = compute_second_moment(integrals, x_axis);
const Vec2f y_axis{0, 1};
const float y_axis_moment = compute_second_moment(integrals, y_axis);
const float moment_ratio = std::pow(width / height, 2);
// Ensure the object transaltion has no effect.
CHECK(x_axis_moment == Approx(width * std::pow(height, 3) / 12));
CHECK(y_axis_moment == Approx(std::pow(width, 3) * height / 12));
// If the object is "wide" the y axis moments should be large compared to x axis moment.
CHECK(y_axis_moment / x_axis_moment == Approx(moment_ratio));
}
TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
Polygon polygon = {
scaled(Vec2f{6.362284076172198, 138.9674202217155}),
scaled(Vec2f{97.48779843751677, 106.08136606617076}),
scaled(Vec2f{135.75221821532384, 66.84428834668765}),
scaled(Vec2f{191.5308049852741, 45.77905628725614}),
scaled(Vec2f{182.7525148049201, 74.01799041087513}),
scaled(Vec2f{296.83210979283473, 196.80022572637228}),
scaled(Vec2f{215.16434429179148, 187.45715418834143}),
scaled(Vec2f{64.64574271229334, 284.293883209721}),
scaled(Vec2f{110.76507036894843, 174.35633141113783}),
scaled(Vec2f{77.56229640885199, 189.33057746591336})
};
Integrals integrals{{polygon}};
std::mt19937 generator{std::random_device{}()};
std::uniform_real_distribution<float> angle_distribution{0, 2*M_PI};
// Meassured counterclockwise from (1, 0)
const float angle = angle_distribution(generator);
Vec2f axis{std::cos(angle), std::sin(angle)};
float moment_calculated_then_rotated = compute_second_moment(
integrals,
axis
);
// We want to rotate the object clockwise by angle to align the axis with (1, 0)
// Method .rotate is counterclockwise for positive angle
polygon.rotate(-angle);
Integrals integrals_rotated{{polygon}};
float moment_rotated_polygon = compute_second_moment(
integrals_rotated,
Vec2f{1, 0}
);
CHECK(moment_calculated_then_rotated == Approx(moment_rotated_polygon));
}