diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 1ca6735cc3..2939425682 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -9,6 +9,11 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(_wx_toolkit "-DwxBUILD_TOOLKIT=gtk${_gtk_ver}") endif() +set(_unicode_utf8 OFF) +if (UNIX) # wxWidgets will not use char as the underlying type for wxString unless its forced to. + set (_unicode_utf8 ON) +endif() + prusaslicer_add_cmake_project(wxWidgets # GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" # GIT_TAG tm_cross_compile #${_wx_git_tag} @@ -23,6 +28,7 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_MEDIACTRL=OFF -DwxUSE_DETECT_SM=OFF -DwxUSE_UNICODE=ON + -DwxUSE_UNICODE_UTF8=${_unicode_utf8} -DwxUSE_OPENGL=ON -DwxUSE_LIBPNG=sys -DwxUSE_ZLIB=sys diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 94f0b5658f..d9274bea93 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -42,7 +42,6 @@ use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; use Slic3r::Flow; use Slic3r::GCode::Reader; -use Slic3r::Geometry::Clipper; use Slic3r::Layer; use Slic3r::Line; use Slic3r::Model; diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 6adb650c21..4ab1bc8ab4 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -4,19 +4,6 @@ use warnings; # an ExPolygon is a polygon with holes -use List::Util qw(first); -use Slic3r::Geometry::Clipper qw(union_ex diff_pl); - -sub offset { - my $self = shift; - return Slic3r::Geometry::Clipper::offset(\@$self, @_); -} - -sub offset_ex { - my $self = shift; - return Slic3r::Geometry::Clipper::offset_ex(\@$self, @_); -} - sub noncollapsing_offset_ex { my $self = shift; my ($distance, @params) = @_; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 286a73e2de..ca262fc766 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -14,10 +14,8 @@ our @EXPORT_OK = qw( dot line_intersection normalize - point_in_segment polyline_lines polygon_is_convex - polygon_segment_having_point scale unscale scaled_epsilon @@ -45,30 +43,6 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } -# used by geometry.t, polygon_segment_having_point -sub point_in_segment { - my ($point, $line) = @_; - - my ($x, $y) = @$point; - my $line_p = $line->pp; - my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X]; - my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y]; - - # check whether the point is in the segment bounding box - return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon) - && $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon); - - # if line is vertical, check whether point's X is the same as the line - if ($line_p->[A][X] == $line_p->[B][X]) { - return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0; - } - - # calculate the Y in line at X of the point - my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y]) - * ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]); - return abs($y3 - $y) < epsilon ? 1 : 0; -} - # used by geometry.t sub polyline_lines { my ($polyline) = @_; @@ -76,17 +50,6 @@ sub polyline_lines { return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1; } -# given a $polygon, return the (first) segment having $point -# used by geometry.t -sub polygon_segment_having_point { - my ($polygon, $point) = @_; - - foreach my $line (@{ $polygon->lines }) { - return $line if point_in_segment($point, $line); - } - return undef; -} - # polygon must be simple (non complex) and ccw sub polygon_is_convex { my ($points) = @_; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index b7a7da7727..c1fa81d9f3 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,9 +6,8 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( offset - offset_ex offset2_ex - diff_ex diff union_ex intersection_ex - JT_ROUND JT_MITER JT_SQUARE - intersection intersection_pl diff_pl union); + offset2_ex + diff_ex diff union_ex + union); 1; diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index bf53520d2d..c06f9a1fa3 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -5,12 +5,6 @@ use warnings; # a line is a two-points line use parent 'Slic3r::Polyline'; -sub intersection { - my $self = shift; - my ($line, $require_crossing) = @_; - return Slic3r::Geometry::line_intersection($self, $line, $require_crossing); -} - sub grow { my $self = shift; return Slic3r::Polyline->new(@$self)->grow(@_); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 0385f88b88..64480e08fe 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -5,9 +5,6 @@ use warnings; use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(scale epsilon); -use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex - offset offset_ex offset2_ex JT_MITER); use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; diff --git a/resources/profiles/TriLAB.idx b/resources/profiles/TriLAB.idx index 1b12439f73..75819e135a 100644 --- a/resources/profiles/TriLAB.idx +++ b/resources/profiles/TriLAB.idx @@ -1,4 +1,5 @@ min_slic3r_version = 2.4.1-rc1 +1.0.1 Fix missing AzteQ Industrial ABS material for 0.6, 0.8 nozzle, enabled elefant foot compensation 1.0.0 Added AzteQ Industrial profiles for 0.8 nozzle, updated spool weight and filament cost, some minor setting improvements min_slic3r_version = 2.3.2-alpha0 0.0.9 Added AzteQ Industrial materials PC/ABS (Fillamentum), PC-Max (Polymaker), Nylon FX256 (Fillamentum), Added DeltiQ 2 materials Nylon PA12 (Fiberlogy), Nylon CF15 Carbon (Fillamentum), PEBA 90A - FlexFill (Fillamentum), MoldLay (Wax-Alike), disabled retract only when crossing perimeters, some minor setting improvements diff --git a/resources/profiles/TriLAB.ini b/resources/profiles/TriLAB.ini index a5b194eca3..b5c31d1832 100644 --- a/resources/profiles/TriLAB.ini +++ b/resources/profiles/TriLAB.ini @@ -6,7 +6,7 @@ name = TriLAB # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.0.0 +config_version = 1.0.1 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/TriLAB/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -118,7 +118,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0.0 +elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -338,6 +338,7 @@ support_material_extrusion_width = 0.55 support_material_interface_spacing = 0.6 support_material_xy_spacing = 0.9 top_infill_extrusion_width = 0.6 +elefant_foot_compensation = 0.2 [print:DeltiQ 0.30mm Strong @0.6 nozzle] inherits = DeltiQ 0.30mm Normal @0.6 nozzle @@ -356,7 +357,7 @@ bottom_solid_min_thickness = 1.2 bridge_flow_ratio = 0.90 bridge_speed = 20 compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_FAMILY_DQ.*/ and nozzle_diameter[0]==0.8 -elefant_foot_compensation = 0.0 +elefant_foot_compensation = 0.2 external_perimeter_extrusion_width = 0.80 external_perimeter_speed = 30 extrusion_width = 0.80 @@ -415,7 +416,7 @@ complete_objects = 0 default_acceleration = 2000 dont_support_bridges = 0 draft_shield = 0 -elefant_foot_compensation = 0 +elefant_foot_compensation = 0.1 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 @@ -558,6 +559,7 @@ support_material_extrusion_width = 0.55 support_material_interface_spacing = 0.6 support_material_xy_spacing = 0.9 top_infill_extrusion_width = 0.6 +elefant_foot_compensation = 0.2 [print:AzteQ Industrial 0.30mm Strong @0.6 nozzle] inherits = AzteQ Industrial 0.30mm Normal @0.6 nozzle @@ -579,7 +581,7 @@ bottom_solid_min_thickness = 0.7 bridge_flow_ratio = 0.95 bridge_speed = 30 dont_support_bridges = 0 -elefant_foot_compensation = 0 +elefant_foot_compensation = 0.2 ensure_vertical_shell_thickness = 0 external_perimeter_extrusion_width = 0.8 external_perimeter_speed = 30 @@ -1413,9 +1415,11 @@ filament_spool_weight = 229 [filament:AzteQ Industrial - ABS - ExtraFill (Fillamentum) @0.6 nozzle] inherits = AzteQ Industrial - ABS - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_MODEL_AQI.*/ and nozzle_diameter[0]==0.6 [filament:AzteQ Industrial - ABS - ExtraFill (Fillamentum) @0.8 nozzle] inherits = AzteQ Industrial - ABS - ExtraFill (Fillamentum) +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_TRILAB.*/ and printer_notes=~/.*PRINTER_MODEL_AQI.*/ and nozzle_diameter[0]==0.8 extrusion_multiplier = 0.95 [filament:AzteQ Industrial - PC/ABS - (Fillamentum)] diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index a087dd8540..bfd307de3d 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "libslic3r.h" #include "clonable_ptr.hpp" @@ -1989,6 +1990,7 @@ public: struct SetDeserializeItem { SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::string_view opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const char *opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const std::string &opt_key, const bool value, bool append = false) : opt_key(opt_key), opt_value(value ? "1" : "0"), append(append) {} SetDeserializeItem(const char *opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 5e6a51d20a..2e9e467890 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -324,10 +324,10 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, E polylines.clear(); } -inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); - for (Polyline &polyline : polylines) + for (const Polyline &polyline : polylines) if (polyline.is_valid()) { ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index f4cf12064d..92e0a698e3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3661,42 +3661,44 @@ void GCodeProcessor::post_process() return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); }; - struct FilamentData - { - double mm{ 0.0 }; - double cm3{ 0.0 }; - double g{ 0.0 }; - double cost{ 0.0 }; - }; + std::vector filament_mm(m_result.extruders_count, 0.0); + std::vector filament_cm3(m_result.extruders_count, 0.0); + std::vector filament_g(m_result.extruders_count, 0.0); + std::vector filament_cost(m_result.extruders_count, 0.0); + + double filament_total_g = 0.0; + double filament_total_cost = 0.0; - FilamentData filament_data; - for (const auto& [role, used] : m_result.print_statistics.used_filaments_per_role) { - filament_data.mm += used.first; - filament_data.g += used.second; - } for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) { - filament_data.cm3 += volume; - filament_data.cost += volume * double(m_result.filament_densities[id]) * double(m_result.filament_cost[id]) * 0.000001; + filament_mm[id] = volume / (static_cast(M_PI) * sqr(0.5 * m_result.filament_diameters[id])); + filament_cm3[id] = volume * 0.001; + filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]); + filament_cost[id] = filament_g[id] * double(m_result.filament_cost[id]) * 0.001; + filament_total_g += filament_g[id]; + filament_total_cost += filament_cost[id]; } - auto process_used_filament = [&filament_data](std::string& gcode_line) { - auto process_tag = [](std::string& gcode_line, const std::string& tag, double value) { + auto process_used_filament = [&](std::string& gcode_line) { + auto process_tag = [](std::string& gcode_line, const std::string& tag, const std::vector& values) { if (boost::algorithm::istarts_with(gcode_line, tag)) { - char buf[128]; - sprintf(buf, "%s %.2lf\n", tag.c_str(), value); - gcode_line = buf; + gcode_line = tag; + char buf[1024]; + for (size_t i = 0; i < values.size(); ++i) { + sprintf(buf, i == values.size() - 1 ? " %.2lf\n" : " %.2lf,", values[i]); + gcode_line += buf; + } return true; } return false; }; bool ret = false; - ret |= process_tag(gcode_line, "; filament used [mm] =", filament_data.mm * 1000.0); - ret |= process_tag(gcode_line, "; filament used [g] =", filament_data.g); - ret |= process_tag(gcode_line, "; total filament used [g] =", filament_data.g); - ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_data.cm3 / 1000.0); - ret |= process_tag(gcode_line, "; filament cost =", filament_data.cost); - ret |= process_tag(gcode_line, "; total filament cost =", filament_data.cost); + ret |= process_tag(gcode_line, "; filament used [mm] =", filament_mm); + ret |= process_tag(gcode_line, "; filament used [g] =", filament_g); + ret |= process_tag(gcode_line, "; total filament used [g] =", { filament_total_g }); + ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_cm3); + ret |= process_tag(gcode_line, "; filament cost =", filament_cost); + ret |= process_tag(gcode_line, "; total filament cost =", { filament_total_cost }); return ret; }; diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 0ab268139f..40a0992292 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -35,6 +35,8 @@ public: float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); } + Point new_XY_scaled(const GCodeReader &reader) const + { return Point::new_scale(this->new_X(reader), this->new_Y(reader)); } float dist_X(const GCodeReader &reader) const { return this->has(X) ? (this->x() - reader.x()) : 0; } float dist_Y(const GCodeReader &reader) const { return this->has(Y) ? (this->y() - reader.y()) : 0; } float dist_Z(const GCodeReader &reader) const { return this->has(Z) ? (this->z() - reader.z()) : 0; } @@ -134,6 +136,8 @@ public: float e() const { return m_position[E]; } float& f() { return m_position[F]; } float f() const { return m_position[F]; } + Point xy_scaled() const { return Point::new_scale(this->x(), this->y()); } + // Returns 0 for gcfNoExtrusion. char extrusion_axis() const { return m_extrusion_axis; } diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 4ff0898820..86768dfc56 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1281,17 +1281,6 @@ PageMode::PageMode(ConfigWizard *parent) radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); - append(radio_simple); - append(radio_advanced); - append(radio_expert); - - append_text("\n" + _L("The size of the object can be specified in inches")); - check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); - append(check_inch); -} - -void PageMode::on_activate() -{ std::string mode { "simple" }; wxGetApp().app_config->get("", "view_mode", mode); @@ -1299,7 +1288,16 @@ void PageMode::on_activate() else if (mode == "expert") { radio_expert->SetValue(true); } else { radio_simple->SetValue(true); } + append(radio_simple); + append(radio_advanced); + append(radio_expert); + + append_text("\n" + _L("The size of the object can be specified in inches")); + check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); + append(check_inch); + + on_activate(); } void PageMode::serialize_mode(AppConfig *app_config) const @@ -1310,11 +1308,6 @@ void PageMode::serialize_mode(AppConfig *app_config) const if (radio_advanced->GetValue()) { mode = "advanced"; } if (radio_expert->GetValue()) { mode = "expert"; } - // If "Mode" page wasn't selected (no one radiobutton is checked), - // we shouldn't to update a view_mode value in app_config - if (mode.empty()) - return; - app_config->set("view_mode", mode); app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index c822a2be81..4de8381ffe 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -426,8 +426,6 @@ struct PageMode: ConfigWizardPage PageMode(ConfigWizard *parent); void serialize_mode(AppConfig *app_config) const; - - virtual void on_activate(); }; struct PageVendors: ConfigWizardPage diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 993070b453..afcc3f9541 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3941,9 +3941,8 @@ void GCodeViewer::render_legend(float& legend_height) PartialTimes items; std::vector custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z; - int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector last_color(extruders_count); - for (int i = 0; i < extruders_count; ++i) { + std::vector last_color(m_extruders_count); + for (int i = 0; i < m_extruders_count; ++i) { last_color[i] = m_tool_colors[i]; } int last_extruder_id = 1; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1a30e08d10..9e73aaf8b3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3465,7 +3465,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // during the scene manipulation. #if ENABLE_NEW_RECTANGLE_SELECTION - if (m_picking_enabled && !any_gizmo_active && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { + if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled()) && !rectangle_selection_dragging) { #else if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty() || !is_layers_editing_enabled())) { #endif // ENABLE_NEW_RECTANGLE_SELECTION diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 570877716f..cde7a05a22 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -706,6 +706,10 @@ void MainFrame::init_tabpanel() // before the MainFrame is fully set up. tab->OnActivate(); m_last_selected_tab = m_tabpanel->GetSelection(); +#ifdef _MSW_DARK_MODE + if (wxGetApp().tabs_as_menu()) + tab->SetFocus(); +#endif } else select_tab(size_t(0)); // select Plater @@ -2018,8 +2022,13 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) m_plater->SetFocus(); Layout(); } - else + else { select(false); +#ifdef _MSW_DARK_MODE + if (wxGetApp().tabs_as_menu() && tab == 0) + m_plater->SetFocus(); +#endif + } // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0ef7ad39d9..6b8c6acab4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3872,6 +3872,9 @@ void Plater::priv::reload_from_disk() old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); sla::reproject_points_and_holes(old_model_object); + + // Fix warning icon in object list + wxGetApp().obj_list()->update_item_error_icon(obj_idx, vol_idx); } } #else diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index b53fe5c474..a09d158821 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -490,8 +490,11 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector& vector) auto* app_config = wxGetApp().app_config; auto hasget = [app_config](const std::string& name, std::vector& vector)->bool { if (app_config->has(name)) { - vector.push_back(std::stoi(app_config->get(name))); - return true; + std::string val = app_config->get(name); + if (!val.empty() || val[0]!='\0') { + vector.push_back(std::stoi(val)); + return true; + } } return false; }; diff --git a/t/fill.t b/t/fill.t deleted file mode 100644 index 88cc35801f..0000000000 --- a/t/fill.t +++ /dev/null @@ -1,318 +0,0 @@ -use Test::More; -use strict; -use warnings; - -#plan tests => 43; -# Test of a 100% coverage is off. -plan tests => 19; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first sum); -use Slic3r; -use Slic3r::Geometry qw(X Y scale unscale convex_hull); -use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex); -use Slic3r::Surface qw(:types); -use Slic3r::Test; - -sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } - -{ - my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); - my $filler = Slic3r::Filler->new_from_type('rectilinear'); - $filler->set_bounding_box($expolygon->bounding_box); - $filler->set_angle(0); - my $surface = Slic3r::Surface->new( - surface_type => S_TYPE_TOP, - expolygon => $expolygon, - ); - my $flow = Slic3r::Flow->new( - width => 0.69, - height => 0.4, - nozzle_diameter => 0.50, - ); - $filler->set_spacing($flow->spacing); - foreach my $angle (0, 45) { - $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); - my $paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); - is scalar @$paths, 1, 'one continuous path'; - } -} - -SKIP: -{ - skip "The FillRectilinear2 does not fill the surface completely", 1; - - my $test = sub { - my ($expolygon, $flow_spacing, $angle, $density) = @_; - - my $filler = Slic3r::Filler->new_from_type('rectilinear'); - $filler->set_bounding_box($expolygon->bounding_box); - $filler->set_angle($angle // 0); - # Adjust line spacing to fill the region. - $filler->set_dont_adjust(0); - $filler->set_link_max_length(scale(1.2*$flow_spacing)); - my $surface = Slic3r::Surface->new( - surface_type => S_TYPE_BOTTOM, - expolygon => $expolygon, - ); - my $flow = Slic3r::Flow->new( - width => $flow_spacing, - height => 0.4, - nozzle_diameter => $flow_spacing, - ); - $filler->set_spacing($flow->spacing); - my $paths = $filler->fill_surface( - $surface, - layer_height => $flow->height, - density => $density // 1, - ); - - # check whether any part was left uncovered - my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @$paths; - my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); - - # ignore very small dots - my $uncovered_filtered = [ grep $_->area > (scale $flow_spacing)**2, @$uncovered ]; - - is scalar(@$uncovered_filtered), 0, 'solid surface is fully filled'; - - if (0 && @$uncovered_filtered) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("uncovered.svg", - no_arrows => 1, - expolygons => [ $expolygon ], - blue_expolygons => [ @$uncovered ], - red_expolygons => [ @$uncovered_filtered ], - polylines => [ @$paths ], - ); - exit; - } - }; - - my $expolygon = Slic3r::ExPolygon->new([ - [6883102, 9598327.01296997], - [6883102, 20327272.01297], - [3116896, 20327272.01297], - [3116896, 9598327.01296997], - ]); - $test->($expolygon, 0.55); - - for (1..20) { - $expolygon->scale(1.05); - $test->($expolygon, 0.55); - } - - $expolygon = Slic3r::ExPolygon->new( - [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] - ); - $test->($expolygon, 0.524341649025257); - - $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); - $test->($expolygon, 0.5, 45, 0.99); # non-solid infill -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([4,0], [10,0], [15,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([15,0], [10,0], [4,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); - $config->set('fill_pattern', $pattern); - $config->set('top_fill_pattern', $pattern); - $config->set('bottom_fill_pattern', $pattern); - $config->set('perimeters', 1); - $config->set('skirts', 0); - $config->set('fill_density', 20); - $config->set('layer_height', 0.05); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); - ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; - my $tool = undef; - my @perimeter_points = my @infill_points = (); - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->perimeter_extruder-1) { - push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } elsif ($tool == $config->infill_extruder-1) { - push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - } - }); - my $convex_hull = convex_hull(\@perimeter_points); - ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.4,0.4,0.4,0.4]); - $config->set('infill_only_where_needed', 1); - $config->set('bottom_solid_layers', 0); - $config->set('infill_extruder', 2); - $config->set('infill_extrusion_width', 0.5); - $config->set('wipe_into_infill', 0); - $config->set('fill_density', 40); - $config->set('cooling', [ 0 ]); # for preventing speeds from being altered - $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('pyramid', config => $config); - - my $tool = undef; - my @infill_extrusions = (); # array of polylines - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - push @infill_extrusions, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points - - my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); - return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); - }; - - my $tolerance = 5; # mm^2 - - $config->set('solid_infill_below_area', 0); - ok $test->() < $tolerance, - 'no infill is generated when using infill_only_where_needed on a pyramid'; - - $config->set('solid_infill_below_area', 70); - ok abs($test->() - $config->solid_infill_below_area) < $tolerance, - 'infill is only generated under the forced solid shells'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('solid_infill_below_area', 20000000); - $config->set('solid_infill_every_layers', 2); - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('cooling', [ 0 ]); - $config->set('first_layer_speed', '100%'); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %layers_with_extrusion = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { - if (($args->{F} // $self->F) != $config->perimeter_speed*60) { - $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); - } - } - }); - - ok !%layers_with_extrusion, - "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('fill_density', 0); - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35,0.35,0.35,0.35]); - $config->set('infill_extruder', 2); - $config->set('solid_infill_extruder', 2); - $config->set('infill_extrusion_width', 0.52); - $config->set('solid_infill_extrusion_width', 0.52); - $config->set('first_layer_extrusion_width', 0); - - my $print = Slic3r::Test::init_print('A', config => $config); - my %infill = (); # Z => [ Line, Line ... ] - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - my $z = 1 * $self->Z; - $infill{$z} ||= []; - push @{$infill{$z}}, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - my $grow_d = scale($config->infill_extrusion_width)/2; - my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); - my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); - my $diff = diff($layer0_infill, $layer1_infill); - $diff = offset2_ex($diff, -$grow_d, +$grow_d); - $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; - is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; -} - -__END__ diff --git a/t/geometry.t b/t/geometry.t index bb72b22e17..874dab9877 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 30; +plan tests => 27; BEGIN { use FindBin; @@ -39,44 +39,6 @@ isnt Slic3r::Geometry::line_intersection($line1, $line2, 1), undef, 'line_inters #========================================================== -{ - my $polygon = Slic3r::Polygon->new( - [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], - [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], - [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], - [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], - [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], - [285273900, 461246400], [254081000, 515273900], - ); - - # this points belongs to $polyline - # note: it's actually a vertex, while we should better check an intermediate point - my $point = Slic3r::Point->new(104577600, 327748400); - - local $Slic3r::Geometry::epsilon = 1E-5; - is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp, - [ [107014700, 340000000], [104577600, 327748400] ], - 'polygon_segment_having_point'; -} - -#========================================================== - -{ - my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); - my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); - is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; -} - -#========================================================== - -{ - my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); - my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); - is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; -} - -#========================================================== - my $polygons = [ Slic3r::Polygon->new( # contour, ccw [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], diff --git a/t/perimeters.t b/t/perimeters.t index adc2a6cec7..c4aef6e7e7 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -14,7 +14,7 @@ use List::Util qw(first); use Slic3r; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(PI scale unscale); -use Slic3r::Geometry::Clipper qw(union_ex diff union offset); +use Slic3r::Geometry::Clipper qw(union_ex diff); use Slic3r::Surface ':types'; use Slic3r::Test; diff --git a/t/polyclip.t b/t/polyclip.t deleted file mode 100644 index 0808c7be99..0000000000 --- a/t/polyclip.t +++ /dev/null @@ -1,121 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 18; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Geometry::Clipper qw(intersection_pl); - -#========================================================== - -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([5, 10], [20, 10])), 1, 'point in horizontal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(30, 10), Slic3r::Line->new([5, 10], [20, 10])), 0, 'point not in horizontal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([10, 5], [10, 20])), 1, 'point in vertical segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 30), Slic3r::Line->new([10, 5], [10, 20])), 0, 'point not in vertical segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(15, 15), Slic3r::Line->new([10, 10], [20, 20])), 1, 'point in diagonal segment'; -is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(20, 15), Slic3r::Line->new([10, 10], [20, 20])), 0, 'point not in diagonal segment'; - -#========================================================== - -my $square = Slic3r::Polygon->new( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -); - -#========================================================== - -{ - my $hole_in_square = [ # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ]; - my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - #is $expolygon->contains_point(Slic3r::Point->new(100, 100)), 1, 'corner point is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(100, 180)), 1, 'point on contour is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(140, 150)), 1, 'point on hole contour is recognized'; - #is $expolygon->contains_point(Slic3r::Point->new(140, 140)), 1, 'point on hole corner is recognized'; - { - my $intersection = intersection_pl([Slic3r::Polyline->new([150,180], [150,150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([150, 180], [150, 160])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([150,150], [150,120])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([150, 140], [150, 120])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([120,180], [180,180])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([120,180], [180,180])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([50, 150], [300, 150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([100, 150], [140, 150])->length, - 'line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([160, 150], [200, 150])->length, - 'line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([300, 150], [50, 150])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([200, 150], [160, 150])->length, - 'reverse line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([140, 150], [100, 150])->length, - 'reverse line is clipped to square with hole'; - } - { - my $intersection = intersection_pl([Slic3r::Polyline->new([100,180], [200,180])], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([100,180], [200,180])->length, - 'tangent line is clipped to square with hole'; - } -} - -#========================================================== - -{ - my $large_circle = Slic3r::Polygon->new_scale( # ccw - [151.8639,288.1192], [133.2778,284.6011], [115.0091,279.6997], [98.2859,270.8606], [82.2734,260.7933], - [68.8974,247.4181], [56.5622,233.0777], [47.7228,216.3558], [40.1617,199.0172], [36.6431,180.4328], - [34.932,165.2312], [37.5567,165.1101], [41.0547,142.9903], [36.9056,141.4295], [40.199,124.1277], - [47.7776,106.7972], [56.6335,90.084], [68.9831,75.7557], [82.3712,62.3948], [98.395,52.3429], - [115.1281,43.5199], [133.4004,38.6374], [151.9884,35.1378], [170.8905,35.8571], [189.6847,37.991], - [207.5349,44.2488], [224.8662,51.8273], [240.0786,63.067], [254.407,75.4169], [265.6311,90.6406], - [275.6832,106.6636], [281.9225,124.52], [286.8064,142.795], [287.5061,161.696], [286.7874,180.5972], - [281.8856,198.8664], [275.6283,216.7169], [265.5604,232.7294], [254.3211,247.942], [239.9802,260.2776], - [224.757,271.5022], [207.4179,279.0635], [189.5605,285.3035], [170.7649,287.4188], - ); - ok $large_circle->is_counter_clockwise, "contour is counter-clockwise"; - - my $small_circle = Slic3r::Polygon->new_scale( # cw - [158.227,215.9007], [164.5136,215.9007], [175.15,214.5007], [184.5576,210.6044], [190.2268,207.8743], - [199.1462,201.0306], [209.0146,188.346], [213.5135,177.4829], [214.6979,168.4866], [216.1025,162.3325], - [214.6463,151.2703], [213.2471,145.1399], [209.0146,134.9203], [199.1462,122.2357], [189.8944,115.1366], - [181.2504,111.5567], [175.5684,108.8205], [164.5136,107.3655], [158.2269,107.3655], [147.5907,108.7656], - [138.183,112.6616], [132.5135,115.3919], [123.5943,122.2357], [113.7259,134.92], [109.2269,145.7834], - [108.0426,154.7799], [106.638,160.9339], [108.0941,171.9957], [109.4933,178.1264], [113.7259,188.3463], - [123.5943,201.0306], [132.8461,208.1296], [141.4901,211.7094], [147.172,214.4458], - ); - ok $small_circle->is_clockwise, "hole is clockwise"; - - my $expolygon = Slic3r::ExPolygon->new($large_circle, $small_circle); - my $line = Slic3r::Polyline->new_scale([152.742,288.086671142818], [152.742,34.166466971035]); - - my $intersection = intersection_pl([$line], \@$expolygon); - is $intersection->[0]->length, Slic3r::Line->new([152742000, 288086661], [152742000, 215178843])->length, - 'line is clipped to square with hole'; - is $intersection->[1]->length, Slic3r::Line->new([152742000, 108087507], [152742000, 35166477])->length, - 'line is clipped to square with hole'; -} - -#========================================================== diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index f7077007db..6be45d2380 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -187,6 +187,19 @@ TriangleMesh mesh(TestMesh m) return mesh; } +TriangleMesh mesh(TestMesh min, Vec3d translate, Vec3d scale) +{ + TriangleMesh m = mesh(min); + m.translate(translate.cast()); + m.scale(scale.cast()); + return m; +} + +TriangleMesh mesh(TestMesh m, Vec3d translate, double scale) +{ + return mesh(m, translate, Vec3d(scale, scale, scale)); +} + static bool verbose_gcode() { const char *v = std::getenv("SLIC3R_TESTS_GCODE"); diff --git a/tests/fff_print/test_extrusion_entity.cpp b/tests/fff_print/test_extrusion_entity.cpp index 9bef52a5c3..7e0ca822fc 100644 --- a/tests/fff_print/test_extrusion_entity.cpp +++ b/tests/fff_print/test_extrusion_entity.cpp @@ -5,6 +5,7 @@ #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/Point.hpp" +#include "libslic3r/ShortestPath.hpp" #include "libslic3r/libslic3r.h" #include "test_data.hpp" @@ -83,3 +84,60 @@ SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") { } } } + +TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { + struct Test { + Polylines unchained; + Polylines chained; + Point initial_point; + }; + std::vector tests { + { + { + { {0,15}, {0,18}, {0,20} }, + { {0,10}, {0,8}, {0,5} } + }, + { + { {0,20}, {0,18}, {0,15} }, + { {0,10}, {0,8}, {0,5} } + }, + { 0,30 } + }, + { + { + { {4,0}, {10,0}, {15,0} }, + { {10,5}, {15,5}, {20,5} } + }, + { + { {20,5}, {15,5}, {10,5} }, + { {15,0}, {10,0}, {4,0} } + }, + { 30,0 } + }, + { + { + { {15,0}, {10,0}, {4,0} }, + { {10,5}, {15,5}, {20,5} } + }, + { + { {20,5}, {15,5}, {10,5} }, + { {15,0}, {10,0}, {4,0} } + }, + { 30,0 } + }, + }; + for (const Test &test : tests) { + Polylines chained = chain_polylines(test.unchained, &test.initial_point); + REQUIRE(chained == test.chained); + ExtrusionEntityCollection unchained_extrusions; + extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained, + erInternalInfill, 0., 0.4f, 0.3f); + ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); + REQUIRE(chained_extrusions.entities.size() == test.chained.size()); + for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + const Points &p1 = test.chained[i].points; + const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + REQUIRE(p1 == p2); + } + } +} \ No newline at end of file diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 8e311282e0..9f30bf8be7 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -7,6 +7,7 @@ #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Flow.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SVG.hpp" #include "libslic3r/libslic3r.h" @@ -14,6 +15,7 @@ #include "test_data.hpp" using namespace Slic3r; +using namespace std::literals; bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0); @@ -43,7 +45,7 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { SECTION("Square") { Slic3r::Points test_set; test_set.reserve(4); - std::vector points {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)}; + std::vector points { {0,0}, {100,0}, {100,100}, {0,100} }; for (size_t i = 0; i < 4; ++i) { std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } ); @@ -118,24 +120,26 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { REQUIRE(std::abs(paths[0].length() - static_cast(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length } - SECTION("Rotated Square") { - Slic3r::Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)}; - Slic3r::ExPolygon expolygon(square); + SECTION("Rotated Square produces one continuous path") { + Slic3r::ExPolygon expolygon(Polygon::new_scale({ {0, 0}, {50, 0}, {50, 50}, {0, 50} })); std::unique_ptr filler(Slic3r::Fill::new_from_type("rectilinear")); - filler->bounding_box = get_extents(expolygon.contour); + filler->bounding_box = get_extents(expolygon); filler->angle = 0; Surface surface(stTop, expolygon); - auto flow = Slic3r::Flow(0.69f, 0.4f, 0.50f); + // width, height, nozzle_dmr + auto flow = Slic3r::Flow(0.69f, 0.4f, 0.5f); FillParams fill_params; - fill_params.density = 1.0; - filler->spacing = flow.spacing(); - - for (auto angle : { 0.0, 45.0}) { - surface.expolygon.rotate(angle, Point(0,0)); - Polylines paths = filler->fill_surface(&surface, fill_params); - REQUIRE(paths.size() == 1); + for (auto density : { 0.4, 1.0 }) { + fill_params.density = density; + filler->spacing = flow.spacing(); + for (auto angle : { 0.0, 45.0}) { + surface.expolygon.rotate(angle, Point(0,0)); + Polylines paths = filler->fill_surface(&surface, fill_params); + // one continuous path + REQUIRE(paths.size() == 1); + } } } @@ -190,202 +194,226 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { } } +SCENARIO("Infill does not exceed perimeters", "[Fill]") +{ + auto test = [](const std::string_view pattern) { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, + { "fill_pattern", pattern }, + { "top_fill_pattern", pattern }, + { "bottom_fill_pattern", pattern }, + { "perimeters", 1 }, + { "skirts", 0 }, + { "fill_density", 0.2 }, + { "layer_height", 0.05 }, + { "perimeter_extruder", 1 }, + { "infill_extruder", 2 } + }); + + WHEN("40mm cube sliced") { + std::string gcode = Slic3r::Test::slice({ mesh(Slic3r::Test::TestMesh::cube_20x20x20, Vec3d::Zero(), 2.0) }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + THEN("infill does not exceed perimeters") { + GCodeReader parser; + const int perimeter_extruder = config.opt_int("perimeter_extruder"); + const int infill_extruder = config.opt_int("infill_extruder"); + int tool = -1; + Points perimeter_points; + Points infill_points; + parser.parse_buffer(gcode, [&tool, &perimeter_points, &infill_points, perimeter_extruder, infill_extruder] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == perimeter_extruder) + perimeter_points.emplace_back(line.new_XY_scaled(self)); + else if (tool == infill_extruder) + infill_points.emplace_back(line.new_XY_scaled(self)); + } + }); + auto convex_hull = Geometry::convex_hull(perimeter_points); + int num_inside = std::count_if(infill_points.begin(), infill_points.end(), [&convex_hull](const Point &pt){ return convex_hull.contains(pt); }); + REQUIRE(num_inside == infill_points.size()); + } + } + }; + + GIVEN("Rectilinear") { test("rectilinear"sv); } + GIVEN("Honeycomb") { test("honeycomb"sv); } + GIVEN("HilbertCurve") { test("hilbertcurve"sv); } + GIVEN("Concentric") { test("concentric"sv); } +} + +SCENARIO("Infill only where needed", "[Fill]") +{ + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, + { "infill_only_where_needed", true }, + { "bottom_solid_layers", 0 }, + { "infill_extruder", 2 }, + { "infill_extrusion_width", 0.5 }, + { "wipe_into_infill", false }, + { "fill_density", 0.4 }, + // for preventing speeds from being altered + { "cooling", "0, 0, 0, 0" }, + // for preventing speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test = [&config]() -> double { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::pyramid }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + GCodeReader parser; + int tool = -1; + const int infill_extruder = config.opt_int("infill_extruder"); + Points infill_points; + parser.parse_buffer(gcode, [&tool, &infill_points, infill_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == infill_extruder) { + infill_points.emplace_back(self.xy_scaled()); + infill_points.emplace_back(line.new_XY_scaled(self)); + } + } + }); + // prevent calling convex_hull() with no points + THEN("infill not empty") { + REQUIRE(! infill_points.empty()); + } + + auto opt_width = config.opt("infill_extrusion_width"); + REQUIRE(! opt_width->percent); + Polygons convex_hull = expand(Geometry::convex_hull(infill_points), scaled(opt_width->value / 2)); + return SCALING_FACTOR * SCALING_FACTOR * std::accumulate(convex_hull.begin(), convex_hull.end(), 0., [](double acc, const Polygon &poly){ return acc + poly.area(); }); + }; + + double tolerance = 5; // mm^2 + + GIVEN("solid_infill_below_area == 0") { + config.opt_float("solid_infill_below_area") = 0; + WHEN("pyramid is sliced ") { + auto area = test(); + THEN("no infill is generated when using infill_only_where_needed on a pyramid") { + REQUIRE(area < tolerance); + } + } + } + GIVEN("solid_infill_below_area == 70") { + config.opt_float("solid_infill_below_area") = 70; + WHEN("pyramid is sliced ") { + auto area = test(); + THEN("infill is only generated under the forced solid shells") { + REQUIRE(std::abs(area - 70) < tolerance); + } + } + } +} + +SCENARIO("Infill density zero", "[Fill]") +{ + WHEN("20mm cube is sliced") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "skirts", 0 }, + { "perimeters", 1 }, + { "fill_density", 0 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 }, + { "solid_infill_below_area", 20000000 }, + { "solid_infill_every_layers", 2 }, + { "perimeter_speed", 99 }, + { "external_perimeter_speed", 99 }, + { "cooling", "0" }, + { "first_layer_speed", "100%" } + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + THEN("solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0") { + GCodeReader parser; + const double perimeter_speed = config.opt_float("perimeter_speed"); + std::map layers_with_extrusion; + parser.parse_buffer(gcode, [&layers_with_extrusion, perimeter_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + if (std::abs(f - perimeter_speed * 60.) > 0.01) + // It is a perimeter. + layers_with_extrusion[self.z()] = f; + } + }); + REQUIRE(layers_with_extrusion.empty()); + } + } + + WHEN("A is sliced") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "skirts", 0 }, + { "perimeters", 3 }, + { "fill_density", 0 }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "nozzle_diameter", "0.35,0.35,0.35,0.35" }, + { "infill_extruder", 2 }, + { "solid_infill_extruder", 2 }, + { "infill_extrusion_width", 0.52 }, + { "solid_infill_extrusion_width", 0.52 }, + { "first_layer_extrusion_width", 0 } + }); + + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::A }, config); + THEN("gcode not empty") { + REQUIRE(! gcode.empty()); + } + + THEN("no missing parts in solid shell when fill_density is 0") { + GCodeReader parser; + int tool = -1; + const int infill_extruder = config.opt_int("infill_extruder"); + std::map infill; + parser.parse_buffer(gcode, [&tool, &infill, infill_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1) + 1; + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0) { + if (tool == infill_extruder) + infill[scaled(self.z())].emplace_back(self.xy_scaled(), line.new_XY_scaled(self)); + } + }); + auto opt_width = config.opt("infill_extrusion_width"); + REQUIRE(! opt_width->percent); + auto grow_d = scaled(opt_width->value / 2); + auto inflate_lines = [grow_d](const Lines &lines) { + Polygons out; + for (const Line &line : lines) + append(out, offset(Polyline{ line.a, line.b }, grow_d, Slic3r::ClipperLib::jtSquare, 3.)); + return union_(out); + }; + Polygons layer0_infill = inflate_lines(infill[scaled(0.2)]); + Polygons layer1_infill = inflate_lines(infill[scaled(0.4)]); + ExPolygons poly = opening_ex(diff_ex(layer0_infill, layer1_infill), grow_d); + const double threshold = 2. * sqr(grow_d * 2.); + int missing_parts = std::count_if(poly.begin(), poly.end(), [threshold](const ExPolygon &poly){ return poly.area() > threshold; }); + REQUIRE(missing_parts == 0); + } + } +} + /* -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::Polyline::Collection->new( - Slic3r::Polyline->new([4,0], [10,0], [15,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([15,0], [10,0], [4,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained path'; -} - -for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { - my $config = Slic3r::Config->new_from_defaults; - $config->set('fill_pattern', $pattern); - $config->set('external_fill_pattern', $pattern); - $config->set('perimeters', 1); - $config->set('skirts', 0); - $config->set('fill_density', 20); - $config->set('layer_height', 0.05); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); - ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; - my $tool = undef; - my @perimeter_points = my @infill_points = (); - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->perimeter_extruder-1) { - push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } elsif ($tool == $config->infill_extruder-1) { - push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - } - }); - my $convex_hull = convex_hull(\@perimeter_points); - ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('infill_only_where_needed', 1); - $config->set('bottom_solid_layers', 0); - $config->set('infill_extruder', 2); - $config->set('infill_extrusion_width', 0.5); - $config->set('fill_density', 40); - $config->set('cooling', 0); # for preventing speeds from being altered - $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('pyramid', config => $config); - - my $tool = undef; - my @infill_extrusions = (); # array of polylines - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - push @infill_extrusions, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points - - my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); - return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); - }; - - my $tolerance = 5; # mm^2 - - $config->set('solid_infill_below_area', 0); - ok $test->() < $tolerance, - 'no infill is generated when using infill_only_where_needed on a pyramid'; - - $config->set('solid_infill_below_area', 70); - ok abs($test->() - $config->solid_infill_below_area) < $tolerance, - 'infill is only generated under the forced solid shells'; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 1); - $config->set('fill_density', 0); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('solid_infill_below_area', 20000000); - $config->set('solid_infill_every_layers', 2); - $config->set('perimeter_speed', 99); - $config->set('external_perimeter_speed', 99); - $config->set('cooling', 0); - $config->set('first_layer_speed', '100%'); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %layers_with_extrusion = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { - if (($args->{F} // $self->F) != $config->perimeter_speed*60) { - $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); - } - } - }); - - ok !%layers_with_extrusion, - "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; -} - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 3); - $config->set('fill_density', 0); - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.35]); - $config->set('infill_extruder', 2); - $config->set('solid_infill_extruder', 2); - $config->set('infill_extrusion_width', 0.52); - $config->set('solid_infill_extrusion_width', 0.52); - $config->set('first_layer_extrusion_width', 0); - - my $print = Slic3r::Test::init_print('A', config => $config); - my %infill = (); # Z => [ Line, Line ... ] - my $tool = undef; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - if ($tool == $config->infill_extruder-1) { - my $z = 1 * $self->Z; - $infill{$z} ||= []; - push @{$infill{$z}}, Slic3r::Line->new_scale( - [ $self->X, $self->Y ], - [ $info->{new_X}, $info->{new_Y} ], - ); - } - } - }); - my $grow_d = scale($config->infill_extrusion_width)/2; - my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); - my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); - my $diff = diff($layer0_infill, $layer1_infill); - $diff = offset2_ex($diff, -$grow_d, +$grow_d); - $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; - is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; -} - { # GH: #2697 my $config = Slic3r::Config->new_from_defaults; @@ -427,6 +455,78 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my @holes = map @{$_->holes}, @$covered; ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters'; } + +{ + skip "The FillRectilinear2 does not fill the surface completely", 1; + + my $test = sub { + my ($expolygon, $flow_spacing, $angle, $density) = @_; + + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_bounding_box($expolygon->bounding_box); + $filler->set_angle($angle // 0); + # Adjust line spacing to fill the region. + $filler->set_dont_adjust(0); + $filler->set_link_max_length(scale(1.2*$flow_spacing)); + my $surface = Slic3r::Surface->new( + surface_type => S_TYPE_BOTTOM, + expolygon => $expolygon, + ); + my $flow = Slic3r::Flow->new( + width => $flow_spacing, + height => 0.4, + nozzle_diameter => $flow_spacing, + ); + $filler->set_spacing($flow->spacing); + my $paths = $filler->fill_surface( + $surface, + layer_height => $flow->height, + density => $density // 1, + ); + + # check whether any part was left uncovered + my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @$paths; + my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); + + # ignore very small dots + my $uncovered_filtered = [ grep $_->area > (scale $flow_spacing)**2, @$uncovered ]; + + is scalar(@$uncovered_filtered), 0, 'solid surface is fully filled'; + + if (0 && @$uncovered_filtered) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("uncovered.svg", + no_arrows => 1, + expolygons => [ $expolygon ], + blue_expolygons => [ @$uncovered ], + red_expolygons => [ @$uncovered_filtered ], + polylines => [ @$paths ], + ); + exit; + } + }; + + my $expolygon = Slic3r::ExPolygon->new([ + [6883102, 9598327.01296997], + [6883102, 20327272.01297], + [3116896, 20327272.01297], + [3116896, 9598327.01296997], + ]); + $test->($expolygon, 0.55); + + for (1..20) { + $expolygon->scale(1.05); + $test->($expolygon, 0.55); + } + + $expolygon = Slic3r::ExPolygon->new( + [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] + ); + $test->($expolygon, 0.524341649025257); + + $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); + $test->($expolygon, 0.5, 45, 0.99); # non-solid infill +} */ bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle, double density) diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 1b2b45eca7..b357d8ca83 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -188,6 +188,46 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { REQUIRE(intersection.front().area() == Approx(match.area())); } } + + ExPolygons expolygons { ExPolygon { square, hole_in_square } }; + WHEN("Clipping line 1") { + Polylines intersection = intersection_pl({ Polyline { { 15, 18 }, { 15, 15 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(15, 18) - Vec2f(15, 16)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 2") { + Polylines intersection = intersection_pl({ Polyline { { 15, 15 }, { 15, 12 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(15, 14) - Vec2f(15, 12)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 3") { + Polylines intersection = intersection_pl({ Polyline { { 12, 18 }, { 18, 18 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(18, 18) - Vec2f(12, 18)).norm() == Approx(intersection.front().length())); + } + } + WHEN("Clipping line 4") { + Polylines intersection = intersection_pl({ Polyline { { 5, 15 }, { 30, 15 } } }, expolygons); + THEN("line is clipped to square with hole") { + REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection.front().length())); + REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection[1].length())); + } + } + WHEN("Clipping line 5") { + Polylines intersection = intersection_pl({ Polyline { { 30, 15 }, { 5, 15 } } }, expolygons); + THEN("reverse line is clipped to square with hole") { + REQUIRE((Vec2f(20, 15) - Vec2f(16, 15)).norm() == Approx(intersection.front().length())); + REQUIRE((Vec2f(14, 15) - Vec2f(10, 15)).norm() == Approx(intersection[1].length())); + } + } + WHEN("Clipping line 6") { + Polylines intersection = intersection_pl({ Polyline { { 10, 18 }, { 20, 18 } } }, expolygons); + THEN("tangent line is clipped to square with hole") { + REQUIRE((Vec2f(20, 18) - Vec2f(10, 18)).norm() == Approx(intersection.front().length())); + } + } } GIVEN("square with hole 2") { // CCW oriented contour @@ -223,6 +263,44 @@ SCENARIO("Various Clipper operations - t/clipper.t", "[ClipperUtils]") { } } } + GIVEN("circle") { + Slic3r::ExPolygon circle_with_hole { Polygon::new_scale({ + { 151.8639,288.1192 }, {133.2778,284.6011}, { 115.0091,279.6997 }, { 98.2859,270.8606 }, { 82.2734,260.7933 }, + { 68.8974,247.4181 }, { 56.5622,233.0777 }, { 47.7228,216.3558 }, { 40.1617,199.0172 }, { 36.6431,180.4328 }, + { 34.932,165.2312 }, { 37.5567,165.1101 }, { 41.0547,142.9903 }, { 36.9056,141.4295 }, { 40.199,124.1277 }, + { 47.7776,106.7972 }, { 56.6335,90.084 }, { 68.9831,75.7557 }, { 82.3712,62.3948 }, { 98.395,52.3429 }, + { 115.1281,43.5199 }, { 133.4004,38.6374 }, { 151.9884,35.1378 }, { 170.8905,35.8571 }, { 189.6847,37.991 }, + { 207.5349,44.2488 }, { 224.8662,51.8273 }, { 240.0786,63.067 }, { 254.407,75.4169 }, { 265.6311,90.6406 }, + { 275.6832,106.6636 }, { 281.9225,124.52 }, { 286.8064,142.795 }, { 287.5061,161.696 }, { 286.7874,180.5972 }, + { 281.8856,198.8664 }, { 275.6283,216.7169 }, { 265.5604,232.7294 }, { 254.3211,247.942 }, { 239.9802,260.2776 }, + { 224.757,271.5022 }, { 207.4179,279.0635 }, { 189.5605,285.3035 }, { 170.7649,287.4188 } + }) }; + circle_with_hole.holes = { Polygon::new_scale({ + { 158.227,215.9007 }, { 164.5136,215.9007 }, { 175.15,214.5007 }, { 184.5576,210.6044 }, { 190.2268,207.8743 }, + { 199.1462,201.0306 }, { 209.0146,188.346 }, { 213.5135,177.4829 }, { 214.6979,168.4866 }, { 216.1025,162.3325 }, + { 214.6463,151.2703 }, { 213.2471,145.1399 }, { 209.0146,134.9203 }, { 199.1462,122.2357 }, { 189.8944,115.1366 }, + { 181.2504,111.5567 }, { 175.5684,108.8205 }, { 164.5136,107.3655 }, { 158.2269,107.3655 }, { 147.5907,108.7656 }, + { 138.183,112.6616 }, { 132.5135,115.3919 }, { 123.5943,122.2357 }, { 113.7259,134.92 }, { 109.2269,145.7834 }, + { 108.0426,154.7799 }, { 106.638,160.9339 }, { 108.0941,171.9957 }, { 109.4933,178.1264 }, { 113.7259,188.3463 }, + { 123.5943,201.0306 }, { 132.8461,208.1296 }, { 141.4901,211.7094 }, { 147.172,214.4458 } + }) }; + THEN("contour is counter-clockwise") { + REQUIRE(circle_with_hole.contour.is_counter_clockwise()); + } + THEN("hole is counter-clockwise") { + REQUIRE(circle_with_hole.holes.size() == 1); + REQUIRE(circle_with_hole.holes.front().is_clockwise()); + } + + WHEN("clipping a line") { + auto line = Polyline::new_scale({ { 152.742,288.086671142818 }, { 152.742,34.166466971035 } }); + Polylines intersection = intersection_pl({ line }, { circle_with_hole }); + THEN("clipped to two pieces") { + REQUIRE(intersection.front().length() == Approx((Vec2d(152742000, 215178843) - Vec2d(152742000, 288086661)).norm())); + REQUIRE(intersection[1].length() == Approx((Vec2d(152742000, 35166477) - Vec2d(152742000, 108087507)).norm())); + } + } + } } template diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 06fc98322f..022ba2c013 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -53,11 +53,8 @@ set(XS_XSP_FILES ${XSP_DIR}/ExtrusionLoop.xsp ${XSP_DIR}/ExtrusionMultiPath.xsp ${XSP_DIR}/ExtrusionPath.xsp - ${XSP_DIR}/ExtrusionSimulator.xsp - ${XSP_DIR}/Filler.xsp ${XSP_DIR}/Flow.xsp ${XSP_DIR}/GCode.xsp - # ${XSP_DIR}/GCodeSender.xsp ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 6d3bf35cf2..83bc0ee70e 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -133,23 +133,6 @@ sub clone { ); } -package Slic3r::ExtrusionSimulator; - -sub new { - my ($class, %args) = @_; - return $class->_new(); -} - -package Slic3r::Filler; - -sub fill_surface { - my ($self, $surface, %args) = @_; - $self->set_density($args{density}) if defined($args{density}); - $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); - $self->set_complete($args{complete}) if defined($args{complete}); - return $self->_fill_surface($surface); -} - package Slic3r::Flow; sub new { @@ -255,19 +238,12 @@ for my $class (qw( Slic3r::ExtrusionMultiPath Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection - Slic3r::ExtrusionSimulator - Slic3r::Filler Slic3r::Flow Slic3r::GCode Slic3r::GCode::PlaceholderParser Slic3r::Geometry::BoundingBox Slic3r::Geometry::BoundingBoxf Slic3r::Geometry::BoundingBoxf3 - Slic3r::GUI::_3DScene::GLShader - Slic3r::GUI::_3DScene::GLVolume - Slic3r::GUI::Preset - Slic3r::GUI::PresetCollection - Slic3r::GUI::Tab Slic3r::Layer Slic3r::Layer::Region Slic3r::Layer::Support diff --git a/xs/main.xs.in b/xs/main.xs.in index c10f432d83..d8db108be5 100644 --- a/xs/main.xs.in +++ b/xs/main.xs.in @@ -2,7 +2,6 @@ #include #include #include -// #include #ifdef __cplusplus /* extern "C" { */ diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 20288243e9..2d996120dd 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -9,8 +9,6 @@ REGISTER_CLASS(ExtrusionMultiPath, "ExtrusionMultiPath"); REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); -REGISTER_CLASS(ExtrusionSimulator, "ExtrusionSimulator"); -REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index eae3afeffb..5f9f359067 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -9,16 +9,6 @@ %{ -IV -_constant() - ALIAS: - JT_MITER = jtMiter - JT_ROUND = jtRound - JT_SQUARE = jtSquare - CODE: - RETVAL = ix; - OUTPUT: RETVAL - Polygons offset(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons @@ -30,17 +20,6 @@ offset(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) OUTPUT: RETVAL -ExPolygons -offset_ex(polygons, delta, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - RETVAL = offset_ex(polygons, delta, joinType, miterLimit); - OUTPUT: - RETVAL - ExPolygons offset2_ex(polygons, delta1, delta2, joinType = Slic3r::ClipperLib::jtMiter, miterLimit = 3) Polygons polygons @@ -73,44 +52,6 @@ diff_ex(subject, clip, safety_offset = false) OUTPUT: RETVAL -Polylines -diff_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - RETVAL = diff_pl(subject, clip); - OUTPUT: - RETVAL - -Polygons -intersection(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = intersection(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - -ExPolygons -intersection_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - RETVAL = intersection_ex(subject, clip, safety_offset ? ApplySafetyOffset::Yes : ApplySafetyOffset::No); - OUTPUT: - RETVAL - -Polylines -intersection_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - RETVAL = intersection_pl(subject, clip); - OUTPUT: - RETVAL - Polygons union(subject, safety_offset = false) Polygons subject diff --git a/xs/xsp/ExtrusionSimulator.xsp b/xs/xsp/ExtrusionSimulator.xsp deleted file mode 100644 index 9395913b41..0000000000 --- a/xs/xsp/ExtrusionSimulator.xsp +++ /dev/null @@ -1,50 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionSimulator.hpp" -%} - -%name{Slic3r::ExtrusionSimulator} class ExtrusionSimulator { - ~ExtrusionSimulator(); - %name{_new} ExtrusionSimulator(); - - Clone clone() - %code{% RETVAL = THIS; %}; - - void set_image_size(Point *image_size) - %code{% THIS->set_image_size(*image_size); %}; - void set_viewport(BoundingBox *viewport) - %code{% THIS->set_viewport(*viewport); %}; - void set_bounding_box(BoundingBox *bbox) - %code{% THIS->set_bounding_box(*bbox); %}; - - void reset_accumulator(); - void extrude_to_accumulator(ExtrusionPath *path, Point *shift, ExtrusionSimulationType simulationType) - %code{% THIS->extrude_to_accumulator(*path, *shift, simulationType); %}; - void evaluate_accumulator(ExtrusionSimulationType simulationType); - void* image_ptr() - %code{% RETVAL = const_cast(const_cast(THIS)->image_ptr()); %}; - -%{ - -%} -}; - -%package{Slic3r::ExtrusionSimulator}; -%{ - -IV -_constant() - ALIAS: - EXTRSIM_SIMPLE = ExtrusionSimulationSimple - EXTRSIM_DONT_SPREAD = ExtrusionSimulationDontSpread - EXTRSIM_SPREAD_NFULL = ExtrisopmSimulationSpreadNotOverfilled - EXTRSIM_SPREAD_FULL = ExtrusionSimulationSpreadFull - EXTRSIM_SPREAD_EXCESS = ExtrusionSimulationSpreadExcess - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp deleted file mode 100644 index 2ea2b34d5a..0000000000 --- a/xs/xsp/Filler.xsp +++ /dev/null @@ -1,65 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Fill/Fill.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/ExtrusionEntityCollection.hpp" -%} - -%name{Slic3r::Filler} class Filler { - ~Filler(); - - void set_bounding_box(BoundingBox *bbox) - %code{% THIS->fill->set_bounding_box(*bbox); %}; - void set_spacing(coordf_t spacing) - %code{% THIS->fill->spacing = spacing; %}; - coordf_t spacing() - %code{% RETVAL = THIS->fill->spacing; %}; - void set_layer_id(size_t layer_id) - %code{% THIS->fill->layer_id = layer_id; %}; - void set_z(coordf_t z) - %code{% THIS->fill->z = z; %}; - void set_angle(float angle) - %code{% THIS->fill->angle = angle; %}; - void set_link_max_length(coordf_t len) - %code{% THIS->fill->link_max_length = len; %}; - void set_loop_clipping(coordf_t clipping) - %code{% THIS->fill->loop_clipping = clipping; %}; - - bool use_bridge_flow() - %code{% RETVAL = THIS->fill->use_bridge_flow(); %}; - bool no_sort() - %code{% RETVAL = THIS->fill->no_sort(); %}; - - void set_density(float density) - %code{% THIS->params.density = density; %}; - void set_dont_adjust(bool dont_adjust) - %code{% THIS->params.dont_adjust = dont_adjust; %}; - - PolylineCollection* _fill_surface(Surface *surface) - %code{% - PolylineCollection *pc = NULL; - if (THIS->fill != NULL) { - pc = new PolylineCollection(); - pc->polylines = THIS->fill->fill_surface(surface, THIS->params); - } - RETVAL = pc; - %}; - -%{ - -Filler* -new_from_type(CLASS, type) - char* CLASS; - std::string type; - CODE: - Filler *filler = new Filler(); - filler->fill = Fill::new_from_type(type); - RETVAL = filler; - OUTPUT: - RETVAL - -%} - -}; diff --git a/xs/xsp/GCodeSender.xsp b/xs/xsp/GCodeSender.xsp deleted file mode 100644 index f99244a1ff..0000000000 --- a/xs/xsp/GCodeSender.xsp +++ /dev/null @@ -1,24 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/GCodeSender.hpp" -%} - -%name{Slic3r::GCode::Sender} class GCodeSender { - GCodeSender(); - ~GCodeSender(); - - bool connect(std::string port, unsigned int baud_rate); - void disconnect(); - bool is_connected(); - bool wait_connected(unsigned int timeout = 3); - int queue_size(); - void send(std::string s, bool priority = false); - void pause_queue(); - void resume_queue(); - void purge_queue(bool priority = false); - std::vector purge_log(); - std::string getT(); - std::string getB(); -}; diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 6b6d525240..984eb16a91 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -41,12 +41,6 @@ Clone bounding_box(); Clone point_projection(Point* point) %code{% RETVAL = THIS->point_projection(*point); %}; - Clone intersection(Line* line) - %code{% - Point p; - (void)THIS->intersection(*line, &p); - RETVAL = p; - %}; Clone first_intersection(Line* line) %code{% Point p; diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index 49d988333c..8804b851bb 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -82,16 +82,6 @@ Surface::polygons() OUTPUT: RETVAL -Surfaces -Surface::offset(delta, joinType = ClipperLib::jtMiter, miterLimit = 3) - const float delta - Slic3r::ClipperLib::JoinType joinType - double miterLimit - CODE: - surfaces_append(RETVAL, offset_ex(THIS->expolygon, delta, joinType, miterLimit), *THIS); - OUTPUT: - RETVAL - %} }; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index ca26750dc5..a660041bc8 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -118,14 +118,6 @@ ExtrusionLoop* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -ExtrusionSimulator* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -Filler* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - Flow* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index f9e61c6a03..cc71094459 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -58,9 +58,6 @@ %typemap{ExPolygonCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{Filler*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{Flow*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -88,9 +85,6 @@ %typemap{ExtrusionLoop*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{ExtrusionSimulator*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple};