Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into vb_et_instances_synch
This commit is contained in:
commit
92d26883a5
@ -4499,6 +4499,8 @@ filament_soluble = 1
|
|||||||
filament_type = PVB
|
filament_type = PVB
|
||||||
filament_colour = #FFFF6F
|
filament_colour = #FFFF6F
|
||||||
filament_spool_weight = 201
|
filament_spool_weight = 201
|
||||||
|
bed_temperature = 75
|
||||||
|
first_layer_bed_temperature = 75
|
||||||
slowdown_below_layer_time = 20
|
slowdown_below_layer_time = 20
|
||||||
filament_ramming_parameters = "120 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6"
|
filament_ramming_parameters = "120 110 1.74194 1.90323 2.16129 2.48387 2.83871 3.25806 3.83871 4.6129 5.41935 5.96774| 0.05 1.69677 0.45 1.96128 0.95 2.63872 1.45 3.46129 1.95 4.99031 2.45 6.12908 2.95 8.30974 3.45 11.4065 3.95 7.6 4.45 7.6 4.95 7.6"
|
||||||
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.05{else}0.08{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K24{elsif nozzle_diameter[0]==0.8};{else}M900 K45{endif} ; Filament gcode LA 1.0"
|
start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.6}0.12{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/ and nozzle_diameter[0]==0.8}0.06{elsif printer_notes=~/.*PRINTER_MODEL_MINI.*/}0.2{elsif nozzle_diameter[0]==0.8}0.02{elsif nozzle_diameter[0]==0.6}0.05{else}0.08{endif} ; Filament gcode LA 1.5\n{if printer_notes=~/.*PRINTER_MODEL_MINI.*/};{elsif printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}M900 K200{elsif nozzle_diameter[0]==0.6}M900 K24{elsif nozzle_diameter[0]==0.8};{else}M900 K45{endif} ; Filament gcode LA 1.0"
|
||||||
|
@ -776,7 +776,7 @@ public:
|
|||||||
static int nil_value() { return std::numeric_limits<int>::max(); }
|
static int nil_value() { return std::numeric_limits<int>::max(); }
|
||||||
// A scalar is nil, or all values of a vector are nil.
|
// A scalar is nil, or all values of a vector are nil.
|
||||||
bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; }
|
bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; }
|
||||||
bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); }
|
bool is_nil(size_t idx) const override { return values[idx < this->values.size() ? idx : 0] == nil_value(); }
|
||||||
|
|
||||||
std::string serialize() const override
|
std::string serialize() const override
|
||||||
{
|
{
|
||||||
|
@ -32,8 +32,8 @@ public:
|
|||||||
ExPolygon& operator=(const ExPolygon &other) = default;
|
ExPolygon& operator=(const ExPolygon &other) = default;
|
||||||
ExPolygon& operator=(ExPolygon &&other) = default;
|
ExPolygon& operator=(ExPolygon &&other) = default;
|
||||||
|
|
||||||
Polygon contour;
|
Polygon contour; //CCW
|
||||||
Polygons holes;
|
Polygons holes; //CW
|
||||||
|
|
||||||
void clear() { contour.points.clear(); holes.clear(); }
|
void clear() { contour.points.clear(); holes.clear(); }
|
||||||
void scale(double factor);
|
void scale(double factor);
|
||||||
|
@ -113,26 +113,19 @@ namespace Slic3r {
|
|||||||
{
|
{
|
||||||
std::string gcode;
|
std::string gcode;
|
||||||
|
|
||||||
// move to the nearest standby point
|
unsigned int extruder_id = gcodegen.writer().extruder()->id();
|
||||||
if (!this->standby_points.empty()) {
|
const ConfigOptionIntsNullable& filament_idle_temp = gcodegen.config().idle_temperature;
|
||||||
// get current position in print coordinates
|
if (filament_idle_temp.is_nil(extruder_id)) {
|
||||||
Vec3d writer_pos = gcodegen.writer().get_position();
|
// There is no idle temperature defined in filament settings.
|
||||||
Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
|
// Use the delta value from print config.
|
||||||
|
if (gcodegen.config().standby_temperature_delta.value != 0) {
|
||||||
// find standby point
|
// we assume that heating is always slower than cooling, so no need to block
|
||||||
Point standby_point = nearest_point(this->standby_points, pos).first;
|
gcode += gcodegen.writer().set_temperature
|
||||||
|
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
|
||||||
/* We don't call gcodegen.travel_to() because we don't need retraction (it was already
|
}
|
||||||
triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
|
} else {
|
||||||
of the destination point must not be transformed by origin nor current extruder offset. */
|
// Use the value from filament settings. That one is absolute, not delta.
|
||||||
gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
|
gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
|
||||||
"move to standby position");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gcodegen.config().standby_temperature_delta.value != 0) {
|
|
||||||
// we assume that heating is always slower than cooling, so no need to block
|
|
||||||
gcode += gcodegen.writer().set_temperature
|
|
||||||
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gcode;
|
return gcode;
|
||||||
@ -145,8 +138,7 @@ namespace Slic3r {
|
|||||||
std::string();
|
std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int OozePrevention::_get_temp(const GCode& gcodegen) const
|
||||||
OozePrevention::_get_temp(GCode& gcodegen)
|
|
||||||
{
|
{
|
||||||
return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0)
|
return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0)
|
||||||
? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
|
? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
|
||||||
@ -238,8 +230,16 @@ namespace Slic3r {
|
|||||||
|
|
||||||
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
|
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
|
||||||
|
|
||||||
if (! tcr.priming) {
|
double current_z = gcodegen.writer().get_position().z();
|
||||||
// Move over the wipe tower.
|
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
||||||
|
z = current_z;
|
||||||
|
|
||||||
|
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
|
||||||
|
const bool will_go_down = ! is_approx(z, current_z);
|
||||||
|
|
||||||
|
if (! needs_toolchange || (gcodegen.config().single_extruder_multi_material && ! tcr.priming)) {
|
||||||
|
// Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the
|
||||||
|
// toolchange will travel there anyway (if there is a toolchange).
|
||||||
gcode += gcodegen.retract();
|
gcode += gcodegen.retract();
|
||||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||||
gcode += gcodegen.travel_to(
|
gcode += gcodegen.travel_to(
|
||||||
@ -248,82 +248,36 @@ namespace Slic3r {
|
|||||||
"Travel to a Wipe Tower");
|
"Travel to a Wipe Tower");
|
||||||
gcode += gcodegen.unretract();
|
gcode += gcodegen.unretract();
|
||||||
}
|
}
|
||||||
|
|
||||||
double current_z = gcodegen.writer().get_position().z();
|
if (will_go_down) {
|
||||||
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
|
||||||
z = current_z;
|
|
||||||
if (! is_approx(z, current_z)) {
|
|
||||||
gcode += gcodegen.writer().retract();
|
gcode += gcodegen.writer().retract();
|
||||||
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
|
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
|
||||||
gcode += gcodegen.writer().unretract();
|
gcode += gcodegen.writer().unretract();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Process the end filament gcode.
|
|
||||||
std::string end_filament_gcode_str;
|
|
||||||
if (gcodegen.writer().extruder() != nullptr) {
|
|
||||||
// Process the custom end_filament_gcode in case of single_extruder_multi_material.
|
|
||||||
unsigned int old_extruder_id = gcodegen.writer().extruder()->id();
|
|
||||||
const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id);
|
|
||||||
if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) {
|
|
||||||
end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
|
|
||||||
check_add_eol(end_filament_gcode_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament.
|
|
||||||
// Otherwise, leave control to the user completely.
|
|
||||||
std::string toolchange_gcode_str;
|
std::string toolchange_gcode_str;
|
||||||
const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
|
std::string deretraction_str;
|
||||||
if (! toolchange_gcode.empty()) {
|
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
|
||||||
DynamicConfig config;
|
if (gcodegen.config().single_extruder_multi_material)
|
||||||
int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
|
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||||
config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
|
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||||
config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
|
if (gcodegen.config().wipe_tower)
|
||||||
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
|
deretraction_str = gcodegen.unretract();
|
||||||
config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
|
|
||||||
config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
|
|
||||||
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
|
|
||||||
toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config);
|
|
||||||
check_add_eol(toolchange_gcode_str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string toolchange_command;
|
|
||||||
if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)))
|
|
||||||
toolchange_command = gcodegen.writer().toolchange(new_extruder_id);
|
|
||||||
if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id))
|
|
||||||
toolchange_gcode_str += toolchange_command;
|
|
||||||
else {
|
|
||||||
// We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
|
|
||||||
}
|
|
||||||
|
|
||||||
gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
|
|
||||||
|
|
||||||
// Process the start filament gcode.
|
|
||||||
std::string start_filament_gcode_str;
|
|
||||||
const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
|
|
||||||
if (!start_filament_gcode.empty()) {
|
|
||||||
// Process the start_filament_gcode for the active filament only.
|
|
||||||
DynamicConfig config;
|
|
||||||
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
|
|
||||||
config.set_key_value("layer_z", new ConfigOptionFloat(gcodegen.writer().get_position()(2) - gcodegen.m_config.z_offset.value));
|
|
||||||
config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
|
|
||||||
config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id));
|
|
||||||
start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config);
|
|
||||||
check_add_eol(start_filament_gcode_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert the end filament, toolchange, and start filament gcode into the generated gcode.
|
// Insert the toolchange and deretraction gcode into the generated gcode.
|
||||||
DynamicConfig config;
|
DynamicConfig config;
|
||||||
config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str));
|
|
||||||
config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
|
config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
|
||||||
config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str));
|
config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
|
||||||
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
|
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
|
||||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||||
gcode += tcr_gcode;
|
gcode += tcr_gcode;
|
||||||
check_add_eol(toolchange_gcode_str);
|
check_add_eol(toolchange_gcode_str);
|
||||||
|
|
||||||
|
|
||||||
// A phony move to the end position at the wipe tower.
|
// A phony move to the end position at the wipe tower.
|
||||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||||
@ -898,34 +852,7 @@ namespace DoExport {
|
|||||||
|
|
||||||
static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
|
static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
|
||||||
{
|
{
|
||||||
// Calculate wiping points if needed
|
ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
|
||||||
if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
|
|
||||||
Points skirt_points;
|
|
||||||
for (const ExtrusionEntity *ee : print.skirt().entities)
|
|
||||||
for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
|
|
||||||
append(skirt_points, path.polyline.points);
|
|
||||||
if (! skirt_points.empty()) {
|
|
||||||
Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
|
|
||||||
Polygons skirts;
|
|
||||||
for (unsigned int extruder_id : print.extruders()) {
|
|
||||||
const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
|
|
||||||
Polygon s(outer_skirt);
|
|
||||||
s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1)));
|
|
||||||
skirts.emplace_back(std::move(s));
|
|
||||||
}
|
|
||||||
ooze_prevention.enable = true;
|
|
||||||
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.)));
|
|
||||||
#if 0
|
|
||||||
require "Slic3r/SVG.pm";
|
|
||||||
Slic3r::SVG::output(
|
|
||||||
"ooze_prevention.svg",
|
|
||||||
red_polygons => \@skirts,
|
|
||||||
polygons => [$outer_skirt],
|
|
||||||
points => $gcodegen->ooze_prevention->standby_points,
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
|
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
|
||||||
@ -1263,6 +1190,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
|
m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
|
||||||
m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
|
m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
|
||||||
m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
|
m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
|
||||||
|
|
||||||
|
std::vector<unsigned char> is_extruder_used(print.config().nozzle_diameter.size(), 0);
|
||||||
|
for (unsigned int extruder_id : print.extruders())
|
||||||
|
is_extruder_used[extruder_id] = true;
|
||||||
|
m_placeholder_parser.set("is_extruder_used", new ConfigOptionBools(is_extruder_used));
|
||||||
}
|
}
|
||||||
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
|
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
|
||||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||||
@ -1282,8 +1214,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
// Set other general things.
|
// Set other general things.
|
||||||
file.write(this->preamble());
|
file.write(this->preamble());
|
||||||
|
|
||||||
// Calculate wiping points if needed
|
// Enable ooze prevention if configured so.
|
||||||
DoExport::init_ooze_prevention(print, m_ooze_prevention);
|
DoExport::init_ooze_prevention(print, m_ooze_prevention);
|
||||||
|
|
||||||
print.throw_if_canceled();
|
print.throw_if_canceled();
|
||||||
|
|
||||||
// Collect custom seam data from all objects.
|
// Collect custom seam data from all objects.
|
||||||
@ -1814,7 +1747,7 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr
|
|||||||
m_writer.set_temperature(temp, wait, first_printing_extruder_id);
|
m_writer.set_temperature(temp, wait, first_printing_extruder_id);
|
||||||
} else {
|
} else {
|
||||||
// Custom G-code does not set the extruder temperature. Do it now.
|
// Custom G-code does not set the extruder temperature. Do it now.
|
||||||
if (print.config().single_extruder_multi_material.value) {
|
if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) {
|
||||||
// Set temperature of the first printing extruder only.
|
// Set temperature of the first printing extruder only.
|
||||||
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
|
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
|
||||||
if (temp > 0)
|
if (temp > 0)
|
||||||
@ -2136,11 +2069,14 @@ LayerResult GCode::process_layer(
|
|||||||
// Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
|
// Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
|
||||||
// first_layer_temperature vs. temperature settings.
|
// first_layer_temperature vs. temperature settings.
|
||||||
for (const Extruder &extruder : m_writer.extruders()) {
|
for (const Extruder &extruder : m_writer.extruders()) {
|
||||||
if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id())
|
if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) {
|
||||||
// In single extruder multi material mode, set the temperature for the current extruder only.
|
// In single extruder multi material mode, set the temperature for the current extruder only.
|
||||||
continue;
|
// The same applies when ooze prevention is enabled.
|
||||||
|
if (extruder.id() != m_writer.extruder()->id())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
int temperature = print.config().temperature.get_at(extruder.id());
|
int temperature = print.config().temperature.get_at(extruder.id());
|
||||||
if (temperature > 0 && temperature != print.config().first_layer_temperature.get_at(extruder.id()))
|
if (temperature > 0 && (temperature != print.config().first_layer_temperature.get_at(extruder.id())))
|
||||||
gcode += m_writer.set_temperature(temperature, false, extruder.id());
|
gcode += m_writer.set_temperature(temperature, false, extruder.id());
|
||||||
}
|
}
|
||||||
gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id));
|
gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id));
|
||||||
@ -3186,6 +3122,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||||||
if (! start_filament_gcode.empty()) {
|
if (! start_filament_gcode.empty()) {
|
||||||
// Process the start_filament_gcode for the filament.
|
// Process the start_filament_gcode for the filament.
|
||||||
DynamicConfig config;
|
DynamicConfig config;
|
||||||
|
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||||
|
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value));
|
||||||
|
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
|
||||||
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
|
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
|
||||||
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
|
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
|
||||||
check_add_eol(gcode);
|
check_add_eol(gcode);
|
||||||
@ -3201,8 +3140,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||||||
m_wipe.reset_path();
|
m_wipe.reset_path();
|
||||||
|
|
||||||
if (m_writer.extruder() != nullptr) {
|
if (m_writer.extruder() != nullptr) {
|
||||||
// Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower
|
// Process the custom end_filament_gcode.
|
||||||
// so it should not be injected twice.
|
|
||||||
unsigned int old_extruder_id = m_writer.extruder()->id();
|
unsigned int old_extruder_id = m_writer.extruder()->id();
|
||||||
const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id);
|
const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id);
|
||||||
if (! end_filament_gcode.empty()) {
|
if (! end_filament_gcode.empty()) {
|
||||||
@ -3212,8 +3150,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// If ooze prevention is enabled, park current extruder in the nearest
|
// If ooze prevention is enabled, set current extruder to the standby temperature.
|
||||||
// standby point and set it to the standby temperature.
|
|
||||||
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
|
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
|
||||||
gcode += m_ooze_prevention.pre_toolchange(*this);
|
gcode += m_ooze_prevention.pre_toolchange(*this);
|
||||||
|
|
||||||
@ -3257,6 +3194,9 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||||||
if (! start_filament_gcode.empty()) {
|
if (! start_filament_gcode.empty()) {
|
||||||
// Process the start_filament_gcode for the new filament.
|
// Process the start_filament_gcode for the new filament.
|
||||||
DynamicConfig config;
|
DynamicConfig config;
|
||||||
|
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
|
||||||
|
config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position()(2) - m_config.z_offset.value));
|
||||||
|
config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
|
||||||
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
|
config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
|
||||||
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
|
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
|
||||||
check_add_eol(gcode);
|
check_add_eol(gcode);
|
||||||
|
@ -39,14 +39,13 @@ struct PrintInstance;
|
|||||||
class OozePrevention {
|
class OozePrevention {
|
||||||
public:
|
public:
|
||||||
bool enable;
|
bool enable;
|
||||||
Points standby_points;
|
|
||||||
|
|
||||||
OozePrevention() : enable(false) {}
|
OozePrevention() : enable(false) {}
|
||||||
std::string pre_toolchange(GCode &gcodegen);
|
std::string pre_toolchange(GCode &gcodegen);
|
||||||
std::string post_toolchange(GCode &gcodegen);
|
std::string post_toolchange(GCode &gcodegen);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _get_temp(GCode &gcodegen);
|
int _get_temp(const GCode &gcodegen) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Wipe {
|
class Wipe {
|
||||||
|
@ -122,39 +122,18 @@ std::vector<ExtendedPoint> estimate_points_properties(const std::vector<P>
|
|||||||
CurvatureEstimator cestim;
|
CurvatureEstimator cestim;
|
||||||
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
|
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
|
||||||
|
|
||||||
std::vector<P> extrusion_points;
|
|
||||||
{
|
|
||||||
if (max_line_length <= 0) {
|
|
||||||
extrusion_points = input_points;
|
|
||||||
} else {
|
|
||||||
extrusion_points.reserve(input_points.size() * 2);
|
|
||||||
for (size_t i = 0; i + 1 < input_points.size(); i++) {
|
|
||||||
const P &curr = input_points[i];
|
|
||||||
const P &next = input_points[i + 1];
|
|
||||||
extrusion_points.push_back(curr);
|
|
||||||
auto len = maybe_unscale(next - curr).squaredNorm();
|
|
||||||
double t = sqrt((max_line_length * max_line_length) / len);
|
|
||||||
size_t new_point_count = 1.0 / (t + EPSILON);
|
|
||||||
for (size_t j = 1; j < new_point_count + 1; j++) {
|
|
||||||
extrusion_points.push_back(curr * (1.0 - j * t) + next * (j * t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extrusion_points.push_back(input_points.back());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ExtendedPoint> points;
|
std::vector<ExtendedPoint> points;
|
||||||
points.reserve(extrusion_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
|
points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
|
||||||
|
|
||||||
{
|
{
|
||||||
ExtendedPoint start_point{maybe_unscale(extrusion_points.front())};
|
ExtendedPoint start_point{maybe_unscale(input_points.front())};
|
||||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
||||||
start_point.distance = distance + boundary_offset;
|
start_point.distance = distance + boundary_offset;
|
||||||
start_point.nearest_prev_layer_line = nearest_line;
|
start_point.nearest_prev_layer_line = nearest_line;
|
||||||
points.push_back(start_point);
|
points.push_back(start_point);
|
||||||
}
|
}
|
||||||
for (size_t i = 1; i < extrusion_points.size(); i++) {
|
for (size_t i = 1; i < input_points.size(); i++) {
|
||||||
ExtendedPoint next_point{maybe_unscale(extrusion_points[i])};
|
ExtendedPoint next_point{maybe_unscale(input_points[i])};
|
||||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
||||||
next_point.distance = distance + boundary_offset;
|
next_point.distance = distance + boundary_offset;
|
||||||
next_point.nearest_prev_layer_line = nearest_line;
|
next_point.nearest_prev_layer_line = nearest_line;
|
||||||
@ -172,7 +151,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const std::vector<P>
|
|||||||
|
|
||||||
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
|
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
|
||||||
std::vector<ExtendedPoint> new_points;
|
std::vector<ExtendedPoint> new_points;
|
||||||
new_points.reserve(points.size() * 2);
|
new_points.reserve(points.size()*2);
|
||||||
new_points.push_back(points.front());
|
new_points.push_back(points.front());
|
||||||
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
|
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
|
||||||
const ExtendedPoint &curr = points[point_idx];
|
const ExtendedPoint &curr = points[point_idx];
|
||||||
@ -201,7 +180,30 @@ std::vector<ExtendedPoint> estimate_points_properties(const std::vector<P>
|
|||||||
}
|
}
|
||||||
new_points.push_back(next);
|
new_points.push_back(next);
|
||||||
}
|
}
|
||||||
points = std::move(new_points);
|
points = new_points;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_line_length > 0) {
|
||||||
|
std::vector<ExtendedPoint> new_points;
|
||||||
|
new_points.reserve(points.size()*2);
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i + 1 < points.size(); i++) {
|
||||||
|
const ExtendedPoint &curr = points[i];
|
||||||
|
const ExtendedPoint &next = points[i + 1];
|
||||||
|
new_points.push_back(curr);
|
||||||
|
double len = (next.position - curr.position).squaredNorm();
|
||||||
|
double t = sqrt((max_line_length * max_line_length) / len);
|
||||||
|
size_t new_point_count = 1.0 / t;
|
||||||
|
for (size_t j = 1; j < new_point_count + 1; j++) {
|
||||||
|
Vec2d pos = curr.position * (1.0 - j * t) + next.position * (j * t);
|
||||||
|
auto [p_dist, p_near_l,
|
||||||
|
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.cast<AABBScalar>());
|
||||||
|
new_points.push_back(ExtendedPoint{pos, float(p_dist + boundary_offset), p_near_l});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_points.push_back(points.back());
|
||||||
|
}
|
||||||
|
points = new_points;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
||||||
|
@ -71,6 +71,8 @@ public:
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WipeTowerWriter& set_position(const Vec2f &pos) { m_current_pos = pos; return *this; }
|
||||||
|
|
||||||
WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; }
|
WipeTowerWriter& set_initial_tool(size_t tool) { m_current_tool = tool; return *this; }
|
||||||
|
|
||||||
WipeTowerWriter& set_z(float z)
|
WipeTowerWriter& set_z(float z)
|
||||||
@ -802,22 +804,26 @@ void WipeTower::toolchange_Unload(
|
|||||||
{
|
{
|
||||||
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
|
float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width;
|
||||||
float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width;
|
float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width;
|
||||||
|
|
||||||
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
|
const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
|
||||||
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
|
const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
|
||||||
|
|
||||||
|
const Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f);
|
||||||
|
|
||||||
writer.append("; CP TOOLCHANGE UNLOAD\n")
|
writer.append("; CP TOOLCHANGE UNLOAD\n")
|
||||||
.change_analyzer_line_width(line_width);
|
.change_analyzer_line_width(line_width);
|
||||||
|
|
||||||
unsigned i = 0; // iterates through ramming_speed
|
unsigned i = 0; // iterates through ramming_speed
|
||||||
m_left_to_right = true; // current direction of ramming
|
m_left_to_right = true; // current direction of ramming
|
||||||
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
||||||
float e_done = 0; // measures E move done from each segment
|
float e_done = 0; // measures E move done from each segment
|
||||||
|
|
||||||
writer.travel(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f ); // move to starting position
|
|
||||||
|
|
||||||
|
if (m_semm)
|
||||||
|
writer.travel(ramming_start_pos); // move to starting position
|
||||||
|
else
|
||||||
|
writer.set_position(ramming_start_pos);
|
||||||
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
|
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
|
||||||
if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) {
|
if (m_semm && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||||
|
|
||||||
// this is y of the center of previous sparse infill border
|
// this is y of the center of previous sparse infill border
|
||||||
float sparse_beginning_y = 0.f;
|
float sparse_beginning_y = 0.f;
|
||||||
@ -849,7 +855,7 @@ void WipeTower::toolchange_Unload(
|
|||||||
writer.disable_linear_advance();
|
writer.disable_linear_advance();
|
||||||
|
|
||||||
// now the ramming itself:
|
// now the ramming itself:
|
||||||
while (i < m_filpar[m_current_tool].ramming_speed.size())
|
while (m_semm && i < m_filpar[m_current_tool].ramming_speed.size())
|
||||||
{
|
{
|
||||||
const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height);
|
const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height);
|
||||||
const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move;
|
const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move;
|
||||||
@ -898,7 +904,7 @@ void WipeTower::toolchange_Unload(
|
|||||||
|
|
||||||
// Cooling:
|
// Cooling:
|
||||||
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
|
const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
|
||||||
if (number_of_moves > 0) {
|
if (m_semm && number_of_moves > 0) {
|
||||||
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
|
const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
|
||||||
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
|
const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
|
||||||
|
|
||||||
@ -916,14 +922,20 @@ void WipeTower::toolchange_Unload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// let's wait is necessary:
|
if (m_semm) {
|
||||||
writer.wait(m_filpar[m_current_tool].delay);
|
// let's wait is necessary:
|
||||||
// we should be at the beginning of the cooling tube again - let's move to parking position:
|
writer.wait(m_filpar[m_current_tool].delay);
|
||||||
writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
|
// we should be at the beginning of the cooling tube again - let's move to parking position:
|
||||||
|
writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||||
writer.travel(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f);
|
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||||
|
if (m_semm)
|
||||||
|
writer.travel(pos, 2400.f);
|
||||||
|
else
|
||||||
|
writer.set_position(pos);
|
||||||
|
|
||||||
writer.resume_preview()
|
writer.resume_preview()
|
||||||
.flush_planner_queue();
|
.flush_planner_queue();
|
||||||
@ -941,7 +953,7 @@ void WipeTower::toolchange_Change(
|
|||||||
|
|
||||||
// This is where we want to place the custom gcodes. We will use placeholders for this.
|
// This is where we want to place the custom gcodes. We will use placeholders for this.
|
||||||
// These will be substituted by the actual gcodes when the gcode is generated.
|
// These will be substituted by the actual gcodes when the gcode is generated.
|
||||||
writer.append("[end_filament_gcode]\n");
|
//writer.append("[end_filament_gcode]\n");
|
||||||
writer.append("[toolchange_gcode]\n");
|
writer.append("[toolchange_gcode]\n");
|
||||||
|
|
||||||
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
||||||
@ -952,11 +964,12 @@ void WipeTower::toolchange_Change(
|
|||||||
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
|
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
|
||||||
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
|
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
|
||||||
+ never_skip_tag() + "\n");
|
+ never_skip_tag() + "\n");
|
||||||
|
writer.append("[deretraction_from_wipe_tower_generator]");
|
||||||
|
|
||||||
// The toolchange Tn command will be inserted later, only in case that the user does
|
// The toolchange Tn command will be inserted later, only in case that the user does
|
||||||
// not provide a custom toolchange gcode.
|
// not provide a custom toolchange gcode.
|
||||||
writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed.
|
writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed.
|
||||||
writer.append("[start_filament_gcode]\n");
|
//writer.append("[start_filament_gcode]\n");
|
||||||
|
|
||||||
writer.flush_planner_queue();
|
writer.flush_planner_queue();
|
||||||
m_current_tool = new_tool;
|
m_current_tool = new_tool;
|
||||||
|
@ -290,7 +290,6 @@ private:
|
|||||||
// Extruder specific parameters.
|
// Extruder specific parameters.
|
||||||
std::vector<FilamentParameters> m_filpar;
|
std::vector<FilamentParameters> m_filpar;
|
||||||
|
|
||||||
|
|
||||||
// State of the wipe tower generator.
|
// State of the wipe tower generator.
|
||||||
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
|
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
|
||||||
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
|
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
|
||||||
|
@ -470,7 +470,7 @@ static std::vector<std::string> s_Preset_filament_options {
|
|||||||
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
|
"extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time",
|
||||||
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
|
"filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves",
|
||||||
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
|
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
|
||||||
"temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
|
"temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
|
||||||
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
|
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
|
||||||
"start_filament_gcode", "end_filament_gcode",
|
"start_filament_gcode", "end_filament_gcode",
|
||||||
// Retract overrides
|
// Retract overrides
|
||||||
|
@ -191,6 +191,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
|| opt_key == "infill_first"
|
|| opt_key == "infill_first"
|
||||||
|| opt_key == "single_extruder_multi_material"
|
|| opt_key == "single_extruder_multi_material"
|
||||||
|| opt_key == "temperature"
|
|| opt_key == "temperature"
|
||||||
|
|| opt_key == "idle_temperature"
|
||||||
|| opt_key == "wipe_tower"
|
|| opt_key == "wipe_tower"
|
||||||
|| opt_key == "wipe_tower_width"
|
|| opt_key == "wipe_tower_width"
|
||||||
|| opt_key == "wipe_tower_brim_width"
|
|| opt_key == "wipe_tower_brim_width"
|
||||||
@ -347,7 +348,7 @@ std::vector<ObjectID> Print::print_object_ids() const
|
|||||||
|
|
||||||
bool Print::has_infinite_skirt() const
|
bool Print::has_infinite_skirt() const
|
||||||
{
|
{
|
||||||
return (m_config.draft_shield == dsEnabled && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
|
return (m_config.draft_shield == dsEnabled && m_config.skirts > 0)/* || (m_config.ooze_prevention && this->extruders().size() > 1)*/;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Print::has_skirt() const
|
bool Print::has_skirt() const
|
||||||
@ -505,8 +506,8 @@ std::string Print::validate(std::string* warning) const
|
|||||||
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.");
|
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.");
|
||||||
if (! m_config.use_relative_e_distances)
|
if (! m_config.use_relative_e_distances)
|
||||||
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
|
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
|
||||||
if (m_config.ooze_prevention)
|
if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
|
||||||
return L("Ooze prevention is currently not supported with the wipe tower enabled.");
|
return L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.");
|
||||||
if (m_config.use_volumetric_e)
|
if (m_config.use_volumetric_e)
|
||||||
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
|
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
|
||||||
if (m_config.complete_objects && extruders.size() > 1)
|
if (m_config.complete_objects && extruders.size() > 1)
|
||||||
|
@ -229,6 +229,11 @@ static void assign_printer_technology_to_unknown(t_optiondef_map &options, Print
|
|||||||
kvp.second.printer_technology = printer_technology;
|
kvp.second.printer_technology = printer_technology;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
|
||||||
|
namespace {
|
||||||
|
const int max_temp = 1500;
|
||||||
|
};
|
||||||
|
|
||||||
PrintConfigDef::PrintConfigDef()
|
PrintConfigDef::PrintConfigDef()
|
||||||
{
|
{
|
||||||
this->init_common_params();
|
this->init_common_params();
|
||||||
@ -1972,9 +1977,7 @@ void PrintConfigDef::init_fff_params()
|
|||||||
|
|
||||||
def = this->add("ooze_prevention", coBool);
|
def = this->add("ooze_prevention", coBool);
|
||||||
def->label = L("Enable");
|
def->label = L("Enable");
|
||||||
def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. "
|
def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. ");
|
||||||
"It will enable a tall skirt automatically and move extruders outside such "
|
|
||||||
"skirt when changing temperatures.");
|
|
||||||
def->mode = comExpert;
|
def->mode = comExpert;
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
@ -2475,7 +2478,8 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def = this->add("standby_temperature_delta", coInt);
|
def = this->add("standby_temperature_delta", coInt);
|
||||||
def->label = L("Temperature variation");
|
def->label = L("Temperature variation");
|
||||||
def->tooltip = L("Temperature difference to be applied when an extruder is not active. "
|
def->tooltip = L("Temperature difference to be applied when an extruder is not active. "
|
||||||
"Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped.");
|
"The value is not used when 'idle_temperature' in filament settings "
|
||||||
|
"is defined.");
|
||||||
def->sidetext = "∆°C";
|
def->sidetext = "∆°C";
|
||||||
def->min = -max_temp;
|
def->min = -max_temp;
|
||||||
def->max = max_temp;
|
def->max = max_temp;
|
||||||
@ -3731,6 +3735,15 @@ void PrintConfigDef::init_sla_params()
|
|||||||
def->min = 0;
|
def->min = 0;
|
||||||
def->set_default_value(new ConfigOptionFloat(0.3));
|
def->set_default_value(new ConfigOptionFloat(0.3));
|
||||||
|
|
||||||
|
def = this->add_nullable("idle_temperature", coInts);
|
||||||
|
def->label = L("Idle temperature");
|
||||||
|
def->tooltip = L("Nozzle temperature when the tool is currently not used in multi-tool setups."
|
||||||
|
"This is only used when 'Ooze prevention is active in Print Settings.'");
|
||||||
|
def->sidetext = L("°C");
|
||||||
|
def->min = 0;
|
||||||
|
def->max = max_temp;
|
||||||
|
def->set_default_value(new ConfigOptionIntsNullable { ConfigOptionIntsNullable::nil_value() });
|
||||||
|
|
||||||
def = this->add("bottle_volume", coFloat);
|
def = this->add("bottle_volume", coFloat);
|
||||||
def->label = L("Bottle volume");
|
def->label = L("Bottle volume");
|
||||||
def->tooltip = L("Bottle volume");
|
def->tooltip = L("Bottle volume");
|
||||||
@ -4511,6 +4524,12 @@ std::string validate(const FullPrintConfig &cfg)
|
|||||||
assert(opt != nullptr);
|
assert(opt != nullptr);
|
||||||
const ConfigOptionDef *optdef = print_config_def.get(opt_key);
|
const ConfigOptionDef *optdef = print_config_def.get(opt_key);
|
||||||
assert(optdef != nullptr);
|
assert(optdef != nullptr);
|
||||||
|
|
||||||
|
if (opt->nullable() && opt->is_nil()) {
|
||||||
|
// Do not check nil values
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bool out_of_range = false;
|
bool out_of_range = false;
|
||||||
switch (opt->type()) {
|
switch (opt->type()) {
|
||||||
case coFloat:
|
case coFloat:
|
||||||
|
@ -769,6 +769,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
((ConfigOptionFloatOrPercent, first_layer_height))
|
((ConfigOptionFloatOrPercent, first_layer_height))
|
||||||
((ConfigOptionFloatOrPercent, first_layer_speed))
|
((ConfigOptionFloatOrPercent, first_layer_speed))
|
||||||
((ConfigOptionInts, first_layer_temperature))
|
((ConfigOptionInts, first_layer_temperature))
|
||||||
|
((ConfigOptionIntsNullable, idle_temperature))
|
||||||
((ConfigOptionInts, full_fan_speed_layer))
|
((ConfigOptionInts, full_fan_speed_layer))
|
||||||
((ConfigOptionFloat, infill_acceleration))
|
((ConfigOptionFloat, infill_acceleration))
|
||||||
((ConfigOptionBool, infill_first))
|
((ConfigOptionBool, infill_first))
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#include "Exception.hpp"
|
#include "Exception.hpp"
|
||||||
|
#include "KDTreeIndirect.hpp"
|
||||||
|
#include "Point.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
#include "BoundingBox.hpp"
|
#include "BoundingBox.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
@ -21,14 +23,19 @@
|
|||||||
#include "SupportSpotsGenerator.hpp"
|
#include "SupportSpotsGenerator.hpp"
|
||||||
#include "TriangleSelectorWrapper.hpp"
|
#include "TriangleSelectorWrapper.hpp"
|
||||||
#include "format.hpp"
|
#include "format.hpp"
|
||||||
|
#include "libslic3r.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
@ -406,32 +413,58 @@ void PrintObject::ironing()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
std::vector<size_t> problematic_layers = SupportSpotsGenerator::quick_search(this);
|
|
||||||
if (!problematic_layers.empty()) {
|
|
||||||
std::cout << "Object needs supports" << std::endl;
|
|
||||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
|
|
||||||
L("Supportable issues found. Consider enabling supports for this object"));
|
|
||||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
|
|
||||||
L("Supportable issues found. Consider enabling supports for this object"));
|
|
||||||
for (size_t index = 0; index < std::min(problematic_layers.size(), size_t(4)); ++index) {
|
|
||||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
|
|
||||||
format(L("Layer with issues: %1%"), problematic_layers[index] + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
void PrintObject::generate_support_spots()
|
void PrintObject::generate_support_spots()
|
||||||
{
|
{
|
||||||
if (this->set_started(posSupportSpotsSearch)) {
|
if (this->set_started(posSupportSpotsSearch)) {
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Searching support spots - start";
|
BOOST_LOG_TRIVIAL(debug) << "Searching support spots - start";
|
||||||
m_print->set_status(75, L("Searching support spots"));
|
m_print->set_status(75, L("Searching support spots"));
|
||||||
if (!this->shared_regions()->generated_support_points.has_value()) {
|
if (!this->shared_regions()->generated_support_points.has_value()) {
|
||||||
PrintTryCancel cancel_func = m_print->make_try_cancel();
|
PrintTryCancel cancel_func = m_print->make_try_cancel();
|
||||||
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values};
|
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values,
|
||||||
SupportSpotsGenerator::SupportPoints supp_points = SupportSpotsGenerator::full_search(this, cancel_func, params);
|
float(this->print()->m_config.perimeter_acceleration.getFloat()),
|
||||||
|
this->config().raft_layers.getInt(), this->config().brim_type.value,
|
||||||
|
float(this->config().brim_width.getFloat())};
|
||||||
|
auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params);
|
||||||
this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points};
|
this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points};
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
|
|
||||||
|
auto alert_fn = [&](PrintStateBase::WarningLevel level, SupportSpotsGenerator::SupportPointCause cause) {
|
||||||
|
switch (cause) {
|
||||||
|
case SupportSpotsGenerator::SupportPointCause::LongBridge:
|
||||||
|
this->active_step_add_warning(level, L("There are bridges longer than recommended length. Consider adding supports. ") +
|
||||||
|
(L("Object name")) + ": " + this->model_object()->name);
|
||||||
|
break;
|
||||||
|
case SupportSpotsGenerator::SupportPointCause::FloatingBridgeAnchor:
|
||||||
|
this->active_step_add_warning(level, L("Unsupported bridges will collapse. Supports are needed. ") + (L("Object name")) +
|
||||||
|
": " + this->model_object()->name);
|
||||||
|
break;
|
||||||
|
case SupportSpotsGenerator::SupportPointCause::FloatingExtrusion:
|
||||||
|
if (level == PrintStateBase::WarningLevel::CRITICAL) {
|
||||||
|
this->active_step_add_warning(level, L("Clusters of unsupported extrusions found. Supports are needed. ") +
|
||||||
|
(L("Object name")) + ": " + this->model_object()->name);
|
||||||
|
} else {
|
||||||
|
this->active_step_add_warning(level, L("Some unspported extrusions found. Consider adding supports. ") +
|
||||||
|
(L("Object name")) + ": " + this->model_object()->name);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SupportSpotsGenerator::SupportPointCause::SeparationFromBed:
|
||||||
|
this->active_step_add_warning(level, L("Object part may break from the bed. Consider adding brim and/or supports. ") +
|
||||||
|
(L("Object name")) + ": " + this->model_object()->name);
|
||||||
|
break;
|
||||||
|
case SupportSpotsGenerator::SupportPointCause::UnstableFloatingPart:
|
||||||
|
this->active_step_add_warning(level, L("Floating object parts detected. Supports are needed. ") + (L("Object name")) +
|
||||||
|
": " + this->model_object()->name);
|
||||||
|
break;
|
||||||
|
case SupportSpotsGenerator::SupportPointCause::WeakObjectPart:
|
||||||
|
this->active_step_add_warning(level, L("Thin parts of the object may break. Consider adding supports. ") +
|
||||||
|
(L("Object name")) + ": " + this->model_object()->name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this->has_support()) {
|
||||||
|
SupportSpotsGenerator::raise_alerts_for_issues(supp_points, partial_objects, alert_fn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Searching support spots - end";
|
BOOST_LOG_TRIVIAL(debug) << "Searching support spots - end";
|
||||||
this->set_done(posSupportSpotsSearch);
|
this->set_done(posSupportSpotsSearch);
|
||||||
@ -468,7 +501,10 @@ void PrintObject::estimate_curled_extrusions()
|
|||||||
|
|
||||||
// Estimate curling of support material and add it to the malformaition lines of each layer
|
// Estimate curling of support material and add it to the malformaition lines of each layer
|
||||||
float support_flow_width = support_material_flow(this, this->config().layer_height).width();
|
float support_flow_width = support_material_flow(this, this->config().layer_height).width();
|
||||||
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values};
|
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values,
|
||||||
|
float(this->print()->m_config.perimeter_acceleration.getFloat()),
|
||||||
|
this->config().raft_layers.getInt(), this->config().brim_type.value,
|
||||||
|
float(this->config().brim_width.getFloat())};
|
||||||
SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params);
|
SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params);
|
||||||
SupportSpotsGenerator::estimate_malformations(this->layers(), params);
|
SupportSpotsGenerator::estimate_malformations(this->layers(), params);
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
@ -580,6 +616,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
if ( opt_key == "brim_width"
|
if ( opt_key == "brim_width"
|
||||||
|| opt_key == "brim_separation"
|
|| opt_key == "brim_separation"
|
||||||
|| opt_key == "brim_type") {
|
|| opt_key == "brim_type") {
|
||||||
|
steps.emplace_back(posSupportSpotsSearch);
|
||||||
// Brim is printed below supports, support invalidates brim and skirt.
|
// Brim is printed below supports, support invalidates brim and skirt.
|
||||||
steps.emplace_back(posSupportMaterial);
|
steps.emplace_back(posSupportMaterial);
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
#define SLAPRINT_DO_BENCHMARK
|
// #define SLAPRINT_DO_BENCHMARK
|
||||||
|
|
||||||
#ifdef SLAPRINT_DO_BENCHMARK
|
#ifdef SLAPRINT_DO_BENCHMARK
|
||||||
#include <libnest2d/tools/benchmark.h>
|
#include <libnest2d/tools/benchmark.h>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "PrincipalComponents2D.hpp"
|
#include "PrincipalComponents2D.hpp"
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
#include "PrintBase.hpp"
|
#include "PrintBase.hpp"
|
||||||
|
#include "PrintConfig.hpp"
|
||||||
#include "Tesselate.hpp"
|
#include "Tesselate.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "tbb/parallel_for.h"
|
#include "tbb/parallel_for.h"
|
||||||
@ -23,6 +24,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
@ -68,7 +70,7 @@ public:
|
|||||||
float len;
|
float len;
|
||||||
const ExtrusionEntity *origin_entity;
|
const ExtrusionEntity *origin_entity;
|
||||||
|
|
||||||
bool support_point_generated = false;
|
std::optional<SupportSpotsGenerator::SupportPointCause> support_point_generated = {};
|
||||||
float form_quality = 1.0f;
|
float form_quality = 1.0f;
|
||||||
float curled_up_height = 0.0f;
|
float curled_up_height = 0.0f;
|
||||||
|
|
||||||
@ -81,10 +83,6 @@ auto get_b(ExtrusionLine &&l) { return l.b; }
|
|||||||
|
|
||||||
namespace SupportSpotsGenerator {
|
namespace SupportSpotsGenerator {
|
||||||
|
|
||||||
SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction)
|
|
||||||
: position(position), force(force), spot_radius(spot_radius), direction(direction)
|
|
||||||
{}
|
|
||||||
|
|
||||||
using LD = AABBTreeLines::LinesDistancer<ExtrusionLine>;
|
using LD = AABBTreeLines::LinesDistancer<ExtrusionLine>;
|
||||||
|
|
||||||
struct SupportGridFilter
|
struct SupportGridFilter
|
||||||
@ -240,13 +238,66 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
|||||||
checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end());
|
checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end());
|
||||||
}
|
}
|
||||||
return checked_lines_out;
|
return checked_lines_out;
|
||||||
|
} else if (entity->role().is_bridge() && !entity->role().is_perimeter()) {
|
||||||
|
// pure bridges are handled separately, beacuse we need to align the forward and backward direction support points
|
||||||
|
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const float flow_width = get_flow_width(layer_region, entity->role());
|
||||||
|
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, true, true>(entity->as_polyline().points,
|
||||||
|
prev_layer_boundary, flow_width,
|
||||||
|
params.bridge_distance);
|
||||||
|
|
||||||
|
std::vector<ExtrusionLine> lines_out;
|
||||||
|
lines_out.reserve(annotated_points.size());
|
||||||
|
float bridged_distance = 0.0f;
|
||||||
|
|
||||||
|
std::optional<Vec2d> bridging_dir{};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||||
|
ExtendedPoint &curr_point = annotated_points[i];
|
||||||
|
ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i - 1];
|
||||||
|
|
||||||
|
SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor :
|
||||||
|
SupportPointCause::LongBridge;
|
||||||
|
float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f;
|
||||||
|
Vec2d line_dir = (curr_point.position - prev_point.position).normalized();
|
||||||
|
|
||||||
|
ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast<float>() : curr_point.position.cast<float>(),
|
||||||
|
curr_point.position.cast<float>(), line_len, entity};
|
||||||
|
|
||||||
|
float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f,
|
||||||
|
params.bridge_distance /
|
||||||
|
((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) *
|
||||||
|
(1.0f + std::abs(curr_point.curvature))));
|
||||||
|
|
||||||
|
if (!bridging_dir.has_value() && curr_point.distance > flow_width && line_len > params.bridge_distance * 0.6) {
|
||||||
|
bridging_dir = (prev_point.position - curr_point.position).normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr_point.distance > flow_width && potential_cause == SupportPointCause::LongBridge && bridging_dir.has_value() &&
|
||||||
|
bridging_dir->dot(line_dir) < 0.8) { // skip backward direction of bridge - supported by forward points enough
|
||||||
|
bridged_distance += line_len;
|
||||||
|
} else if (curr_point.distance > flow_width) {
|
||||||
|
bridged_distance += line_len;
|
||||||
|
if (bridged_distance > max_bridge_len) {
|
||||||
|
bridged_distance = 0.0f;
|
||||||
|
line_out.support_point_generated = potential_cause;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bridged_distance = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines_out.push_back(line_out);
|
||||||
|
}
|
||||||
|
return lines_out;
|
||||||
|
|
||||||
} else { // single extrusion path, with possible varying parameters
|
} else { // single extrusion path, with possible varying parameters
|
||||||
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
|
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const float flow_width = get_flow_width(layer_region, entity->role());
|
const float flow_width = get_flow_width(layer_region, entity->role());
|
||||||
|
|
||||||
// Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
|
// Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
|
||||||
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
|
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
|
||||||
prev_layer_lines, flow_width,
|
prev_layer_lines, flow_width,
|
||||||
@ -263,28 +314,51 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
|||||||
curr_point.position.cast<float>(), line_len, entity};
|
curr_point.position.cast<float>(), line_len, entity};
|
||||||
|
|
||||||
const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ?
|
const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ?
|
||||||
prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) :
|
prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) :
|
||||||
ExtrusionLine{};
|
ExtrusionLine{};
|
||||||
|
|
||||||
// correctify the distance sign using slice polygons
|
// correctify the distance sign using slice polygons
|
||||||
float sign = (prev_layer_boundary.distance_from_lines<true>(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f;
|
float sign = (prev_layer_boundary.distance_from_lines<true>(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f;
|
||||||
curr_point.distance *= sign;
|
curr_point.distance *= sign;
|
||||||
|
|
||||||
float max_bridge_len = params.bridge_distance /
|
SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion;
|
||||||
((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)));
|
if (bridged_distance + line_len > params.bridge_distance * 0.8 && std::abs(curr_point.curvature) < 0.1) {
|
||||||
|
potential_cause = SupportPointCause::FloatingExtrusion;
|
||||||
|
}
|
||||||
|
|
||||||
|
float max_bridge_len = std::max(params.support_points_interface_radius * 2.0f,
|
||||||
|
params.bridge_distance /
|
||||||
|
((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)) *
|
||||||
|
(1.0f + std::abs(curr_point.curvature))));
|
||||||
|
|
||||||
if (curr_point.distance > 2.0f * flow_width) {
|
if (curr_point.distance > 2.0f * flow_width) {
|
||||||
line_out.form_quality = 0.8f;
|
line_out.form_quality = 0.8f;
|
||||||
bridged_distance += line_len;
|
bridged_distance += line_len;
|
||||||
if (bridged_distance > max_bridge_len) {
|
if (bridged_distance > max_bridge_len) {
|
||||||
line_out.support_point_generated = true;
|
std::cout << "Problem found A: " << std::endl;
|
||||||
|
std::cout << "bridged_distance: " << bridged_distance << std::endl;
|
||||||
|
std::cout << "max_bridge_len: " << max_bridge_len << std::endl;
|
||||||
|
std::cout << "line_out.form_quality: " << line_out.form_quality << std::endl;
|
||||||
|
std::cout << "curr_point.distance: " << curr_point.distance << std::endl;
|
||||||
|
std::cout << "curr_point.curvature: " << curr_point.curvature << std::endl;
|
||||||
|
std::cout << "flow_width: " << flow_width << std::endl;
|
||||||
|
|
||||||
|
line_out.support_point_generated = potential_cause;
|
||||||
bridged_distance = 0.0f;
|
bridged_distance = 0.0f;
|
||||||
}
|
}
|
||||||
} else if (curr_point.distance > flow_width * (1.0 + std::clamp(curr_point.curvature, -0.30f, 0.20f))) {
|
} else if (curr_point.distance > flow_width * (1.0 + std::clamp(curr_point.curvature, -0.30f, 0.20f))) {
|
||||||
bridged_distance += line_len;
|
bridged_distance += line_len;
|
||||||
line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f;
|
line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f;
|
||||||
if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) {
|
if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) {
|
||||||
line_out.support_point_generated = true;
|
std::cout << "Problem found B: " << std::endl;
|
||||||
|
std::cout << "bridged_distance: " << bridged_distance << std::endl;
|
||||||
|
std::cout << "max_bridge_len: " << max_bridge_len << std::endl;
|
||||||
|
std::cout << "line_out.form_quality: " << line_out.form_quality << std::endl;
|
||||||
|
std::cout << "curr_point.distance: " << curr_point.distance << std::endl;
|
||||||
|
std::cout << "curr_point.curvature: " << curr_point.curvature << std::endl;
|
||||||
|
std::cout << "flow_width: " << flow_width << std::endl;
|
||||||
|
|
||||||
|
line_out.support_point_generated = potential_cause;
|
||||||
line_out.form_quality = 0.5f;
|
line_out.form_quality = 0.5f;
|
||||||
bridged_distance = 0.0f;
|
bridged_distance = 0.0f;
|
||||||
}
|
}
|
||||||
@ -349,11 +423,13 @@ public:
|
|||||||
Vec3f sticking_centroid_accumulator = Vec3f::Zero();
|
Vec3f sticking_centroid_accumulator = Vec3f::Zero();
|
||||||
Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero();
|
Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero();
|
||||||
float sticking_second_moment_of_area_covariance_accumulator{};
|
float sticking_second_moment_of_area_covariance_accumulator{};
|
||||||
|
bool connected_to_bed = false;
|
||||||
|
|
||||||
ObjectPart() = default;
|
ObjectPart() = default;
|
||||||
|
|
||||||
void add(const ObjectPart &other)
|
void add(const ObjectPart &other)
|
||||||
{
|
{
|
||||||
|
this->connected_to_bed = this->connected_to_bed || other.connected_to_bed;
|
||||||
this->volume_centroid_accumulator += other.volume_centroid_accumulator;
|
this->volume_centroid_accumulator += other.volume_centroid_accumulator;
|
||||||
this->volume += other.volume;
|
this->volume += other.volume;
|
||||||
this->sticking_area += other.sticking_area;
|
this->sticking_area += other.sticking_area;
|
||||||
@ -416,7 +492,7 @@ public:
|
|||||||
return elastic_section_modulus;
|
return elastic_section_modulus;
|
||||||
}
|
}
|
||||||
|
|
||||||
float is_stable_while_extruding(const SliceConnection &connection,
|
std::tuple<float, SupportPointCause> is_stable_while_extruding(const SliceConnection &connection,
|
||||||
const ExtrusionLine &extruded_line,
|
const ExtrusionLine &extruded_line,
|
||||||
const Vec3f &extreme_point,
|
const Vec3f &extreme_point,
|
||||||
float layer_z,
|
float layer_z,
|
||||||
@ -434,7 +510,7 @@ public:
|
|||||||
|
|
||||||
// section for bed calculations
|
// section for bed calculations
|
||||||
{
|
{
|
||||||
if (this->sticking_area < EPSILON) return 1.0f;
|
if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart};
|
||||||
|
|
||||||
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, this->sticking_centroid_accumulator,
|
||||||
@ -475,16 +551,19 @@ public:
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z;
|
BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (bed_total_torque > 0) return bed_total_torque / bed_conflict_torque_arm;
|
if (bed_total_torque > 0) {
|
||||||
|
return {bed_total_torque / bed_conflict_torque_arm,
|
||||||
|
(this->connected_to_bed ? SupportPointCause::SeparationFromBed : SupportPointCause::UnstableFloatingPart)};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// section for weak connection calculations
|
// section for weak connection calculations
|
||||||
{
|
{
|
||||||
if (connection.area < EPSILON) return 1.0f;
|
if (connection.area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart};
|
||||||
|
|
||||||
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; }
|
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,
|
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_accumulator,
|
||||||
connection.second_moment_of_area_covariance_accumulator,
|
connection.second_moment_of_area_covariance_accumulator,
|
||||||
@ -492,7 +571,7 @@ public:
|
|||||||
params.material_yield_strength;
|
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();
|
||||||
float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z);
|
float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z) * (1.0f - conn_centroid.z() / layer_z);
|
||||||
|
|
||||||
float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z());
|
float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z());
|
||||||
float conn_movement_torque = movement_force * conn_movement_arm;
|
float conn_movement_torque = movement_force * conn_movement_arm;
|
||||||
@ -514,18 +593,20 @@ public:
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z;
|
BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return conn_total_torque / conn_conflict_torque_arm;
|
return {conn_total_torque / conn_conflict_torque_arm, SupportPointCause::WeakObjectPart};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// return new object part and actual area covered by extrusions
|
// return new object part and actual area covered by extrusions
|
||||||
std::tuple<ObjectPart, float> build_object_part_from_slice(const LayerSlice &slice, const Layer *layer)
|
std::tuple<ObjectPart, float> build_object_part_from_slice(const size_t &slice_idx, const Layer *layer, const Params& params)
|
||||||
{
|
{
|
||||||
ObjectPart new_object_part;
|
ObjectPart new_object_part;
|
||||||
float area_covered_by_extrusions = 0;
|
float area_covered_by_extrusions = 0;
|
||||||
|
const LayerSlice& slice = layer->lslices_ex.at(slice_idx);
|
||||||
|
|
||||||
auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions](const ExtrusionEntity *e, const LayerRegion *region) {
|
auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions, ¶ms](const ExtrusionEntity *e,
|
||||||
|
const LayerRegion *region) {
|
||||||
float flow_width = get_flow_width(region, e->role());
|
float flow_width = get_flow_width(region, e->role());
|
||||||
const Layer *l = region->layer();
|
const Layer *l = region->layer();
|
||||||
float slice_z = l->slice_z;
|
float slice_z = l->slice_z;
|
||||||
@ -537,8 +618,9 @@ std::tuple<ObjectPart, float> build_object_part_from_slice(const LayerSlice &sli
|
|||||||
new_object_part.volume += volume;
|
new_object_part.volume += volume;
|
||||||
new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume;
|
new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume;
|
||||||
|
|
||||||
if (l->bottom_z() < EPSILON) { // layer attached on bed
|
if (l->id() == params.raft_layers_count) { // layer attached on bed/raft
|
||||||
float sticking_area = line.len * flow_width;
|
new_object_part.connected_to_bed = true;
|
||||||
|
float sticking_area = line.len * flow_width;
|
||||||
new_object_part.sticking_area += sticking_area;
|
new_object_part.sticking_area += sticking_area;
|
||||||
Vec2f middle = Vec2f((line.a + line.b) / 2.0f);
|
Vec2f middle = Vec2f((line.a + line.b) / 2.0f);
|
||||||
new_object_part.sticking_centroid_accumulator += sticking_area * to_3d(middle, slice_z);
|
new_object_part.sticking_centroid_accumulator += sticking_area * to_3d(middle, slice_z);
|
||||||
@ -579,6 +661,49 @@ std::tuple<ObjectPart, float> build_object_part_from_slice(const LayerSlice &sli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BRIM HANDLING
|
||||||
|
if (layer->id() == params.raft_layers_count && params.raft_layers_count == 0 && params.brim_type != BrimType::btNoBrim) {
|
||||||
|
// TODO: The algorithm here should take into account that multiple slices may have coliding Brim areas and the final brim area is
|
||||||
|
// smaller,
|
||||||
|
// thus has lower adhesion. For now this effect will be neglected.
|
||||||
|
ExPolygon slice_poly = layer->lslices[slice_idx];
|
||||||
|
ExPolygons brim;
|
||||||
|
if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btOuterOnly) {
|
||||||
|
Polygon brim_hole = slice_poly.contour;
|
||||||
|
brim_hole.reverse();
|
||||||
|
brim.push_back(ExPolygon{expand(slice_poly.contour, scale_(params.brim_width)).front(), brim_hole});
|
||||||
|
}
|
||||||
|
if (params.brim_type == BrimType::btOuterAndInner || params.brim_type == BrimType::btInnerOnly) {
|
||||||
|
Polygons brim_contours = slice_poly.holes;
|
||||||
|
polygons_reverse(brim_contours);
|
||||||
|
for (const Polygon &brim_contour : brim_contours) {
|
||||||
|
Polygons brim_holes = shrink({brim_contour}, scale_(params.brim_width));
|
||||||
|
polygons_reverse(brim_holes);
|
||||||
|
ExPolygon inner_brim{brim_contour};
|
||||||
|
inner_brim.holes = brim_holes;
|
||||||
|
brim.push_back(inner_brim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Polygon &poly : to_polygons(brim)) {
|
||||||
|
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);
|
||||||
|
new_object_part.sticking_area += sign * area;
|
||||||
|
new_object_part.sticking_centroid_accumulator += sign * Vec3f(first_moment_of_area.x(), first_moment_of_area.y(),
|
||||||
|
layer->print_z * area);
|
||||||
|
new_object_part.sticking_second_moment_of_area_accumulator += sign * second_moment_area;
|
||||||
|
new_object_part.sticking_second_moment_of_area_covariance_accumulator += sign * second_moment_of_area_covariance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {new_object_part, area_covered_by_extrusions};
|
return {new_object_part, area_covered_by_extrusions};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,11 +746,12 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms)
|
std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po, const PrintTryCancel &cancel_func, const Params ¶ms)
|
||||||
{
|
{
|
||||||
SupportPoints supp_points{};
|
SupportPoints supp_points{};
|
||||||
SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points);
|
SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points);
|
||||||
ActiveObjectParts active_object_parts{};
|
ActiveObjectParts active_object_parts{};
|
||||||
|
PartialObjects partial_objects{};
|
||||||
LD prev_layer_ext_perim_lines;
|
LD prev_layer_ext_perim_lines;
|
||||||
|
|
||||||
std::unordered_map<size_t, size_t> prev_slice_idx_to_object_part_mapping;
|
std::unordered_map<size_t, size_t> prev_slice_idx_to_object_part_mapping;
|
||||||
@ -633,6 +759,14 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
std::unordered_map<size_t, SliceConnection> prev_slice_idx_to_weakest_connection;
|
std::unordered_map<size_t, SliceConnection> prev_slice_idx_to_weakest_connection;
|
||||||
std::unordered_map<size_t, SliceConnection> next_slice_idx_to_weakest_connection;
|
std::unordered_map<size_t, SliceConnection> next_slice_idx_to_weakest_connection;
|
||||||
|
|
||||||
|
auto remember_partial_object = [&active_object_parts, &partial_objects](size_t object_part_id) {
|
||||||
|
auto object_part = active_object_parts.access(object_part_id);
|
||||||
|
if (object_part.volume > EPSILON) {
|
||||||
|
partial_objects.emplace_back(object_part.volume_centroid_accumulator / object_part.volume, object_part.volume,
|
||||||
|
object_part.connected_to_bed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) {
|
for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) {
|
||||||
cancel_func();
|
cancel_func();
|
||||||
const Layer *layer = po->get_layer(layer_idx);
|
const Layer *layer = po->get_layer(layer_idx);
|
||||||
@ -641,7 +775,7 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
|
|
||||||
for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) {
|
for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) {
|
||||||
const LayerSlice &slice = layer->lslices_ex.at(slice_idx);
|
const LayerSlice &slice = layer->lslices_ex.at(slice_idx);
|
||||||
auto [new_part, covered_area] = build_object_part_from_slice(slice, layer);
|
auto [new_part, covered_area] = build_object_part_from_slice(slice_idx, layer, params);
|
||||||
SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer);
|
SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer);
|
||||||
|
|
||||||
#ifdef DETAILED_DEBUG_LOGS
|
#ifdef DETAILED_DEBUG_LOGS
|
||||||
@ -670,7 +804,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
|
|
||||||
final_part_id = *parts_ids.begin();
|
final_part_id = *parts_ids.begin();
|
||||||
for (size_t part_id : parts_ids) {
|
for (size_t part_id : parts_ids) {
|
||||||
if (final_part_id != part_id) { active_object_parts.merge(part_id, final_part_id); }
|
if (final_part_id != part_id) {
|
||||||
|
remember_partial_object(part_id);
|
||||||
|
active_object_parts.merge(part_id, final_part_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto estimate_conn_strength = [bottom_z](const SliceConnection &conn) {
|
auto estimate_conn_strength = [bottom_z](const SliceConnection &conn) {
|
||||||
@ -731,24 +868,25 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
// Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info,
|
// Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info,
|
||||||
// and the support presence grid and add the point to the issues.
|
// and the support presence grid and add the point to the issues.
|
||||||
auto reckon_new_support_point = [&part, &weakest_conn, &supp_points, &supports_presence_grid, ¶ms,
|
auto reckon_new_support_point = [&part, &weakest_conn, &supp_points, &supports_presence_grid, ¶ms,
|
||||||
&layer_idx](const Vec3f &support_point, float force, const Vec2f &dir) {
|
&layer_idx](SupportPointCause cause, const Vec3f &support_point, float force,
|
||||||
|
const Vec2f &dir) {
|
||||||
// if position is taken and point is for global stability (force > 0) or we are too close to the bed, do not add
|
// if position is taken and point is for global stability (force > 0) or we are too close to the bed, do not add
|
||||||
// This allows local support points (e.g. bridging) to be generated densely
|
// This allows local support points (e.g. bridging) to be generated densely
|
||||||
if ((supports_presence_grid.position_taken(support_point) && force > 0) || layer_idx <= 1) {
|
if ((supports_presence_grid.position_taken(support_point) && force > 0) || layer_idx <= 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI);
|
float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI);
|
||||||
// add the stability effect of the point only if the spot is not taken, so that the densely created local support points do not add
|
// add the stability effect of the point only if the spot is not taken, so that the densely created local support points do
|
||||||
// unrealistic amount of stability to the object (due to overlaping of local support points)
|
// not add unrealistic amount of stability to the object (due to overlaping of local support points)
|
||||||
if (!(supports_presence_grid.position_taken(support_point))) {
|
if (!(supports_presence_grid.position_taken(support_point))) {
|
||||||
part.add_support_point(support_point, area);
|
part.add_support_point(support_point, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
float radius = params.support_points_interface_radius;
|
float radius = params.support_points_interface_radius;
|
||||||
supp_points.emplace_back(support_point, force, radius, dir);
|
supp_points.emplace_back(cause, support_point, force, radius, dir);
|
||||||
supports_presence_grid.take_position(support_point);
|
supports_presence_grid.take_position(support_point);
|
||||||
|
|
||||||
// The support point also increases the stability of the weakest connection of the object, which should be reflected
|
// The support point also increases the stability of the weakest connection of the object, which should be reflected
|
||||||
if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist
|
if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist
|
||||||
weakest_conn.area += area;
|
weakest_conn.area += area;
|
||||||
@ -768,9 +906,11 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx];
|
const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx];
|
||||||
if (entity->role() == ExtrusionRole::BridgeInfill) {
|
if (entity->role() == ExtrusionRole::BridgeInfill) {
|
||||||
for (const ExtrusionLine &bridge :
|
for (const ExtrusionLine &bridge :
|
||||||
check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines,prev_layer_boundary, params)) {
|
check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, prev_layer_boundary,
|
||||||
if (bridge.support_point_generated) {
|
params)) {
|
||||||
reckon_new_support_point(create_support_point_position(bridge.b), -EPSILON, Vec2f::Zero());
|
if (bridge.support_point_generated.has_value()) {
|
||||||
|
reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b),
|
||||||
|
-EPSILON, Vec2f::Zero());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -783,10 +923,13 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
std::vector<ExtrusionLine> perims = check_extrusion_entity_stability(entity, perimeter_region,
|
std::vector<ExtrusionLine> perims = check_extrusion_entity_stability(entity, perimeter_region,
|
||||||
prev_layer_ext_perim_lines,prev_layer_boundary, params);
|
prev_layer_ext_perim_lines,prev_layer_boundary, params);
|
||||||
for (const ExtrusionLine &perim : perims) {
|
for (const ExtrusionLine &perim : perims) {
|
||||||
if (perim.support_point_generated) {
|
if (perim.support_point_generated.has_value()) {
|
||||||
reckon_new_support_point(create_support_point_position(perim.b), -EPSILON, Vec2f::Zero());
|
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON,
|
||||||
|
Vec2f::Zero());
|
||||||
|
}
|
||||||
|
if (perim.is_external_perimeter()) {
|
||||||
|
current_slice_ext_perims_lines.push_back(perim);
|
||||||
}
|
}
|
||||||
if (perim.is_external_perimeter()) { current_slice_ext_perims_lines.push_back(perim); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -795,7 +938,8 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
float unchecked_dist = params.min_distance_between_support_points + 1.0f;
|
float unchecked_dist = params.min_distance_between_support_points + 1.0f;
|
||||||
|
|
||||||
for (const ExtrusionLine &line : current_slice_ext_perims_lines) {
|
for (const ExtrusionLine &line : current_slice_ext_perims_lines) {
|
||||||
if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) || line.len < EPSILON) {
|
if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) ||
|
||||||
|
line.len < EPSILON) {
|
||||||
unchecked_dist += line.len;
|
unchecked_dist += line.len;
|
||||||
} else {
|
} else {
|
||||||
unchecked_dist = line.len;
|
unchecked_dist = line.len;
|
||||||
@ -803,8 +947,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
auto [dist, nidx,
|
auto [dist, nidx,
|
||||||
nearest_point] = current_slice_lines_distancer.distance_from_lines_extra<false>(pivot_site_search_point);
|
nearest_point] = current_slice_lines_distancer.distance_from_lines_extra<false>(pivot_site_search_point);
|
||||||
Vec3f support_point = create_support_point_position(nearest_point);
|
Vec3f support_point = create_support_point_position(nearest_point);
|
||||||
auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params);
|
auto [force, cause] = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params);
|
||||||
if (force > 0) { reckon_new_support_point(support_point, force, (line.b - line.a).normalized()); }
|
if (force > 0) {
|
||||||
|
reckon_new_support_point(cause, support_point, force, (line.b - line.a).normalized());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
current_layer_ext_perims_lines.insert(current_layer_ext_perims_lines.end(), current_slice_ext_perims_lines.begin(),
|
current_layer_ext_perims_lines.insert(current_layer_ext_perims_lines.end(), current_slice_ext_perims_lines.begin(),
|
||||||
@ -812,11 +958,16 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
|||||||
} // slice iterations
|
} // slice iterations
|
||||||
prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines);
|
prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines);
|
||||||
} // layer iterations
|
} // layer iterations
|
||||||
return supp_points;
|
|
||||||
|
for (const auto& active_obj_pair : prev_slice_idx_to_object_part_mapping) {
|
||||||
|
remember_partial_object(active_obj_pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {supp_points, partial_objects};
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_FILES
|
#ifdef DEBUG_FILES
|
||||||
void debug_export(SupportPoints support_points, std::string file_name)
|
void debug_export(const SupportPoints& support_points,const PartialObjects& objects, std::string file_name)
|
||||||
{
|
{
|
||||||
Slic3r::CNumericLocalesSetter locales_setter;
|
Slic3r::CNumericLocalesSetter locales_setter;
|
||||||
{
|
{
|
||||||
@ -827,13 +978,27 @@ void debug_export(SupportPoints support_points, std::string file_name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < support_points.size(); ++i) {
|
for (size_t i = 0; i < support_points.size(); ++i) {
|
||||||
if (support_points[i].force <= 0) {
|
Vec3f color{1.0f, 1.0f, 1.0f};
|
||||||
fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1),
|
switch (support_points[i].cause) {
|
||||||
support_points[i].position(2), 0.0, 1.0, 0.0);
|
case SupportPointCause::FloatingBridgeAnchor: color = {0.863281f, 0.109375f, 0.113281f}; break; //RED
|
||||||
} else {
|
case SupportPointCause::LongBridge: color = {0.960938f, 0.90625f, 0.0625f}; break; // YELLOW
|
||||||
fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1),
|
case SupportPointCause::FloatingExtrusion: color = {0.921875f, 0.515625f, 0.101563f}; break; // ORANGE
|
||||||
support_points[i].position(2), 1.0, 0.0, 0.0);
|
case SupportPointCause::SeparationFromBed: color = {0.0f, 1.0f, 0.0}; break; // GREEN
|
||||||
|
case SupportPointCause::UnstableFloatingPart: color = {0.105469f, 0.699219f, 0.84375f}; break; // BLUE
|
||||||
|
case SupportPointCause::WeakObjectPart: color = {0.609375f, 0.210938f, 0.621094f}; break; // PURPLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1),
|
||||||
|
support_points[i].position(2), color[0], color[1], color[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < objects.size(); ++i) {
|
||||||
|
Vec3f color{1.0f, 0.0f, 1.0f};
|
||||||
|
if (objects[i].connected_to_bed) {
|
||||||
|
color = {1.0f, 0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
fprintf(fp, "v %f %f %f %f %f %f\n", objects[i].centroid(0), objects[i].centroid(1), objects[i].centroid(2), color[0],
|
||||||
|
color[1], color[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
@ -841,17 +1006,15 @@ void debug_export(SupportPoints support_points, std::string file_name)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// std::vector<size_t> quick_search(const PrintObject *po, const Params ¶ms) {
|
std::tuple<SupportPoints, PartialObjects> full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms)
|
||||||
// return {};
|
|
||||||
// }
|
|
||||||
SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms)
|
|
||||||
{
|
{
|
||||||
SupportPoints supp_points = check_stability(po, cancel_func, params);
|
auto results = check_stability(po, cancel_func, params);
|
||||||
#ifdef DEBUG_FILES
|
#ifdef DEBUG_FILES
|
||||||
debug_export(supp_points, "issues");
|
auto [supp_points, objects] = results;
|
||||||
|
debug_export(supp_points, objects, "issues");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return supp_points;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, const Params ¶ms)
|
void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, const Params ¶ms)
|
||||||
@ -998,5 +1161,84 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void raise_alerts_for_issues(const SupportPoints &support_points,
|
||||||
|
PartialObjects &partial_objects,
|
||||||
|
std::function<void(PrintStateBase::WarningLevel, SupportPointCause)> alert_fn)
|
||||||
|
{
|
||||||
|
for (const SupportPoint &sp : support_points) {
|
||||||
|
if (sp.cause == SupportPointCause::SeparationFromBed) {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::SeparationFromBed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::reverse(partial_objects.begin(), partial_objects.end());
|
||||||
|
std::sort(partial_objects.begin(), partial_objects.end(),
|
||||||
|
[](const PartialObject &left, const PartialObject &right) { return left.volume > right.volume; });
|
||||||
|
|
||||||
|
float max_volume_part = partial_objects.front().volume;
|
||||||
|
for (const PartialObject &p : partial_objects) {
|
||||||
|
if (p.volume > max_volume_part / 500.0f && !p.connected_to_bed) {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const SupportPoint &sp : support_points) {
|
||||||
|
if (sp.cause == SupportPointCause::UnstableFloatingPart) {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::UnstableFloatingPart);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SupportPoint> ext_supp_points{};
|
||||||
|
ext_supp_points.reserve(support_points.size());
|
||||||
|
for (const SupportPoint &sp : support_points) {
|
||||||
|
switch (sp.cause) {
|
||||||
|
case SupportPointCause::FloatingBridgeAnchor:
|
||||||
|
case SupportPointCause::FloatingExtrusion: ext_supp_points.push_back(sp); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto coord_fn = [&ext_supp_points](size_t idx, size_t dim) { return ext_supp_points[idx].position[dim]; };
|
||||||
|
KDTreeIndirect<3, float, decltype(coord_fn)> ext_points_tree{coord_fn, ext_supp_points.size()};
|
||||||
|
for (const SupportPoint &sp : ext_supp_points) {
|
||||||
|
auto cluster = find_nearby_points(ext_points_tree, sp.position, 3.0);
|
||||||
|
int score = 0;
|
||||||
|
bool floating_bridge = false;
|
||||||
|
for (size_t idx : cluster) {
|
||||||
|
score += ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor ? 3 : 1;
|
||||||
|
floating_bridge = floating_bridge || ext_supp_points[idx].cause == SupportPointCause::FloatingBridgeAnchor;
|
||||||
|
}
|
||||||
|
if (score > 5) {
|
||||||
|
if (floating_bridge) {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingBridgeAnchor);
|
||||||
|
} else {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::CRITICAL, SupportPointCause::FloatingExtrusion);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ext_supp_points.size() > 5) {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::FloatingExtrusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const SupportPoint &sp : support_points) {
|
||||||
|
if (sp.cause == SupportPointCause::LongBridge) {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::LongBridge);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const SupportPoint &sp : support_points) {
|
||||||
|
if (sp.cause == SupportPointCause::WeakObjectPart) {
|
||||||
|
alert_fn(PrintStateBase::WarningLevel::NON_CRITICAL, SupportPointCause::WeakObjectPart);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace SupportSpotsGenerator
|
} // namespace SupportSpotsGenerator
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
@ -4,43 +4,52 @@
|
|||||||
#include "Layer.hpp"
|
#include "Layer.hpp"
|
||||||
#include "Line.hpp"
|
#include "Line.hpp"
|
||||||
#include "PrintBase.hpp"
|
#include "PrintBase.hpp"
|
||||||
|
#include "PrintConfig.hpp"
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <cstddef>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
namespace SupportSpotsGenerator {
|
namespace SupportSpotsGenerator {
|
||||||
|
|
||||||
struct Params {
|
struct Params
|
||||||
Params(const std::vector<std::string> &filament_types) {
|
{
|
||||||
|
Params(
|
||||||
|
const std::vector<std::string> &filament_types, float max_acceleration, int raft_layers_count, BrimType brim_type, float brim_width)
|
||||||
|
: max_acceleration(max_acceleration), raft_layers_count(raft_layers_count), brim_type(brim_type), brim_width(brim_width)
|
||||||
|
{
|
||||||
if (filament_types.size() > 1) {
|
if (filament_types.size() > 1) {
|
||||||
BOOST_LOG_TRIVIAL(warning)
|
BOOST_LOG_TRIVIAL(warning)
|
||||||
<< "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
|
<< "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
|
||||||
}
|
}
|
||||||
if (filament_types.empty() || filament_types[0].empty()) {
|
if (filament_types.empty() || filament_types[0].empty()) {
|
||||||
BOOST_LOG_TRIVIAL(error)
|
BOOST_LOG_TRIVIAL(error) << "SupportSpotsGenerator error: empty filament_type";
|
||||||
<< "SupportSpotsGenerator error: empty filament_type";
|
|
||||||
filament_type = std::string("PLA");
|
filament_type = std::string("PLA");
|
||||||
} else {
|
} else {
|
||||||
filament_type = filament_types[0];
|
filament_type = filament_types[0];
|
||||||
BOOST_LOG_TRIVIAL(debug)
|
BOOST_LOG_TRIVIAL(debug) << "SupportSpotsGenerator: applying filament type: " << filament_type;
|
||||||
<< "SupportSpotsGenerator: applying filament type: " << filament_type;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2]
|
// the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2]
|
||||||
const float bridge_distance = 12.0f; //mm
|
const float bridge_distance = 12.0f; // mm
|
||||||
|
const float max_acceleration; // mm/s^2 ; max acceleration of object in XY -- should be applicable only to printers with bed slinger,
|
||||||
|
// however we do not have such info yet. The force is usually small anyway, so not such a big deal to include it everytime
|
||||||
|
const int raft_layers_count;
|
||||||
|
std::string filament_type;
|
||||||
|
|
||||||
|
BrimType brim_type;
|
||||||
|
const float brim_width;
|
||||||
|
|
||||||
const std::pair<float,float> malformation_distance_factors = std::pair<float, float> { 0.4, 1.2 };
|
const std::pair<float,float> malformation_distance_factors = std::pair<float, float> { 0.4, 1.2 };
|
||||||
const float max_curled_height_factor = 10.0f;
|
const float max_curled_height_factor = 10.0f;
|
||||||
|
|
||||||
const float min_distance_between_support_points = 3.0f; //mm
|
const float min_distance_between_support_points = 3.0f; //mm
|
||||||
const float support_points_interface_radius = 1.5f; // mm
|
const float support_points_interface_radius = 1.5f; // mm
|
||||||
const float connections_min_considerable_area = 1.5f; //mm^2
|
|
||||||
const float min_distance_to_allow_local_supports = 1.0f; //mm
|
const float min_distance_to_allow_local_supports = 1.0f; //mm
|
||||||
|
|
||||||
std::string filament_type;
|
|
||||||
const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position.
|
const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position.
|
||||||
const float max_acceleration = 9 * 1000.0f; // mm/s^2 ; max acceleration of object (bed) in XY (NOTE: The max hit is received by the object in the jerk phase, so the usual machine limits are too low)
|
|
||||||
const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important
|
const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important
|
||||||
const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials.
|
const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials.
|
||||||
const float standard_extruder_conflict_force = 20.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... );
|
const float standard_extruder_conflict_force = 20.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... );
|
||||||
@ -48,10 +57,16 @@ struct Params {
|
|||||||
|
|
||||||
// MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface
|
// MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface
|
||||||
double get_bed_adhesion_yield_strength() const {
|
double get_bed_adhesion_yield_strength() const {
|
||||||
|
if (raft_layers_count > 0) {
|
||||||
|
return get_support_spots_adhesion_strength() * 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
if (filament_type == "PLA") {
|
if (filament_type == "PLA") {
|
||||||
return 0.018 * 1e6;
|
return 0.018 * 1e6;
|
||||||
} else if (filament_type == "PET" || filament_type == "PETG") {
|
} else if (filament_type == "PET" || filament_type == "PETG") {
|
||||||
return 0.3 * 1e6;
|
return 0.3 * 1e6;
|
||||||
|
} else if (filament_type == "ABS" || filament_type == "ASA") {
|
||||||
|
return 0.1 * 1e6; //TODO do measurements
|
||||||
} else { //PLA default value - defensive approach, PLA has quite low adhesion
|
} else { //PLA default value - defensive approach, PLA has quite low adhesion
|
||||||
return 0.018 * 1e6;
|
return 0.018 * 1e6;
|
||||||
}
|
}
|
||||||
@ -63,30 +78,48 @@ struct Params {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The support points are generated for two reasons:
|
enum class SupportPointCause {
|
||||||
// 1. Local extrusion support for extrusions that are printed in the air and would not
|
LongBridge, // point generated on bridge and straight perimeter extrusion longer than the allowed length
|
||||||
|
FloatingBridgeAnchor, // point generated on unsupported bridge endpoint
|
||||||
|
FloatingExtrusion, // point generated on extrusion that does not hold on its own
|
||||||
|
SeparationFromBed, // point generated for object parts that are connected to the bed, but the area is too small and there is a risk of separation (brim may help)
|
||||||
|
UnstableFloatingPart, // point generated for object parts not connected to the bed, holded only by the other support points (brim will not help here)
|
||||||
|
WeakObjectPart // point generated when some part of the object is too weak to hold the upper part and may break (imagine hourglass)
|
||||||
|
};
|
||||||
|
|
||||||
|
// The support points can be sorted into two groups
|
||||||
|
// 1. Local extrusion support for extrusions that are printed in the air and would not
|
||||||
// withstand on their own (too long bridges, sharp turns in large overhang, concave bridge holes, etc.)
|
// withstand on their own (too long bridges, sharp turns in large overhang, concave bridge holes, etc.)
|
||||||
// These points have negative force (-EPSILON) and Vec2f::Zero() direction
|
// These points have negative force (-EPSILON) and Vec2f::Zero() direction
|
||||||
// The algorithm still expects that these points will be supported and accounts for them in the global stability check
|
// The algorithm still expects that these points will be supported and accounts for them in the global stability check.
|
||||||
// 2. Global stability support points are generated at each spot, where the algorithm detects that extruding the current line
|
// 2. Global stability support points are generated at each spot, where the algorithm detects that extruding the current line
|
||||||
// may cause separation of the object part from the bed and/or its support spots or crack in the weak connection of the object parts
|
// may cause separation of the object part from the bed and/or its support spots or crack in the weak connection of the object parts.
|
||||||
// The generated point's direction is the estimated falling direction of the object part, and the force is equal to te difference
|
// The generated point's direction is the estimated falling direction of the object part, and the force is equal to te difference
|
||||||
// between forces that destabilize the object (extruder conflicts with curled filament, weight if instable center of mass, bed movements etc)
|
// between forces that destabilize the object (extruder conflicts with curled filament, weight if instable center of mass, bed movements etc)
|
||||||
// and forces that stabilize the object (bed adhesion, other support spots adhesion, weight if stable center of mass)
|
// and forces that stabilize the object (bed adhesion, other support spots adhesion, weight if stable center of mass).
|
||||||
// Note that the force is only the difference - the amount needed to stabilize the object again.
|
// Note that the force is only the difference - the amount needed to stabilize the object again.
|
||||||
struct SupportPoint {
|
struct SupportPoint
|
||||||
SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction);
|
{
|
||||||
bool is_local_extrusion_support() const { return force < 0; }
|
SupportPoint(SupportPointCause cause, const Vec3f &position, float force, float spot_radius, const Vec2f &direction)
|
||||||
|
: cause(cause), position(position), force(force), spot_radius(spot_radius), direction(direction)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool is_local_extrusion_support() const
|
||||||
|
{
|
||||||
|
return cause == SupportPointCause::LongBridge || cause == SupportPointCause::FloatingExtrusion;
|
||||||
|
}
|
||||||
bool is_global_object_support() const { return !is_local_extrusion_support(); }
|
bool is_global_object_support() const { return !is_local_extrusion_support(); }
|
||||||
|
|
||||||
//position is in unscaled coords. The z coordinate is aligned with the layers bottom_z coordiantes
|
SupportPointCause cause; // reason why this support point was generated. Used for the user alerts
|
||||||
|
// position is in unscaled coords. The z coordinate is aligned with the layers bottom_z coordiantes
|
||||||
Vec3f position;
|
Vec3f position;
|
||||||
// force that destabilizes the object to the point of falling/breaking. It is in g*mm/s^2 units
|
// force that destabilizes the object to the point of falling/breaking. g*mm/s^2 units
|
||||||
// values gathered from large XL print: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103
|
// It is valid only for global_object_support. For local extrusion support points, the force is -EPSILON
|
||||||
|
// values gathered from large XL model: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103
|
||||||
// For reference 18713800 is weight of 1.8 Kg object, 329103 is weight of 0.03 Kg
|
// For reference 18713800 is weight of 1.8 Kg object, 329103 is weight of 0.03 Kg
|
||||||
// The final printed object weight was approx 0.5 Kg
|
// The final sliced object weight was approx 0.5 Kg
|
||||||
float force;
|
float force;
|
||||||
// Expected spot size. The support point strength is calculated from the area defined by this value.
|
// Expected spot size. The support point strength is calculated from the area defined by this value.
|
||||||
// Currently equal to the support_points_interface_radius parameter above
|
// Currently equal to the support_points_interface_radius parameter above
|
||||||
float spot_radius;
|
float spot_radius;
|
||||||
// direction of the fall of the object (z part is neglected)
|
// direction of the fall of the object (z part is neglected)
|
||||||
@ -99,13 +132,28 @@ struct Malformations {
|
|||||||
std::vector<Lines> layers; //for each layer
|
std::vector<Lines> layers; //for each layer
|
||||||
};
|
};
|
||||||
|
|
||||||
// std::vector<size_t> quick_search(const PrintObject *po, const Params ¶ms);
|
struct PartialObject
|
||||||
SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms);
|
{
|
||||||
|
PartialObject(Vec3f centroid, float volume, bool connected_to_bed)
|
||||||
|
: centroid(centroid), volume(volume), connected_to_bed(connected_to_bed)
|
||||||
|
{}
|
||||||
|
|
||||||
void estimate_supports_malformations(std::vector<SupportLayer*> &layers, float supports_flow_width, const Params ¶ms);
|
Vec3f centroid;
|
||||||
void estimate_malformations(std::vector<Layer*> &layers, const Params ¶ms);
|
float volume;
|
||||||
|
bool connected_to_bed;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace SupportSpotsGenerator
|
using PartialObjects = std::vector<PartialObject>;
|
||||||
}
|
|
||||||
|
std::tuple<SupportPoints, PartialObjects> full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms);
|
||||||
|
|
||||||
|
void estimate_supports_malformations(std::vector<SupportLayer *> &layers, float supports_flow_width, const Params ¶ms);
|
||||||
|
void estimate_malformations(std::vector<Layer *> &layers, const Params ¶ms);
|
||||||
|
|
||||||
|
void raise_alerts_for_issues(const SupportPoints &support_points,
|
||||||
|
PartialObjects &partial_objects,
|
||||||
|
std::function<void(PrintStateBase::WarningLevel, SupportPointCause)> alert_fn);
|
||||||
|
|
||||||
|
}} // namespace Slic3r::SupportSpotsGenerator
|
||||||
|
|
||||||
#endif /* SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ */
|
#endif /* SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ */
|
||||||
|
@ -1751,7 +1751,7 @@ PageBuildVolume::PageBuildVolume(ConfigWizard* parent)
|
|||||||
: ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1)
|
: ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1)
|
||||||
, build_volume(new DiamTextCtrl(this))
|
, build_volume(new DiamTextCtrl(this))
|
||||||
{
|
{
|
||||||
append_text(_L("Set verctical size of your printer."));
|
append_text(_L("Set vertical size of your printer."));
|
||||||
|
|
||||||
wxString value = "200";
|
wxString value = "200";
|
||||||
build_volume->SetValue(value);
|
build_volume->SetValue(value);
|
||||||
|
@ -119,8 +119,8 @@ void FileGet::priv::get_perform()
|
|||||||
|
|
||||||
wxString temp_path_wstring(m_tmp_path.wstring());
|
wxString temp_path_wstring(m_tmp_path.wstring());
|
||||||
|
|
||||||
std::cout << "dest_path: " << dest_path.string() << std::endl;
|
//std::cout << "dest_path: " << dest_path.string() << std::endl;
|
||||||
std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
|
//std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path);
|
BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path);
|
||||||
|
|
||||||
|
@ -762,28 +762,26 @@ void SpinCtrl::BUILD() {
|
|||||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
|
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
|
||||||
|
|
||||||
wxString text_value = wxString("");
|
wxString text_value = wxString("");
|
||||||
int default_value = 0;
|
int default_value = UNDEF_VALUE;
|
||||||
|
|
||||||
switch (m_opt.type) {
|
switch (m_opt.type) {
|
||||||
case coInt:
|
case coInt:
|
||||||
default_value = m_opt.default_value->getInt();
|
default_value = m_opt.default_value->getInt();
|
||||||
text_value = wxString::Format(_T("%i"), default_value);
|
|
||||||
break;
|
break;
|
||||||
case coInts:
|
case coInts:
|
||||||
{
|
{
|
||||||
const ConfigOptionInts *vec = m_opt.get_default_value<ConfigOptionInts>();
|
default_value = m_opt.get_default_value<ConfigOptionInts>()->get_at(m_opt_idx);
|
||||||
if (vec == nullptr || vec->empty()) break;
|
if (m_opt.nullable)
|
||||||
for (size_t id = 0; id < vec->size(); ++id)
|
m_last_meaningful_value = default_value == ConfigOptionIntsNullable::nil_value() ? static_cast<int>(m_opt.max) : default_value;
|
||||||
{
|
|
||||||
default_value = vec->get_at(id);
|
|
||||||
text_value += wxString::Format(_T("%i"), default_value);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (default_value != UNDEF_VALUE)
|
||||||
|
text_value = wxString::Format(_T("%i"), default_value);
|
||||||
|
|
||||||
const int min_val = m_opt.min == -FLT_MAX
|
const int min_val = m_opt.min == -FLT_MAX
|
||||||
#ifdef __WXOSX__
|
#ifdef __WXOSX__
|
||||||
// We will forcibly set the input value for SpinControl, since the value
|
// We will forcibly set the input value for SpinControl, since the value
|
||||||
@ -882,6 +880,50 @@ void SpinCtrl::BUILD() {
|
|||||||
window = dynamic_cast<wxWindow*>(temp);
|
window = dynamic_cast<wxWindow*>(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpinCtrl::set_value(const boost::any& value, bool change_event/* = false*/)
|
||||||
|
{
|
||||||
|
m_disable_change_event = !change_event;
|
||||||
|
tmp_value = boost::any_cast<int>(value);
|
||||||
|
m_value = value;
|
||||||
|
if (m_opt.nullable) {
|
||||||
|
const bool m_is_na_val = tmp_value == ConfigOptionIntsNullable::nil_value();
|
||||||
|
if (m_is_na_val)
|
||||||
|
dynamic_cast<wxSpinCtrl*>(window)->SetValue(na_value());
|
||||||
|
else {
|
||||||
|
m_last_meaningful_value = value;
|
||||||
|
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
|
||||||
|
m_disable_change_event = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpinCtrl::set_last_meaningful_value()
|
||||||
|
{
|
||||||
|
const int val = boost::any_cast<int>(m_last_meaningful_value);
|
||||||
|
dynamic_cast<wxSpinCtrl*>(window)->SetValue(val);
|
||||||
|
tmp_value = val;
|
||||||
|
propagate_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpinCtrl::set_na_value()
|
||||||
|
{
|
||||||
|
dynamic_cast<wxSpinCtrl*>(window)->SetValue(na_value());
|
||||||
|
m_value = ConfigOptionIntsNullable::nil_value();
|
||||||
|
propagate_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::any& SpinCtrl::get_value()
|
||||||
|
{
|
||||||
|
wxSpinCtrl* spin = static_cast<wxSpinCtrl*>(window);
|
||||||
|
if (spin->GetTextValue() == na_value())
|
||||||
|
return m_value;
|
||||||
|
|
||||||
|
int value = spin->GetValue();
|
||||||
|
return m_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
void SpinCtrl::propagate_value()
|
void SpinCtrl::propagate_value()
|
||||||
{
|
{
|
||||||
// check if value was really changed
|
// check if value was really changed
|
||||||
|
@ -330,24 +330,18 @@ public:
|
|||||||
void BUILD() override;
|
void BUILD() override;
|
||||||
/// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
/// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||||
void propagate_value() ;
|
void propagate_value() ;
|
||||||
|
/*
|
||||||
void set_value(const std::string& value, bool change_event = false) {
|
void set_value(const std::string& value, bool change_event = false) {
|
||||||
m_disable_change_event = !change_event;
|
m_disable_change_event = !change_event;
|
||||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
|
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
|
||||||
m_disable_change_event = false;
|
m_disable_change_event = false;
|
||||||
}
|
}
|
||||||
void set_value(const boost::any& value, bool change_event = false) override {
|
*/
|
||||||
m_disable_change_event = !change_event;
|
void set_value(const boost::any& value, bool change_event = false) override;
|
||||||
tmp_value = boost::any_cast<int>(value);
|
void set_last_meaningful_value() override;
|
||||||
m_value = value;
|
void set_na_value() override;
|
||||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
|
|
||||||
m_disable_change_event = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::any& get_value() override {
|
boost::any& get_value() override;
|
||||||
int value = static_cast<wxSpinCtrl*>(window)->GetValue();
|
|
||||||
return m_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void msw_rescale() override;
|
void msw_rescale() override;
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ bool GLGizmoFdmSupports::on_init()
|
|||||||
{
|
{
|
||||||
m_shortcut_key = WXK_CONTROL_L;
|
m_shortcut_key = WXK_CONTROL_L;
|
||||||
|
|
||||||
m_desc["auto_generate"] = _L("Auto-generate supports");
|
m_desc["autopaint"] = _L("Automatic painting");
|
||||||
m_desc["generating"] = _L("Generating supports...");
|
m_desc["painting"] = _L("painting...");
|
||||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||||
m_desc["reset_direction"] = _L("Reset direction");
|
m_desc["reset_direction"] = _L("Reset direction");
|
||||||
m_desc["cursor_size"] = _L("Brush size") + ": ";
|
m_desc["cursor_size"] = _L("Brush size") + ": ";
|
||||||
@ -160,9 +160,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (waiting_for_autogenerated_supports) {
|
if (waiting_for_autogenerated_supports) {
|
||||||
m_imgui->text(m_desc.at("generating"));
|
m_imgui->text(m_desc.at("painting"));
|
||||||
} else {
|
} else {
|
||||||
bool generate = m_imgui->button(m_desc.at("auto_generate"));
|
bool generate = m_imgui->button(m_desc.at("autopaint"));
|
||||||
if (generate)
|
if (generate)
|
||||||
auto_generate();
|
auto_generate();
|
||||||
}
|
}
|
||||||
@ -525,12 +525,12 @@ void GLGizmoFdmSupports::auto_generate()
|
|||||||
});
|
});
|
||||||
|
|
||||||
MessageDialog dlg(GUI::wxGetApp().plater(),
|
MessageDialog dlg(GUI::wxGetApp().plater(),
|
||||||
_L("Autogeneration will erase all currently painted areas.") + "\n\n" +
|
_L("Automatic painting will erase all currently painted areas.") + "\n\n" +
|
||||||
_L("Are you sure you want to do it?") + "\n",
|
_L("Are you sure you want to do it?") + "\n",
|
||||||
_L("Warning"), wxICON_WARNING | wxYES | wxNO);
|
_L("Warning"), wxICON_WARNING | wxYES | wxNO);
|
||||||
|
|
||||||
if (not_painted || dlg.ShowModal() == wxID_YES) {
|
if (not_painted || dlg.ShowModal() == wxID_YES) {
|
||||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points"));
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Automatic painting support points"));
|
||||||
int mesh_id = -1.0f;
|
int mesh_id = -1.0f;
|
||||||
for (ModelVolume *mv : mo->volumes) {
|
for (ModelVolume *mv : mo->volumes) {
|
||||||
if (mv->is_model_part()) {
|
if (mv->is_model_part()) {
|
||||||
|
@ -1826,45 +1826,59 @@ static void validate_custom_gcode_cb(Tab* tab, const wxString& title, const t_co
|
|||||||
tab->on_value_change(opt_key, value);
|
tab->on_value_change(opt_key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TabFilament::create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/)
|
||||||
|
{
|
||||||
|
Line line {"",""};
|
||||||
|
if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") {
|
||||||
|
Option opt = optgroup->get_option(opt_key);
|
||||||
|
opt.opt.label = opt.opt.full_label;
|
||||||
|
line = optgroup->create_single_option_line(opt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
line = optgroup->create_single_option_line(optgroup->get_option(opt_key));
|
||||||
|
|
||||||
|
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) {
|
||||||
|
wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, "");
|
||||||
|
|
||||||
|
check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) {
|
||||||
|
const bool is_checked = evt.IsChecked();
|
||||||
|
if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) {
|
||||||
|
if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) {
|
||||||
|
field->toggle(is_checked);
|
||||||
|
if (is_checked)
|
||||||
|
field->set_last_meaningful_value();
|
||||||
|
else
|
||||||
|
field->set_na_value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, check_box->GetId());
|
||||||
|
|
||||||
|
m_overrides_options[opt_key] = check_box;
|
||||||
|
return check_box;
|
||||||
|
};
|
||||||
|
|
||||||
|
optgroup->append_line(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabFilament::update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/, bool is_checked/* = true*/)
|
||||||
|
{
|
||||||
|
if (!m_overrides_options[opt_key])
|
||||||
|
return;
|
||||||
|
m_overrides_options[opt_key]->Enable(is_checked);
|
||||||
|
|
||||||
|
is_checked &= !m_config->option(opt_key)->is_nil();
|
||||||
|
m_overrides_options[opt_key]->SetValue(is_checked);
|
||||||
|
|
||||||
|
Field* field = optgroup->get_fieldc(opt_key, opt_index);
|
||||||
|
if (field != nullptr)
|
||||||
|
field->toggle(is_checked);
|
||||||
|
}
|
||||||
|
|
||||||
void TabFilament::add_filament_overrides_page()
|
void TabFilament::add_filament_overrides_page()
|
||||||
{
|
{
|
||||||
PageShp page = add_options_page(L("Filament Overrides"), "wrench");
|
PageShp page = add_options_page(L("Filament Overrides"), "wrench");
|
||||||
ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction"));
|
ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction"));
|
||||||
|
|
||||||
auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index)
|
|
||||||
{
|
|
||||||
Line line {"",""};
|
|
||||||
if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") {
|
|
||||||
Option opt = optgroup->get_option(opt_key);
|
|
||||||
opt.opt.label = opt.opt.full_label;
|
|
||||||
line = optgroup->create_single_option_line(opt);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
line = optgroup->create_single_option_line(optgroup->get_option(opt_key));
|
|
||||||
|
|
||||||
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) {
|
|
||||||
wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, "");
|
|
||||||
|
|
||||||
check_box->Bind(wxEVT_CHECKBOX, [optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) {
|
|
||||||
const bool is_checked = evt.IsChecked();
|
|
||||||
if (auto optgroup_sh = optgroup_wk.lock(); optgroup_sh) {
|
|
||||||
if (Field *field = optgroup_sh->get_fieldc(opt_key, opt_index); field != nullptr) {
|
|
||||||
field->toggle(is_checked);
|
|
||||||
if (is_checked)
|
|
||||||
field->set_last_meaningful_value();
|
|
||||||
else
|
|
||||||
field->set_na_value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, check_box->GetId());
|
|
||||||
|
|
||||||
m_overrides_options[opt_key] = check_box;
|
|
||||||
return check_box;
|
|
||||||
};
|
|
||||||
|
|
||||||
optgroup->append_line(line);
|
|
||||||
};
|
|
||||||
|
|
||||||
const int extruder_idx = 0; // #ys_FIXME
|
const int extruder_idx = 0; // #ys_FIXME
|
||||||
|
|
||||||
for (const std::string opt_key : { "filament_retract_length",
|
for (const std::string opt_key : { "filament_retract_length",
|
||||||
@ -1879,7 +1893,7 @@ void TabFilament::add_filament_overrides_page()
|
|||||||
"filament_wipe",
|
"filament_wipe",
|
||||||
"filament_retract_before_wipe"
|
"filament_retract_before_wipe"
|
||||||
})
|
})
|
||||||
append_single_option_line(opt_key, extruder_idx);
|
create_line_with_near_label_widget(optgroup, opt_key, extruder_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabFilament::update_filament_overrides_page()
|
void TabFilament::update_filament_overrides_page()
|
||||||
@ -1914,14 +1928,7 @@ void TabFilament::update_filament_overrides_page()
|
|||||||
for (const std::string& opt_key : opt_keys)
|
for (const std::string& opt_key : opt_keys)
|
||||||
{
|
{
|
||||||
bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length;
|
bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length;
|
||||||
m_overrides_options[opt_key]->Enable(is_checked);
|
update_line_with_near_label_widget(optgroup, opt_key, extruder_idx, is_checked);
|
||||||
|
|
||||||
is_checked &= !m_config->option(opt_key)->is_nil();
|
|
||||||
m_overrides_options[opt_key]->SetValue(is_checked);
|
|
||||||
|
|
||||||
Field* field = optgroup->get_fieldc(opt_key, extruder_idx);
|
|
||||||
if (field != nullptr)
|
|
||||||
field->toggle(is_checked);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1952,6 +1959,9 @@ void TabFilament::build()
|
|||||||
};
|
};
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Temperature"));
|
optgroup = page->new_optgroup(L("Temperature"));
|
||||||
|
|
||||||
|
create_line_with_near_label_widget(optgroup, "idle_temperature");
|
||||||
|
|
||||||
Line line = { L("Nozzle"), "" };
|
Line line = { L("Nozzle"), "" };
|
||||||
line.append_option(optgroup->get_option("first_layer_temperature"));
|
line.append_option(optgroup->get_option("first_layer_temperature"));
|
||||||
line.append_option(optgroup->get_option("temperature"));
|
line.append_option(optgroup->get_option("temperature"));
|
||||||
@ -2142,6 +2152,14 @@ void TabFilament::toggle_options()
|
|||||||
|
|
||||||
if (m_active_page->title() == "Filament Overrides")
|
if (m_active_page->title() == "Filament Overrides")
|
||||||
update_filament_overrides_page();
|
update_filament_overrides_page();
|
||||||
|
|
||||||
|
if (m_active_page->title() == "Filament") {
|
||||||
|
Page* page = m_active_page;
|
||||||
|
|
||||||
|
const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Temperature"; });
|
||||||
|
if (og_it != page->m_optgroups.end())
|
||||||
|
update_line_with_near_label_widget(*og_it, "idle_temperature");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabFilament::update()
|
void TabFilament::update()
|
||||||
|
@ -436,6 +436,8 @@ private:
|
|||||||
ogStaticText* m_volumetric_speed_description_line {nullptr};
|
ogStaticText* m_volumetric_speed_description_line {nullptr};
|
||||||
ogStaticText* m_cooling_description_line {nullptr};
|
ogStaticText* m_cooling_description_line {nullptr};
|
||||||
|
|
||||||
|
void create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0);
|
||||||
|
void update_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string &opt_key, int opt_index = 0, bool is_checked = true);
|
||||||
void add_filament_overrides_page();
|
void add_filament_overrides_page();
|
||||||
void update_filament_overrides_page();
|
void update_filament_overrides_page();
|
||||||
void update_volumetric_flow_preset_hints();
|
void update_volumetric_flow_preset_hints();
|
||||||
|
@ -167,10 +167,14 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos
|
|||||||
wxString wxext = boost::nowide::widen(extension);
|
wxString wxext = boost::nowide::widen(extension);
|
||||||
wildcard = GUI::format_wxstr("%1% Files (*.%2%)|*.%2%", wxext.Upper(), wxext);
|
wildcard = GUI::format_wxstr("%1% Files (*.%2%)|*.%2%", wxext.Upper(), wxext);
|
||||||
}
|
}
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(GUI::format(txtctrl_path->GetValue())), ec);
|
||||||
|
if (ec)
|
||||||
|
dir = GUI::format(txtctrl_path->GetValue());
|
||||||
wxDirDialog save_dlg(
|
wxDirDialog save_dlg(
|
||||||
this
|
this
|
||||||
, _L("Select directory:")
|
, _L("Select directory:")
|
||||||
, txtctrl_path->GetValue()
|
, GUI::format_wxstr(dir.string())
|
||||||
/*
|
/*
|
||||||
, filename //boost::nowide::widen(AppUpdater::get_filename_from_url(txtctrl_path->GetValue().ToUTF8().data()))
|
, filename //boost::nowide::widen(AppUpdater::get_filename_from_url(txtctrl_path->GetValue().ToUTF8().data()))
|
||||||
, wildcard
|
, wildcard
|
||||||
@ -188,35 +192,46 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos
|
|||||||
if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) {
|
if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) {
|
||||||
btn_ok->SetLabel(_L("Download"));
|
btn_ok->SetLabel(_L("Download"));
|
||||||
btn_ok->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e){
|
btn_ok->Bind(wxEVT_BUTTON, ([this, path](wxCommandEvent& e){
|
||||||
boost::filesystem::path path = boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename);
|
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
if (path.parent_path().string().empty()) {
|
std::string input = GUI::into_u8(txtctrl_path->GetValue());
|
||||||
|
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(input), ec);
|
||||||
|
if (ec)
|
||||||
|
dir = boost::filesystem::path(input);
|
||||||
|
bool show_change = (dir.string() != input);
|
||||||
|
boost::filesystem::path path = dir / GUI::format(filename);
|
||||||
|
ec.clear();
|
||||||
|
if (dir.string().empty()) {
|
||||||
MessageDialog msgdlg(nullptr, _L("Directory path is empty."), _L("Notice"), wxOK);
|
MessageDialog msgdlg(nullptr, _L("Directory path is empty."), _L("Notice"), wxOK);
|
||||||
msgdlg.ShowModal();
|
msgdlg.ShowModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ec.clear();
|
ec.clear();
|
||||||
if (!boost::filesystem::exists(path.parent_path(), ec) || !boost::filesystem::is_directory(path.parent_path(),ec) || ec) {
|
if (!boost::filesystem::exists(dir, ec) || !boost::filesystem::is_directory(dir,ec) || ec) {
|
||||||
ec.clear();
|
ec.clear();
|
||||||
if (!boost::filesystem::exists(path.parent_path().parent_path(), ec) || !boost::filesystem::is_directory(path.parent_path().parent_path(), ec) || ec) {
|
if (!boost::filesystem::exists(dir.parent_path(), ec) || !boost::filesystem::is_directory(dir.parent_path(), ec) || ec) {
|
||||||
MessageDialog msgdlg(nullptr, _L("Directory path is incorrect."), _L("Notice"), wxOK);
|
MessageDialog msgdlg(nullptr, _L("Directory path is incorrect."), _L("Notice"), wxOK);
|
||||||
msgdlg.ShowModal();
|
msgdlg.ShowModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
show_change = false;
|
||||||
MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Directory %1% doesn't exists. Do you wish to create it?"), GUI::format_wxstr(path.parent_path().string())), _L("Notice"), wxYES_NO);
|
MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Directory %1% doesn't exists. Do you wish to create it?"), dir.string()), _L("Notice"), wxYES_NO);
|
||||||
if (msgdlg.ShowModal() != wxID_YES)
|
if (msgdlg.ShowModal() != wxID_YES)
|
||||||
return;
|
return;
|
||||||
ec.clear();
|
ec.clear();
|
||||||
if(!boost::filesystem::create_directory(path.parent_path(), ec) || ec)
|
if(!boost::filesystem::create_directory(dir, ec) || ec) {
|
||||||
{
|
|
||||||
MessageDialog msgdlg(nullptr, _L("Failed to create directory."), _L("Notice"), wxOK);
|
MessageDialog msgdlg(nullptr, _L("Failed to create directory."), _L("Notice"), wxOK);
|
||||||
msgdlg.ShowModal();
|
msgdlg.ShowModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (boost::filesystem::exists(path)) {
|
if (boost::filesystem::exists(path)) {
|
||||||
MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("File %1% already exists. Do you wish to overwrite it?"), GUI::format_wxstr(path.string())),_L("Notice"), wxYES_NO);
|
show_change = false;
|
||||||
|
MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("File %1% already exists. Do you wish to overwrite it?"), path.string()),_L("Notice"), wxYES_NO);
|
||||||
|
if (msgdlg.ShowModal() != wxID_YES)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (show_change) {
|
||||||
|
MessageDialog msgdlg(nullptr, GUI::format_wxstr(_L("Download path is %1%. Do you wish to continue?"), path.string()), _L("Notice"), wxYES_NO);
|
||||||
if (msgdlg.ShowModal() != wxID_YES)
|
if (msgdlg.ShowModal() != wxID_YES)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -241,7 +256,12 @@ bool AppUpdateDownloadDialog::run_after_download() const
|
|||||||
|
|
||||||
boost::filesystem::path AppUpdateDownloadDialog::get_download_path() const
|
boost::filesystem::path AppUpdateDownloadDialog::get_download_path() const
|
||||||
{
|
{
|
||||||
return boost::filesystem::path(txtctrl_path->GetValue().ToUTF8().data()) / GUI::format(filename);
|
boost::system::error_code ec;
|
||||||
|
std::string input = GUI::format(txtctrl_path->GetValue());
|
||||||
|
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(input), ec);
|
||||||
|
if (ec)
|
||||||
|
dir = boost::filesystem::path(input);
|
||||||
|
return dir / GUI::format(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgUpdateConfig
|
// MsgUpdateConfig
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "slic3r/GUI/format.hpp"
|
#include "slic3r/GUI/format.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
#include "slic3r/Utils/Http.hpp"
|
#include "slic3r/Utils/Http.hpp"
|
||||||
|
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
@ -36,7 +37,7 @@ namespace {
|
|||||||
std::string msg;
|
std::string msg;
|
||||||
bool res = GUI::create_process(path, std::wstring(), msg);
|
bool res = GUI::create_process(path, std::wstring(), msg);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
std::string full_message = GUI::format("Running downloaded instaler of %1% has failed:\n%2%", SLIC3R_APP_NAME, msg);
|
std::string full_message = GUI::format(_utf8("Running downloaded instaler of %1% has failed:\n%2%"), SLIC3R_APP_NAME, msg);
|
||||||
BOOST_LOG_TRIVIAL(error) << full_message; // lm: maybe UI error msg? // dk: bellow. (maybe some general show error evt would be better?)
|
BOOST_LOG_TRIVIAL(error) << full_message; // lm: maybe UI error msg? // dk: bellow. (maybe some general show error evt would be better?)
|
||||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
evt->SetString(full_message);
|
evt->SetString(full_message);
|
||||||
@ -170,8 +171,10 @@ bool AppUpdater::priv::http_get_file(const std::string& url, size_t size_limit,
|
|||||||
// progress function returns true as success (to continue)
|
// progress function returns true as success (to continue)
|
||||||
cancel = (m_cancel ? true : !progress_fn(std::move(progress)));
|
cancel = (m_cancel ? true : !progress_fn(std::move(progress)));
|
||||||
if (cancel) {
|
if (cancel) {
|
||||||
error_message = GUI::format("Error getting: `%1%`: Download was canceled.", //lm:typo //dk: am i blind? :)
|
// Lets keep error_message empty here - if there is need to show error dialog, the message will be probably shown by whatever caused the cancel.
|
||||||
url);
|
/*
|
||||||
|
error_message = GUI::format(_utf8("Error getting: `%1%`: Download was canceled."), url);
|
||||||
|
*/
|
||||||
BOOST_LOG_TRIVIAL(debug) << "AppUpdater::priv::http_get_file message: "<< error_message;
|
BOOST_LOG_TRIVIAL(debug) << "AppUpdater::priv::http_get_file message: "<< error_message;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -200,7 +203,30 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
|||||||
assert(!dest_path.empty());
|
assert(!dest_path.empty());
|
||||||
if (dest_path.empty())
|
if (dest_path.empty())
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(error) << "Download from " << data.url << " could not start. Destination path is empty.";
|
std::string line1 = GUI::format(_utf8("Internal download error for url %1%:"), data.url);
|
||||||
|
std::string line2 = _utf8("Destination path is empty.");
|
||||||
|
std::string message = GUI::format("%1%\n%2%", line1, line2);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << message;
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
|
evt->SetString(message);
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
|
return boost::filesystem::path();
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::filesystem::path tmp_path = dest_path;
|
||||||
|
tmp_path += format(".%1%%2%", get_current_pid(), ".download");
|
||||||
|
FILE* file;
|
||||||
|
wxString temp_path_wstring(tmp_path.wstring());
|
||||||
|
file = fopen(temp_path_wstring.c_str(), "wb");
|
||||||
|
assert(file != NULL);
|
||||||
|
if (file == NULL) {
|
||||||
|
std::string line1 = GUI::format(_utf8("Download from %1% couldn't start:"), data.url);
|
||||||
|
std::string line2 = GUI::format(_utf8("Can't create file at %1%."), tmp_path.string());
|
||||||
|
std::string message = GUI::format("%1%\n%2%", line1, line2);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << message;
|
||||||
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
|
evt->SetString(message);
|
||||||
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
return boost::filesystem::path();
|
return boost::filesystem::path();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +243,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
|||||||
GUI::wxGetApp().QueueEvent(evt);
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
return false;
|
return false;
|
||||||
} else if (progress.dltotal > 0 && progress.dltotal < expected_size) {
|
} else if (progress.dltotal > 0 && progress.dltotal < expected_size) {
|
||||||
//lm:When will this happen? Is that not an error? // dk: It is possible error, but we cannot know until the download is finished. Somehow the total size can grow during the download.
|
// This is possible error, but we cannot know until the download is finished. Somehow the total size can grow during the download.
|
||||||
BOOST_LOG_TRIVIAL(info) << GUI::format("Downloading new %1% has incorrect size. The download will continue. \nExpected size: %2%\nDownload size: %3%", SLIC3R_APP_NAME, expected_size, progress.dltotal);
|
BOOST_LOG_TRIVIAL(info) << GUI::format("Downloading new %1% has incorrect size. The download will continue. \nExpected size: %2%\nDownload size: %3%", SLIC3R_APP_NAME, expected_size, progress.dltotal);
|
||||||
}
|
}
|
||||||
// progress event
|
// progress event
|
||||||
@ -232,27 +258,26 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// on_complete
|
// on_complete
|
||||||
, [dest_path, expected_size](std::string body, std::string& error_message){
|
, [&file, dest_path, tmp_path, expected_size](std::string body, std::string& error_message){
|
||||||
// Size check. Does always 1 char == 1 byte?
|
// Size check. Does always 1 char == 1 byte?
|
||||||
size_t body_size = body.size();
|
size_t body_size = body.size();
|
||||||
if (body_size != expected_size) {
|
if (body_size != expected_size) {
|
||||||
//lm:UI message? // dk: changed. Now it propagates to UI.
|
error_message = GUI::format(_utf8("Downloaded file has wrong size. Expected size: %1% Downloaded size: %2%"), expected_size, body_size);
|
||||||
error_message = GUI::format("Downloaded file has wrong size. Expected size: %1% Downloaded size: %2%", expected_size, body_size);
|
return false;
|
||||||
|
}
|
||||||
|
if (file == NULL) {
|
||||||
|
error_message = GUI::format(_utf8("Can't create file at %1%."), tmp_path.string());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
boost::filesystem::path tmp_path = dest_path;
|
|
||||||
tmp_path += format(".%1%%2%", get_current_pid(), ".download");
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FILE* file;
|
|
||||||
file = fopen(tmp_path.string().c_str(), "wb");
|
|
||||||
fwrite(body.c_str(), 1, body.size(), file);
|
fwrite(body.c_str(), 1, body.size(), file);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
boost::filesystem::rename(tmp_path, dest_path);
|
boost::filesystem::rename(tmp_path, dest_path);
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
error_message = GUI::format("Failed to write and move %1% to %2%:/n%3%", tmp_path, dest_path, e.what());
|
error_message = GUI::format(_utf8("Failed to write to file or to move %1% to %2%:\n%3%"), tmp_path, dest_path, e.what());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -261,16 +286,19 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
|||||||
);
|
);
|
||||||
if (!res)
|
if (!res)
|
||||||
{
|
{
|
||||||
if (m_cancel)
|
if (m_cancel) {
|
||||||
{
|
BOOST_LOG_TRIVIAL(info) << error_message;
|
||||||
BOOST_LOG_TRIVIAL(info) << error_message; //lm:Is this an error? // dk: changed to info
|
|
||||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification
|
||||||
GUI::wxGetApp().QueueEvent(evt);
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
} else {
|
} else {
|
||||||
std::string message = GUI::format("Downloading new %1% has failed:\n%2%", SLIC3R_APP_NAME, error_message);
|
std::string message = (error_message.empty()
|
||||||
BOOST_LOG_TRIVIAL(error) << message;
|
? std::string()
|
||||||
|
: GUI::format(_utf8("Downloading new %1% has failed:\n%2%"), SLIC3R_APP_NAME, error_message));
|
||||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||||
evt->SetString(message);
|
if (!message.empty()) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << message;
|
||||||
|
evt->SetString(message);
|
||||||
|
}
|
||||||
GUI::wxGetApp().QueueEvent(evt);
|
GUI::wxGetApp().QueueEvent(evt);
|
||||||
}
|
}
|
||||||
return boost::filesystem::path();
|
return boost::filesystem::path();
|
||||||
|
@ -108,18 +108,18 @@ SCENARIO("Ooze prevention", "[Multi]")
|
|||||||
|
|
||||||
Polygon convex_hull = Geometry::convex_hull(extrusion_points);
|
Polygon convex_hull = Geometry::convex_hull(extrusion_points);
|
||||||
|
|
||||||
THEN("all nozzles are outside skirt at toolchange") {
|
// THEN("all nozzles are outside skirt at toolchange") {
|
||||||
Points t;
|
// Points t;
|
||||||
sort_remove_duplicates(toolchange_points);
|
// sort_remove_duplicates(toolchange_points);
|
||||||
size_t inside = 0;
|
// size_t inside = 0;
|
||||||
for (const auto &point : toolchange_points)
|
// for (const auto &point : toolchange_points)
|
||||||
for (const Vec2d &offset : print_config.extruder_offset.values) {
|
// for (const Vec2d &offset : print_config.extruder_offset.values) {
|
||||||
Point p = point + scaled<coord_t>(offset);
|
// Point p = point + scaled<coord_t>(offset);
|
||||||
if (convex_hull.contains(p))
|
// if (convex_hull.contains(p))
|
||||||
++ inside;
|
// ++ inside;
|
||||||
}
|
// }
|
||||||
REQUIRE(inside == 0);
|
// REQUIRE(inside == 0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
require "Slic3r/SVG.pm";
|
require "Slic3r/SVG.pm";
|
||||||
|
Loading…
Reference in New Issue
Block a user