mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 06:12:00 +08:00
Merge branch 'master' into fs_svg_SPE-1517
This commit is contained in:
commit
5da14c491e
@ -1,4 +1,5 @@
|
||||
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-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.
|
||||
|
@ -5,7 +5,7 @@
|
||||
name = Prusa Research
|
||||
# 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.
|
||||
config_version = 1.11.0-alpha3
|
||||
config_version = 1.11.0-alpha4
|
||||
# Where to get the updates from?
|
||||
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%
|
||||
@ -6399,18 +6399,18 @@ compatible_printers_condition = nozzle_diameter[0]!=0.8 and printer_model!="MK2S
|
||||
|
||||
[filament:Generic PETG @PG]
|
||||
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]
|
||||
inherits = Generic PETG; *PET06PG*
|
||||
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]
|
||||
inherits = Generic PETG; *PET08PG*
|
||||
first_layer_temperature = 240
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
inherits = Generic PLA; *PLA06PG*
|
||||
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]
|
||||
inherits = Generic PLA; *PLA08PG*
|
||||
first_layer_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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
inherits = Prusa PETG; *PET08PG*
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
inherits = Prusament PETG; *PET08PG*
|
||||
first_layer_temperature = 250
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
inherits = Prusa PLA; *PLA06PG*
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
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]
|
||||
inherits = Prusament PLA; *PLA06PG*
|
||||
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]
|
||||
inherits = Prusament PLA; *PLA08PG*
|
||||
|
@ -160,6 +160,8 @@ set(SLIC3R_SOURCES
|
||||
GCode/ExtrusionProcessor.hpp
|
||||
GCode/FindReplace.cpp
|
||||
GCode/FindReplace.hpp
|
||||
GCode/LabelObjects.cpp
|
||||
GCode/LabelObjects.hpp
|
||||
GCode/GCodeWriter.cpp
|
||||
GCode/GCodeWriter.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "Exception.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "GCode/LabelObjects.hpp"
|
||||
#include "GCode/PrintExtents.hpp"
|
||||
#include "GCode/Thumbnails.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
@ -102,7 +103,6 @@ namespace Slic3r {
|
||||
gcode += '\n';
|
||||
}
|
||||
|
||||
|
||||
// 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)
|
||||
{
|
||||
@ -1144,6 +1144,10 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail
|
||||
// Emit machine envelope limits for the Marlin firmware.
|
||||
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.
|
||||
m_placeholder_parser_integration.init(m_writer);
|
||||
// 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)
|
||||
{
|
||||
bool first = true;
|
||||
int object_id = 0;
|
||||
// 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) {
|
||||
first = false;
|
||||
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_last_obj_copy = this_object_copy;
|
||||
this->set_origin(unscale(offset));
|
||||
if (this->config().gcode_label_objects) {
|
||||
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";
|
||||
}
|
||||
gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No);
|
||||
}
|
||||
};
|
||||
|
||||
@ -2538,8 +2534,8 @@ void GCodeGenerator::process_layer_single_object(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! first && this->config().gcode_label_objects)
|
||||
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";
|
||||
if (! first)
|
||||
gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]);
|
||||
}
|
||||
|
||||
void GCodeGenerator::apply_print_config(const PrintConfig &print_config)
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
#include "GCode/FindReplace.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
#include "GCode/LabelObjects.hpp"
|
||||
#include "GCode/PressureEqualizer.hpp"
|
||||
#include "GCode/RetractWhenCrossingPerimeters.hpp"
|
||||
#include "GCode/SmoothPath.hpp"
|
||||
@ -353,6 +354,7 @@ private:
|
||||
|
||||
OozePrevention m_ooze_prevention;
|
||||
GCode::Wipe m_wipe;
|
||||
GCode::LabelObjects m_label_objects;
|
||||
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
|
||||
JPSPathFinder m_avoid_crossing_curled_overhangs;
|
||||
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
|
||||
|
@ -49,6 +49,34 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
float flow_width,
|
||||
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 AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar;
|
||||
@ -156,69 +184,65 @@ std::vector<ExtendedPoint> estimate_points_properties(const 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());
|
||||
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) {
|
||||
ExtendedPoint &a = points[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--;
|
||||
distances_for_curvature[point_idx] = (b.position - a.position).norm();
|
||||
accumulated_distance += distances_for_curvature[point_idx];
|
||||
}
|
||||
|
||||
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}) {
|
||||
size_t tail_point = 0;
|
||||
float tail_window_acc = 0;
|
||||
float tail_angle_acc = 0;
|
||||
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
||||
ExtendedPoint ¤t = points[point_idx];
|
||||
|
||||
size_t head_point = 0;
|
||||
float head_window_acc = 0;
|
||||
float head_angle_acc = 0;
|
||||
|
||||
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
|
||||
if (point_idx == 0) {
|
||||
while (tail_window_acc < window_size * 0.5) {
|
||||
tail_window_acc += distances_for_curvature[tail_point];
|
||||
tail_angle_acc += angles_for_curvature[tail_point];
|
||||
tail_point = prev_idx_modulo(tail_point, points.size());
|
||||
Vec2d back_position = current.position;
|
||||
{
|
||||
size_t back_point_index = point_idx;
|
||||
float dist_backwards = 0;
|
||||
while (dist_backwards < window_size * 0.5 && back_point_index != get_prev_index(back_point_index, points.size())) {
|
||||
float line_dist = distances_for_curvature[get_prev_index(back_point_index, points.size())];
|
||||
if (dist_backwards + line_dist > window_size * 0.5) {
|
||||
back_position = points[back_point_index].position +
|
||||
(window_size * 0.5 - dist_backwards) *
|
||||
(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());
|
||||
tail_window_acc -= distances_for_curvature[tail_point];
|
||||
tail_angle_acc -= angles_for_curvature[tail_point];
|
||||
}
|
||||
|
||||
while (head_window_acc < window_size * 0.5) {
|
||||
head_point = next_idx_modulo(head_point, points.size());
|
||||
head_window_acc += distances_for_curvature[head_point];
|
||||
head_angle_acc += angles_for_curvature[head_point];
|
||||
Vec2d front_position = current.position;
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
float new_curvature = angle(current.position - back_position, front_position - current.position) / window_size;
|
||||
if (abs(current.curvature) < abs(new_curvature)) {
|
||||
current.curvature = new_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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -595,8 +595,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
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_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_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_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));
|
||||
|
190
src/libslic3r/GCode/LabelObjects.cpp
Normal file
190
src/libslic3r/GCode/LabelObjects.cpp
Normal 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
|
42
src/libslic3r/GCode/LabelObjects.hpp
Normal file
42
src/libslic3r/GCode/LabelObjects.hpp
Normal 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_
|
@ -146,4 +146,83 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations, double* min_error)
|
||||
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
|
||||
|
@ -141,6 +141,13 @@ Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20);
|
||||
// Find circle using RANSAC randomized algorithm.
|
||||
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.
|
||||
template<typename Vector, typename Points>
|
||||
CircleSq<Vector> smallest_enclosing_circle2_welzl(const Points &points, const typename Vector::Scalar epsilon)
|
||||
|
@ -229,6 +229,13 @@ static const t_config_enum_values s_keys_map_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 = {
|
||||
{ "PNG", int(GCodeThumbnailsFormat::PNG) },
|
||||
{ "JPG", int(GCodeThumbnailsFormat::JPG) },
|
||||
@ -1493,13 +1500,20 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comExpert;
|
||||
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->tooltip = L("Enable this to add comments into the G-Code labeling print moves with what object they belong to,"
|
||||
" which is useful for the Octoprint CancelObject plugin. This settings is NOT compatible with "
|
||||
"Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill.");
|
||||
def->tooltip = L("Selects whether labels should be exported at object boundaries and in what format.\n"
|
||||
" OctoPrint = comments to be consumed by OctoPrint CancelObject plugin.\n"
|
||||
" 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->set_default_value(new ConfigOptionBool(0));
|
||||
def->set_default_value(new ConfigOptionEnum<LabelObjectsStyle>(LabelObjectsStyle::Disabled));
|
||||
|
||||
def = this->add("gcode_substitutions", coStrings);
|
||||
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")) {
|
||||
// draft_shield used to be a bool, it was turned into an enum in PrusaSlicer 2.4.0.
|
||||
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") {
|
||||
opt_key = "print_host";
|
||||
} else if (opt_key == "octoprint_cafile") {
|
||||
|
@ -147,6 +147,10 @@ enum DraftShield {
|
||||
dsDisabled, dsLimited, dsEnabled
|
||||
};
|
||||
|
||||
enum class LabelObjectsStyle {
|
||||
Disabled, Octoprint, Firmware
|
||||
};
|
||||
|
||||
enum class PerimeterGeneratorType
|
||||
{
|
||||
// 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(BrimType)
|
||||
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(ForwardCompatibilitySubstitutionRule)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
|
||||
@ -729,7 +734,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloats, filament_multitool_ramming_flow))
|
||||
((ConfigOptionBool, gcode_comments))
|
||||
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
|
||||
((ConfigOptionBool, gcode_label_objects))
|
||||
((ConfigOptionEnum<LabelObjectsStyle>, gcode_label_objects))
|
||||
// Triples of strings: "search pattern", "replace with pattern", "attribs"
|
||||
// where "attribs" are one of:
|
||||
// r - regular expression
|
||||
|
@ -166,7 +166,7 @@ struct SliceConnection
|
||||
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;
|
||||
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 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),
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(below_polys, slice_bb));
|
||||
|
||||
for (const Polygon &poly : overlap) {
|
||||
Vec2f p0 = unscaled(poly.first_point()).cast<float>();
|
||||
for (size_t i = 2; i < poly.points.size(); i++) {
|
||||
Vec2f p1 = unscaled(poly.points[i - 1]).cast<float>();
|
||||
Vec2f p2 = unscaled(poly.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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
const Integrals integrals{overlap};
|
||||
connection.area += integrals.area;
|
||||
connection.centroid_accumulator += Vec3f(integrals.x_i.x(), integrals.x_i.y(), layer->print_z * integrals.area);
|
||||
connection.second_moment_of_area_accumulator += integrals.x_i_squared;
|
||||
connection.second_moment_of_area_covariance_accumulator += integrals.xy;
|
||||
|
||||
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
|
||||
{
|
||||
public:
|
||||
@ -482,43 +534,22 @@ public:
|
||||
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 ¢roid_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 Vec2f &line_dir,
|
||||
const Vec3f &extreme_point,
|
||||
const Vec3f ¢roid_accumulator,
|
||||
const Vec2f &second_moment_of_area_accumulator,
|
||||
const float &second_moment_of_area_covariance_accumulator,
|
||||
const float &area) const
|
||||
{
|
||||
float directional_xy_variance = compute_directional_xy_variance(line_dir, centroid_accumulator, second_moment_of_area_accumulator,
|
||||
second_moment_of_area_covariance_accumulator, area);
|
||||
if (directional_xy_variance < EPSILON) { return 0.0f; }
|
||||
Vec3f centroid = centroid_accumulator / area;
|
||||
const Integrals& integrals
|
||||
) const {
|
||||
float second_moment_of_area = compute_second_moment(integrals, Vec2f{-line_dir.y(), line_dir.x()});
|
||||
|
||||
if (second_moment_of_area < EPSILON) { return 0.0f; }
|
||||
|
||||
Vec2f centroid = integrals.x_i / integrals.area;
|
||||
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>()),
|
||||
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
|
||||
BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist;
|
||||
@ -534,6 +565,12 @@ public:
|
||||
float layer_z,
|
||||
const Params ¶ms) 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();
|
||||
const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume;
|
||||
float mass = this->volume * params.filament_density;
|
||||
@ -548,19 +585,19 @@ public:
|
||||
{
|
||||
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;
|
||||
float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator,
|
||||
this->sticking_second_moment_of_area_accumulator,
|
||||
this->sticking_second_moment_of_area_covariance_accumulator,
|
||||
this->sticking_area) *
|
||||
params.get_bed_adhesion_yield_strength();
|
||||
float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, integrals) * params.get_bed_adhesion_yield_strength();
|
||||
|
||||
Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>());
|
||||
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,
|
||||
this->sticking_second_moment_of_area_covariance_accumulator,
|
||||
this->sticking_area);
|
||||
|
||||
float bed_weight_dir_xy_variance = compute_second_moment(integrals, {-bed_weight_arm.y(), bed_weight_arm.x()}) / 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_torque = bed_weight_sign * bed_weight_arm_len * weight;
|
||||
|
||||
@ -600,11 +637,14 @@ public:
|
||||
Vec3f conn_centroid = connection.centroid_accumulator / connection.area;
|
||||
|
||||
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,
|
||||
connection.second_moment_of_area_covariance_accumulator,
|
||||
connection.area) *
|
||||
params.material_yield_strength;
|
||||
|
||||
Integrals integrals;
|
||||
integrals.area = connection.area;
|
||||
integrals.x_i = connection.centroid_accumulator.head<2>();
|
||||
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();
|
||||
if (layer_z - conn_centroid.z() < 30.0) {
|
||||
|
@ -147,6 +147,33 @@ struct PartialObject
|
||||
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>;
|
||||
|
||||
// Both support points and partial objects are sorted from the lowest z to the highest
|
||||
|
@ -277,7 +277,6 @@ void EditGCodeDialog::add_selected_value_to_gcode()
|
||||
if (val.IsEmpty())
|
||||
return;
|
||||
|
||||
const long pos = m_gcode_editor->GetInsertionPoint();
|
||||
m_gcode_editor->WriteText(m_gcode_editor->GetInsertionPoint() == m_gcode_editor->GetLastPosition() ? "\n" + val : val);
|
||||
|
||||
if (val.Last() == ']') {
|
||||
|
@ -4735,6 +4735,7 @@ void SubstitutionManager::update_from_config()
|
||||
if (m_substitutions == subst && m_grid_sizer->IsShown(1)) {
|
||||
// just update visibility for chb_match_single_lines
|
||||
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) {
|
||||
const std::string& params = subst[i + 2];
|
||||
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();
|
||||
call_ui_update();
|
||||
|
||||
if (!m_grid_sizer->IsEmpty())
|
||||
if (!m_grid_sizer->IsEmpty()) {
|
||||
m_grid_sizer->Clear(true);
|
||||
m_chb_match_single_lines.clear();
|
||||
}
|
||||
|
||||
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.set_cb_edited_substitution([this]() {
|
||||
update_dirty();
|
||||
Layout();
|
||||
wxGetApp().mainframe->on_config_changed(m_config); // invalidate print
|
||||
});
|
||||
m_subst_manager.set_cb_hide_delete_all_btn([this]() {
|
||||
|
@ -39,6 +39,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_astar.cpp
|
||||
test_anyptr.cpp
|
||||
test_jump_point_search.cpp
|
||||
test_support_spots_generator.cpp
|
||||
../data/prusaparts.cpp
|
||||
../data/prusaparts.hpp
|
||||
)
|
||||
|
97
tests/libslic3r/test_support_spots_generator.cpp
Normal file
97
tests/libslic3r/test_support_spots_generator.cpp
Normal 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));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user