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_colour = #FFFF6F
|
||||
filament_spool_weight = 201
|
||||
bed_temperature = 75
|
||||
first_layer_bed_temperature = 75
|
||||
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"
|
||||
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(); }
|
||||
// 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(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
|
||||
{
|
||||
|
@ -32,8 +32,8 @@ public:
|
||||
ExPolygon& operator=(const ExPolygon &other) = default;
|
||||
ExPolygon& operator=(ExPolygon &&other) = default;
|
||||
|
||||
Polygon contour;
|
||||
Polygons holes;
|
||||
Polygon contour; //CCW
|
||||
Polygons holes; //CW
|
||||
|
||||
void clear() { contour.points.clear(); holes.clear(); }
|
||||
void scale(double factor);
|
||||
|
@ -113,26 +113,19 @@ namespace Slic3r {
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
// move to the nearest standby point
|
||||
if (!this->standby_points.empty()) {
|
||||
// get current position in print coordinates
|
||||
Vec3d writer_pos = gcodegen.writer().get_position();
|
||||
Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
|
||||
|
||||
// find standby point
|
||||
Point standby_point = nearest_point(this->standby_points, pos).first;
|
||||
|
||||
/* 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
|
||||
of the destination point must not be transformed by origin nor current extruder offset. */
|
||||
gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
|
||||
"move to standby position");
|
||||
}
|
||||
|
||||
unsigned int extruder_id = gcodegen.writer().extruder()->id();
|
||||
const ConfigOptionIntsNullable& filament_idle_temp = gcodegen.config().idle_temperature;
|
||||
if (filament_idle_temp.is_nil(extruder_id)) {
|
||||
// There is no idle temperature defined in filament settings.
|
||||
// Use the delta value from print config.
|
||||
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());
|
||||
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
|
||||
}
|
||||
} else {
|
||||
// Use the value from filament settings. That one is absolute, not delta.
|
||||
gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
|
||||
}
|
||||
|
||||
return gcode;
|
||||
@ -145,8 +138,7 @@ namespace Slic3r {
|
||||
std::string();
|
||||
}
|
||||
|
||||
int
|
||||
OozePrevention::_get_temp(GCode& gcodegen)
|
||||
int OozePrevention::_get_temp(const GCode& gcodegen) const
|
||||
{
|
||||
return (gcodegen.layer() != nullptr && gcodegen.layer()->id() == 0)
|
||||
? 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);
|
||||
|
||||
if (! tcr.priming) {
|
||||
// Move over the wipe tower.
|
||||
double current_z = gcodegen.writer().get_position().z();
|
||||
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();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(
|
||||
@ -249,81 +249,35 @@ namespace Slic3r {
|
||||
gcode += gcodegen.unretract();
|
||||
}
|
||||
|
||||
double current_z = gcodegen.writer().get_position().z();
|
||||
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
||||
z = current_z;
|
||||
if (! is_approx(z, current_z)) {
|
||||
if (will_go_down) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
|
||||
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;
|
||||
const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value;
|
||||
if (! toolchange_gcode.empty()) {
|
||||
DynamicConfig config;
|
||||
int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1;
|
||||
config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id));
|
||||
config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id));
|
||||
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
|
||||
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 deretraction_str;
|
||||
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
|
||||
if (gcodegen.config().single_extruder_multi_material)
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower)
|
||||
deretraction_str = gcodegen.unretract();
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
|
||||
// Insert the toolchange and deretraction gcode into the generated gcode.
|
||||
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.
|
||||
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("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);
|
||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
check_add_eol(toolchange_gcode_str);
|
||||
|
||||
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
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)
|
||||
{
|
||||
// Calculate wiping points if needed
|
||||
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
|
||||
}
|
||||
}
|
||||
ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
|
||||
}
|
||||
|
||||
// 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_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() }));
|
||||
|
||||
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);
|
||||
// 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.
|
||||
file.write(this->preamble());
|
||||
|
||||
// Calculate wiping points if needed
|
||||
// Enable ooze prevention if configured so.
|
||||
DoExport::init_ooze_prevention(print, m_ooze_prevention);
|
||||
|
||||
print.throw_if_canceled();
|
||||
|
||||
// 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);
|
||||
} else {
|
||||
// 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.
|
||||
int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
|
||||
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
|
||||
// first_layer_temperature vs. temperature settings.
|
||||
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.
|
||||
// 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());
|
||||
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_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()) {
|
||||
// Process the start_filament_gcode for the filament.
|
||||
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)));
|
||||
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
|
||||
check_add_eol(gcode);
|
||||
@ -3201,8 +3140,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
||||
m_wipe.reset_path();
|
||||
|
||||
if (m_writer.extruder() != nullptr) {
|
||||
// Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower
|
||||
// so it should not be injected twice.
|
||||
// Process the custom end_filament_gcode.
|
||||
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);
|
||||
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
|
||||
// standby point and set it to the standby temperature.
|
||||
// If ooze prevention is enabled, set current extruder to the standby temperature.
|
||||
if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
|
||||
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()) {
|
||||
// Process the start_filament_gcode for the new filament.
|
||||
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)));
|
||||
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
|
||||
check_add_eol(gcode);
|
||||
|
@ -39,14 +39,13 @@ struct PrintInstance;
|
||||
class OozePrevention {
|
||||
public:
|
||||
bool enable;
|
||||
Points standby_points;
|
||||
|
||||
OozePrevention() : enable(false) {}
|
||||
std::string pre_toolchange(GCode &gcodegen);
|
||||
std::string post_toolchange(GCode &gcodegen);
|
||||
|
||||
private:
|
||||
int _get_temp(GCode &gcodegen);
|
||||
int _get_temp(const GCode &gcodegen) const;
|
||||
};
|
||||
|
||||
class Wipe {
|
||||
|
@ -122,39 +122,18 @@ std::vector<ExtendedPoint> estimate_points_properties(const std::vector<P>
|
||||
CurvatureEstimator cestim;
|
||||
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;
|
||||
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>());
|
||||
start_point.distance = distance + boundary_offset;
|
||||
start_point.nearest_prev_layer_line = nearest_line;
|
||||
points.push_back(start_point);
|
||||
}
|
||||
for (size_t i = 1; i < extrusion_points.size(); i++) {
|
||||
ExtendedPoint next_point{maybe_unscale(extrusion_points[i])};
|
||||
for (size_t i = 1; i < input_points.size(); 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>());
|
||||
next_point.distance = distance + boundary_offset;
|
||||
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) {
|
||||
std::vector<ExtendedPoint> new_points;
|
||||
new_points.reserve(points.size() * 2);
|
||||
new_points.reserve(points.size()*2);
|
||||
new_points.push_back(points.front());
|
||||
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++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);
|
||||
}
|
||||
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) {
|
||||
|
@ -71,6 +71,8 @@ public:
|
||||
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_z(float z)
|
||||
@ -806,6 +808,8 @@ void WipeTower::toolchange_Unload(
|
||||
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 Vec2f ramming_start_pos = Vec2f(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f);
|
||||
|
||||
writer.append("; CP TOOLCHANGE UNLOAD\n")
|
||||
.change_analyzer_line_width(line_width);
|
||||
|
||||
@ -814,10 +818,12 @@ void WipeTower::toolchange_Unload(
|
||||
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
||||
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 (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
|
||||
float sparse_beginning_y = 0.f;
|
||||
@ -849,7 +855,7 @@ void WipeTower::toolchange_Unload(
|
||||
writer.disable_linear_advance();
|
||||
|
||||
// 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 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:
|
||||
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& final_speed = m_filpar[m_current_tool].cooling_final_speed;
|
||||
|
||||
@ -916,14 +922,20 @@ void WipeTower::toolchange_Unload(
|
||||
}
|
||||
}
|
||||
|
||||
if (m_semm) {
|
||||
// let's wait is necessary:
|
||||
writer.wait(m_filpar[m_current_tool].delay);
|
||||
// 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:
|
||||
// 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()
|
||||
.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.
|
||||
// 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");
|
||||
|
||||
// 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())
|
||||
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
|
||||
+ 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
|
||||
// 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.append("[start_filament_gcode]\n");
|
||||
//writer.append("[start_filament_gcode]\n");
|
||||
|
||||
writer.flush_planner_queue();
|
||||
m_current_tool = new_tool;
|
||||
|
@ -290,7 +290,6 @@ private:
|
||||
// Extruder specific parameters.
|
||||
std::vector<FilamentParameters> m_filpar;
|
||||
|
||||
|
||||
// State of the wipe tower generator.
|
||||
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.
|
||||
|
@ -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",
|
||||
"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",
|
||||
"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",
|
||||
"start_filament_gcode", "end_filament_gcode",
|
||||
// Retract overrides
|
||||
|
@ -191,6 +191,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "infill_first"
|
||||
|| opt_key == "single_extruder_multi_material"
|
||||
|| opt_key == "temperature"
|
||||
|| opt_key == "idle_temperature"
|
||||
|| opt_key == "wipe_tower"
|
||||
|| opt_key == "wipe_tower_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
|
||||
{
|
||||
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
|
||||
@ -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.");
|
||||
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).");
|
||||
if (m_config.ooze_prevention)
|
||||
return L("Ooze prevention is currently not supported with the wipe tower enabled.");
|
||||
if (m_config.ooze_prevention && m_config.single_extruder_multi_material)
|
||||
return L("Ooze prevention is only supported with the wipe tower when 'single_extruder_multi_material' is off.");
|
||||
if (m_config.use_volumetric_e)
|
||||
return L("The Wipe Tower currently does not support volumetric E (use_volumetric_e=0).");
|
||||
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;
|
||||
}
|
||||
|
||||
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
|
||||
namespace {
|
||||
const int max_temp = 1500;
|
||||
};
|
||||
|
||||
PrintConfigDef::PrintConfigDef()
|
||||
{
|
||||
this->init_common_params();
|
||||
@ -1972,9 +1977,7 @@ void PrintConfigDef::init_fff_params()
|
||||
|
||||
def = this->add("ooze_prevention", coBool);
|
||||
def->label = L("Enable");
|
||||
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->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. ");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
@ -2475,7 +2478,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("standby_temperature_delta", coInt);
|
||||
def->label = L("Temperature variation");
|
||||
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->min = -max_temp;
|
||||
def->max = max_temp;
|
||||
@ -3731,6 +3735,15 @@ void PrintConfigDef::init_sla_params()
|
||||
def->min = 0;
|
||||
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->label = L("Bottle volume");
|
||||
def->tooltip = L("Bottle volume");
|
||||
@ -4511,6 +4524,12 @@ std::string validate(const FullPrintConfig &cfg)
|
||||
assert(opt != nullptr);
|
||||
const ConfigOptionDef *optdef = print_config_def.get(opt_key);
|
||||
assert(optdef != nullptr);
|
||||
|
||||
if (opt->nullable() && opt->is_nil()) {
|
||||
// Do not check nil values
|
||||
continue;
|
||||
}
|
||||
|
||||
bool out_of_range = false;
|
||||
switch (opt->type()) {
|
||||
case coFloat:
|
||||
|
@ -769,6 +769,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloatOrPercent, first_layer_height))
|
||||
((ConfigOptionFloatOrPercent, first_layer_speed))
|
||||
((ConfigOptionInts, first_layer_temperature))
|
||||
((ConfigOptionIntsNullable, idle_temperature))
|
||||
((ConfigOptionInts, full_fan_speed_layer))
|
||||
((ConfigOptionFloat, infill_acceleration))
|
||||
((ConfigOptionBool, infill_first))
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "Exception.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
@ -21,14 +23,19 @@
|
||||
#include "SupportSpotsGenerator.hpp"
|
||||
#include "TriangleSelectorWrapper.hpp"
|
||||
#include "format.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <float.h>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
@ -406,21 +413,6 @@ 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()
|
||||
{
|
||||
if (this->set_started(posSupportSpotsSearch)) {
|
||||
@ -428,10 +420,51 @@ void PrintObject::generate_support_spots()
|
||||
m_print->set_status(75, L("Searching support spots"));
|
||||
if (!this->shared_regions()->generated_support_points.has_value()) {
|
||||
PrintTryCancel cancel_func = m_print->make_try_cancel();
|
||||
SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values};
|
||||
SupportSpotsGenerator::SupportPoints supp_points = SupportSpotsGenerator::full_search(this, cancel_func, params);
|
||||
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())};
|
||||
auto [supp_points, partial_objects] = SupportSpotsGenerator::full_search(this, cancel_func, params);
|
||||
this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points};
|
||||
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";
|
||||
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
|
||||
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_malformations(this->layers(), params);
|
||||
m_print->throw_if_canceled();
|
||||
@ -580,6 +616,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
if ( opt_key == "brim_width"
|
||||
|| opt_key == "brim_separation"
|
||||
|| opt_key == "brim_type") {
|
||||
steps.emplace_back(posSupportSpotsSearch);
|
||||
// Brim is printed below supports, support invalidates brim and skirt.
|
||||
steps.emplace_back(posSupportMaterial);
|
||||
} else if (
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#define SLAPRINT_DO_BENCHMARK
|
||||
// #define SLAPRINT_DO_BENCHMARK
|
||||
|
||||
#ifdef SLAPRINT_DO_BENCHMARK
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "PrincipalComponents2D.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "PrintBase.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "tbb/parallel_for.h"
|
||||
@ -23,6 +24,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <stack>
|
||||
@ -68,7 +70,7 @@ public:
|
||||
float len;
|
||||
const ExtrusionEntity *origin_entity;
|
||||
|
||||
bool support_point_generated = false;
|
||||
std::optional<SupportSpotsGenerator::SupportPointCause> support_point_generated = {};
|
||||
float form_quality = 1.0f;
|
||||
float curled_up_height = 0.0f;
|
||||
|
||||
@ -81,10 +83,6 @@ auto get_b(ExtrusionLine &&l) { return l.b; }
|
||||
|
||||
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>;
|
||||
|
||||
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());
|
||||
}
|
||||
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
|
||||
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
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
|
||||
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
|
||||
prev_layer_lines, flow_width,
|
||||
@ -270,21 +321,44 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
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;
|
||||
|
||||
float max_bridge_len = params.bridge_distance /
|
||||
((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature)));
|
||||
SupportPointCause potential_cause = SupportPointCause::FloatingExtrusion;
|
||||
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) {
|
||||
line_out.form_quality = 0.8f;
|
||||
bridged_distance += line_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;
|
||||
}
|
||||
} else if (curr_point.distance > flow_width * (1.0 + std::clamp(curr_point.curvature, -0.30f, 0.20f))) {
|
||||
bridged_distance += line_len;
|
||||
line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f;
|
||||
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;
|
||||
bridged_distance = 0.0f;
|
||||
}
|
||||
@ -349,11 +423,13 @@ public:
|
||||
Vec3f sticking_centroid_accumulator = Vec3f::Zero();
|
||||
Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero();
|
||||
float sticking_second_moment_of_area_covariance_accumulator{};
|
||||
bool connected_to_bed = false;
|
||||
|
||||
ObjectPart() = default;
|
||||
|
||||
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 += other.volume;
|
||||
this->sticking_area += other.sticking_area;
|
||||
@ -416,7 +492,7 @@ public:
|
||||
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 Vec3f &extreme_point,
|
||||
float layer_z,
|
||||
@ -434,7 +510,7 @@ public:
|
||||
|
||||
// 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;
|
||||
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;
|
||||
#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
|
||||
{
|
||||
if (connection.area < EPSILON) return 1.0f;
|
||||
if (connection.area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart};
|
||||
|
||||
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,
|
||||
connection.second_moment_of_area_accumulator,
|
||||
connection.second_moment_of_area_covariance_accumulator,
|
||||
@ -492,7 +571,7 @@ public:
|
||||
params.material_yield_strength;
|
||||
|
||||
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_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;
|
||||
#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
|
||||
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;
|
||||
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());
|
||||
const Layer *l = region->layer();
|
||||
float slice_z = l->slice_z;
|
||||
@ -537,7 +618,8 @@ std::tuple<ObjectPart, float> build_object_part_from_slice(const LayerSlice &sli
|
||||
new_object_part.volume += 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
|
||||
new_object_part.connected_to_bed = true;
|
||||
float sticking_area = line.len * flow_width;
|
||||
new_object_part.sticking_area += sticking_area;
|
||||
Vec2f middle = Vec2f((line.a + line.b) / 2.0f);
|
||||
@ -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};
|
||||
}
|
||||
|
||||
@ -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{};
|
||||
SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points);
|
||||
ActiveObjectParts active_object_parts{};
|
||||
PartialObjects partial_objects{};
|
||||
LD prev_layer_ext_perim_lines;
|
||||
|
||||
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> 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) {
|
||||
cancel_func();
|
||||
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) {
|
||||
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);
|
||||
|
||||
#ifdef DETAILED_DEBUG_LOGS
|
||||
@ -670,7 +804,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
||||
|
||||
final_part_id = *parts_ids.begin();
|
||||
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) {
|
||||
@ -731,7 +868,8 @@ 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,
|
||||
// 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,
|
||||
&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
|
||||
// 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) {
|
||||
@ -739,14 +877,14 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
||||
}
|
||||
|
||||
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
|
||||
// unrealistic amount of stability to the object (due to overlaping of local support points)
|
||||
// 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 unrealistic amount of stability to the object (due to overlaping of local support points)
|
||||
if (!(supports_presence_grid.position_taken(support_point))) {
|
||||
part.add_support_point(support_point, area);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// The support point also increases the stability of the weakest connection of the object, which should be reflected
|
||||
@ -768,9 +906,11 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
||||
const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx];
|
||||
if (entity->role() == ExtrusionRole::BridgeInfill) {
|
||||
for (const ExtrusionLine &bridge :
|
||||
check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines,prev_layer_boundary, params)) {
|
||||
if (bridge.support_point_generated) {
|
||||
reckon_new_support_point(create_support_point_position(bridge.b), -EPSILON, Vec2f::Zero());
|
||||
check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, prev_layer_boundary,
|
||||
params)) {
|
||||
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,
|
||||
prev_layer_ext_perim_lines,prev_layer_boundary, params);
|
||||
for (const ExtrusionLine &perim : perims) {
|
||||
if (perim.support_point_generated) {
|
||||
reckon_new_support_point(create_support_point_position(perim.b), -EPSILON, Vec2f::Zero());
|
||||
if (perim.support_point_generated.has_value()) {
|
||||
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;
|
||||
|
||||
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;
|
||||
} else {
|
||||
unchecked_dist = line.len;
|
||||
@ -803,8 +947,10 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
||||
auto [dist, nidx,
|
||||
nearest_point] = current_slice_lines_distancer.distance_from_lines_extra<false>(pivot_site_search_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);
|
||||
if (force > 0) { reckon_new_support_point(support_point, force, (line.b - line.a).normalized()); }
|
||||
auto [force, cause] = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params);
|
||||
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(),
|
||||
@ -812,11 +958,16 @@ SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cance
|
||||
} // slice iterations
|
||||
prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines);
|
||||
} // 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
|
||||
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;
|
||||
{
|
||||
@ -827,13 +978,27 @@ void debug_export(SupportPoints support_points, std::string file_name)
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < support_points.size(); ++i) {
|
||||
if (support_points[i].force <= 0) {
|
||||
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), 0.0, 1.0, 0.0);
|
||||
} else {
|
||||
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), 1.0, 0.0, 0.0);
|
||||
Vec3f color{1.0f, 1.0f, 1.0f};
|
||||
switch (support_points[i].cause) {
|
||||
case SupportPointCause::FloatingBridgeAnchor: color = {0.863281f, 0.109375f, 0.113281f}; break; //RED
|
||||
case SupportPointCause::LongBridge: color = {0.960938f, 0.90625f, 0.0625f}; break; // YELLOW
|
||||
case SupportPointCause::FloatingExtrusion: color = {0.921875f, 0.515625f, 0.101563f}; break; // ORANGE
|
||||
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);
|
||||
@ -841,17 +1006,15 @@ void debug_export(SupportPoints support_points, std::string file_name)
|
||||
}
|
||||
#endif
|
||||
|
||||
// std::vector<size_t> quick_search(const PrintObject *po, const Params ¶ms) {
|
||||
// return {};
|
||||
// }
|
||||
SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms)
|
||||
std::tuple<SupportPoints, PartialObjects> 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
|
||||
debug_export(supp_points, "issues");
|
||||
auto [supp_points, objects] = results;
|
||||
debug_export(supp_points, objects, "issues");
|
||||
#endif
|
||||
|
||||
return supp_points;
|
||||
return results;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 Slic3r
|
||||
|
@ -4,43 +4,52 @@
|
||||
#include "Layer.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "PrintBase.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace SupportSpotsGenerator {
|
||||
|
||||
struct Params {
|
||||
Params(const std::vector<std::string> &filament_types) {
|
||||
struct Params
|
||||
{
|
||||
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) {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "SupportSpotsGenerator does not currently handle different materials properly, only first will be used";
|
||||
}
|
||||
if (filament_types.empty() || filament_types[0].empty()) {
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< "SupportSpotsGenerator error: empty filament_type";
|
||||
BOOST_LOG_TRIVIAL(error) << "SupportSpotsGenerator error: empty filament_type";
|
||||
filament_type = std::string("PLA");
|
||||
} else {
|
||||
filament_type = filament_types[0];
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "SupportSpotsGenerator: applying filament type: " << filament_type;
|
||||
BOOST_LOG_TRIVIAL(debug) << "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]
|
||||
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 float max_curled_height_factor = 10.0f;
|
||||
|
||||
const float min_distance_between_support_points = 3.0f; //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
|
||||
|
||||
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 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 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, ... );
|
||||
@ -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
|
||||
double get_bed_adhesion_yield_strength() const {
|
||||
if (raft_layers_count > 0) {
|
||||
return get_support_spots_adhesion_strength() * 2.0;
|
||||
}
|
||||
|
||||
if (filament_type == "PLA") {
|
||||
return 0.018 * 1e6;
|
||||
} else if (filament_type == "PET" || filament_type == "PETG") {
|
||||
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
|
||||
return 0.018 * 1e6;
|
||||
}
|
||||
@ -63,28 +78,46 @@ struct Params {
|
||||
}
|
||||
};
|
||||
|
||||
// The support points are generated for two reasons:
|
||||
enum class SupportPointCause {
|
||||
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.)
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
struct SupportPoint {
|
||||
SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction);
|
||||
bool is_local_extrusion_support() const { return force < 0; }
|
||||
struct SupportPoint
|
||||
{
|
||||
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(); }
|
||||
|
||||
//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;
|
||||
// force that destabilizes the object to the point of falling/breaking. It is in g*mm/s^2 units
|
||||
// values gathered from large XL print: Min : 0 | Max : 18713800 | Average : 1361186 | Median : 329103
|
||||
// force that destabilizes the object to the point of falling/breaking. g*mm/s^2 units
|
||||
// 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
|
||||
// The final printed object weight was approx 0.5 Kg
|
||||
// The final sliced object weight was approx 0.5 Kg
|
||||
float force;
|
||||
// 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
|
||||
@ -99,13 +132,28 @@ struct Malformations {
|
||||
std::vector<Lines> layers; //for each layer
|
||||
};
|
||||
|
||||
// std::vector<size_t> quick_search(const PrintObject *po, const Params ¶ms);
|
||||
SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms);
|
||||
struct PartialObject
|
||||
{
|
||||
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);
|
||||
void estimate_malformations(std::vector<Layer*> &layers, const Params ¶ms);
|
||||
Vec3f centroid;
|
||||
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_ */
|
||||
|
@ -1751,7 +1751,7 @@ PageBuildVolume::PageBuildVolume(ConfigWizard* parent)
|
||||
: ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1)
|
||||
, 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";
|
||||
build_volume->SetValue(value);
|
||||
|
@ -119,8 +119,8 @@ void FileGet::priv::get_perform()
|
||||
|
||||
wxString temp_path_wstring(m_tmp_path.wstring());
|
||||
|
||||
std::cout << "dest_path: " << dest_path.string() << std::endl;
|
||||
std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
|
||||
//std::cout << "dest_path: " << dest_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);
|
||||
|
||||
|
@ -762,28 +762,26 @@ void SpinCtrl::BUILD() {
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
|
||||
|
||||
wxString text_value = wxString("");
|
||||
int default_value = 0;
|
||||
int default_value = UNDEF_VALUE;
|
||||
|
||||
switch (m_opt.type) {
|
||||
case coInt:
|
||||
default_value = m_opt.default_value->getInt();
|
||||
text_value = wxString::Format(_T("%i"), default_value);
|
||||
break;
|
||||
case coInts:
|
||||
{
|
||||
const ConfigOptionInts *vec = m_opt.get_default_value<ConfigOptionInts>();
|
||||
if (vec == nullptr || vec->empty()) break;
|
||||
for (size_t id = 0; id < vec->size(); ++id)
|
||||
{
|
||||
default_value = vec->get_at(id);
|
||||
text_value += wxString::Format(_T("%i"), default_value);
|
||||
}
|
||||
default_value = m_opt.get_default_value<ConfigOptionInts>()->get_at(m_opt_idx);
|
||||
if (m_opt.nullable)
|
||||
m_last_meaningful_value = default_value == ConfigOptionIntsNullable::nil_value() ? static_cast<int>(m_opt.max) : default_value;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (default_value != UNDEF_VALUE)
|
||||
text_value = wxString::Format(_T("%i"), default_value);
|
||||
|
||||
const int min_val = m_opt.min == -FLT_MAX
|
||||
#ifdef __WXOSX__
|
||||
// We will forcibly set the input value for SpinControl, since the value
|
||||
@ -882,6 +880,50 @@ void SpinCtrl::BUILD() {
|
||||
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()
|
||||
{
|
||||
// check if value was really changed
|
||||
|
@ -330,24 +330,18 @@ public:
|
||||
void BUILD() override;
|
||||
/// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value() ;
|
||||
|
||||
/*
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) override {
|
||||
m_disable_change_event = !change_event;
|
||||
tmp_value = boost::any_cast<int>(value);
|
||||
m_value = value;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
*/
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
void set_last_meaningful_value() override;
|
||||
void set_na_value() override;
|
||||
|
||||
boost::any& get_value() override {
|
||||
int value = static_cast<wxSpinCtrl*>(window)->GetValue();
|
||||
return m_value = value;
|
||||
}
|
||||
boost::any& get_value() override;
|
||||
|
||||
void msw_rescale() override;
|
||||
|
||||
|
@ -43,8 +43,8 @@ bool GLGizmoFdmSupports::on_init()
|
||||
{
|
||||
m_shortcut_key = WXK_CONTROL_L;
|
||||
|
||||
m_desc["auto_generate"] = _L("Auto-generate supports");
|
||||
m_desc["generating"] = _L("Generating supports...");
|
||||
m_desc["autopaint"] = _L("Automatic painting");
|
||||
m_desc["painting"] = _L("painting...");
|
||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||
m_desc["reset_direction"] = _L("Reset direction");
|
||||
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();
|
||||
|
||||
if (waiting_for_autogenerated_supports) {
|
||||
m_imgui->text(m_desc.at("generating"));
|
||||
m_imgui->text(m_desc.at("painting"));
|
||||
} else {
|
||||
bool generate = m_imgui->button(m_desc.at("auto_generate"));
|
||||
bool generate = m_imgui->button(m_desc.at("autopaint"));
|
||||
if (generate)
|
||||
auto_generate();
|
||||
}
|
||||
@ -525,12 +525,12 @@ void GLGizmoFdmSupports::auto_generate()
|
||||
});
|
||||
|
||||
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("Warning"), wxICON_WARNING | wxYES | wxNO);
|
||||
|
||||
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;
|
||||
for (ModelVolume *mv : mo->volumes) {
|
||||
if (mv->is_model_part()) {
|
||||
|
@ -1826,13 +1826,8 @@ static void validate_custom_gcode_cb(Tab* tab, const wxString& title, const t_co
|
||||
tab->on_value_change(opt_key, value);
|
||||
}
|
||||
|
||||
void TabFilament::add_filament_overrides_page()
|
||||
void TabFilament::create_line_with_near_label_widget(ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index/* = 0*/)
|
||||
{
|
||||
PageShp page = add_options_page(L("Filament Overrides"), "wrench");
|
||||
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);
|
||||
@ -1863,7 +1858,26 @@ void TabFilament::add_filament_overrides_page()
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
PageShp page = add_options_page(L("Filament Overrides"), "wrench");
|
||||
ConfigOptionsGroupShp optgroup = page->new_optgroup(L("Retraction"));
|
||||
|
||||
const int extruder_idx = 0; // #ys_FIXME
|
||||
|
||||
@ -1879,7 +1893,7 @@ void TabFilament::add_filament_overrides_page()
|
||||
"filament_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()
|
||||
@ -1914,14 +1928,7 @@ void TabFilament::update_filament_overrides_page()
|
||||
for (const std::string& opt_key : opt_keys)
|
||||
{
|
||||
bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length;
|
||||
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, extruder_idx);
|
||||
if (field != nullptr)
|
||||
field->toggle(is_checked);
|
||||
update_line_with_near_label_widget(optgroup, opt_key, extruder_idx, is_checked);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1952,6 +1959,9 @@ void TabFilament::build()
|
||||
};
|
||||
|
||||
optgroup = page->new_optgroup(L("Temperature"));
|
||||
|
||||
create_line_with_near_label_widget(optgroup, "idle_temperature");
|
||||
|
||||
Line line = { L("Nozzle"), "" };
|
||||
line.append_option(optgroup->get_option("first_layer_temperature"));
|
||||
line.append_option(optgroup->get_option("temperature"));
|
||||
@ -2142,6 +2152,14 @@ void TabFilament::toggle_options()
|
||||
|
||||
if (m_active_page->title() == "Filament Overrides")
|
||||
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()
|
||||
|
@ -436,6 +436,8 @@ private:
|
||||
ogStaticText* m_volumetric_speed_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 update_filament_overrides_page();
|
||||
void update_volumetric_flow_preset_hints();
|
||||
|
@ -167,10 +167,14 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos
|
||||
wxString wxext = boost::nowide::widen(extension);
|
||||
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(
|
||||
this
|
||||
, _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()))
|
||||
, wildcard
|
||||
@ -188,35 +192,46 @@ AppUpdateDownloadDialog::AppUpdateDownloadDialog( const Semver& ver_online, boos
|
||||
if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) {
|
||||
btn_ok->SetLabel(_L("Download"));
|
||||
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;
|
||||
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);
|
||||
msgdlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
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();
|
||||
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);
|
||||
msgdlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
show_change = false;
|
||||
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)
|
||||
return;
|
||||
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);
|
||||
msgdlg.ShowModal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
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)
|
||||
return;
|
||||
}
|
||||
@ -241,7 +256,12 @@ bool AppUpdateDownloadDialog::run_after_download() 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
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
@ -36,7 +37,7 @@ namespace {
|
||||
std::string msg;
|
||||
bool res = GUI::create_process(path, std::wstring(), msg);
|
||||
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?)
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED);
|
||||
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)
|
||||
cancel = (m_cancel ? true : !progress_fn(std::move(progress)));
|
||||
if (cancel) {
|
||||
error_message = GUI::format("Error getting: `%1%`: Download was canceled.", //lm:typo //dk: am i blind? :)
|
||||
url);
|
||||
// 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.
|
||||
/*
|
||||
error_message = GUI::format(_utf8("Error getting: `%1%`: Download was canceled."), url);
|
||||
*/
|
||||
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());
|
||||
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();
|
||||
}
|
||||
|
||||
@ -217,7 +243,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
||||
GUI::wxGetApp().QueueEvent(evt);
|
||||
return false;
|
||||
} 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);
|
||||
}
|
||||
// progress event
|
||||
@ -232,27 +258,26 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
||||
return true;
|
||||
}
|
||||
// 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_t body_size = body.size();
|
||||
if (body_size != expected_size) {
|
||||
//lm:UI message? // dk: changed. Now it propagates to UI.
|
||||
error_message = GUI::format("Downloaded file has wrong size. Expected size: %1% Downloaded size: %2%", expected_size, body_size);
|
||||
error_message = GUI::format(_utf8("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;
|
||||
}
|
||||
boost::filesystem::path tmp_path = dest_path;
|
||||
tmp_path += format(".%1%%2%", get_current_pid(), ".download");
|
||||
try
|
||||
{
|
||||
FILE* file;
|
||||
file = fopen(tmp_path.string().c_str(), "wb");
|
||||
fwrite(body.c_str(), 1, body.size(), file);
|
||||
fclose(file);
|
||||
boost::filesystem::rename(tmp_path, dest_path);
|
||||
}
|
||||
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 true;
|
||||
@ -261,16 +286,19 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d
|
||||
);
|
||||
if (!res)
|
||||
{
|
||||
if (m_cancel)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << error_message; //lm:Is this an error? // dk: changed to info
|
||||
if (m_cancel) {
|
||||
BOOST_LOG_TRIVIAL(info) << error_message;
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification
|
||||
GUI::wxGetApp().QueueEvent(evt);
|
||||
} else {
|
||||
std::string message = GUI::format("Downloading new %1% has failed:\n%2%", SLIC3R_APP_NAME, error_message);
|
||||
BOOST_LOG_TRIVIAL(error) << message;
|
||||
std::string message = (error_message.empty()
|
||||
? 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);
|
||||
if (!message.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << message;
|
||||
evt->SetString(message);
|
||||
}
|
||||
GUI::wxGetApp().QueueEvent(evt);
|
||||
}
|
||||
return boost::filesystem::path();
|
||||
|
@ -108,18 +108,18 @@ SCENARIO("Ooze prevention", "[Multi]")
|
||||
|
||||
Polygon convex_hull = Geometry::convex_hull(extrusion_points);
|
||||
|
||||
THEN("all nozzles are outside skirt at toolchange") {
|
||||
Points t;
|
||||
sort_remove_duplicates(toolchange_points);
|
||||
size_t inside = 0;
|
||||
for (const auto &point : toolchange_points)
|
||||
for (const Vec2d &offset : print_config.extruder_offset.values) {
|
||||
Point p = point + scaled<coord_t>(offset);
|
||||
if (convex_hull.contains(p))
|
||||
++ inside;
|
||||
}
|
||||
REQUIRE(inside == 0);
|
||||
}
|
||||
// THEN("all nozzles are outside skirt at toolchange") {
|
||||
// Points t;
|
||||
// sort_remove_duplicates(toolchange_points);
|
||||
// size_t inside = 0;
|
||||
// for (const auto &point : toolchange_points)
|
||||
// for (const Vec2d &offset : print_config.extruder_offset.values) {
|
||||
// Point p = point + scaled<coord_t>(offset);
|
||||
// if (convex_hull.contains(p))
|
||||
// ++ inside;
|
||||
// }
|
||||
// REQUIRE(inside == 0);
|
||||
// }
|
||||
|
||||
#if 0
|
||||
require "Slic3r/SVG.pm";
|
||||
|
Loading…
Reference in New Issue
Block a user