diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 2939425682..bf5fd62894 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -10,7 +10,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") endif() set(_unicode_utf8 OFF) -if (UNIX) # wxWidgets will not use char as the underlying type for wxString unless its forced to. +if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for wxString unless its forced to. set (_unicode_utf8 ON) endif() diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 7f7b589d0d..a4146f169a 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -15,20 +15,4 @@ sub config { return $self->object->config; } -sub region { - my $self = shift; - my ($region_id) = @_; - - while ($self->region_count <= $region_id) { - $self->add_region($self->object->print->get_region($self->region_count)); - } - - return $self->get_region($region_id); -} - -sub regions { - my ($self) = @_; - return [ map $self->get_region($_), 0..($self->region_count-1) ]; -} - 1; diff --git a/resources/localization/list.txt b/resources/localization/list.txt index d68f99bff8..bc8c9f2727 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -17,15 +17,20 @@ src/slic3r/GUI/GalleryDialog.cpp src/slic3r/GUI/GCodeViewer.cpp src/slic3r/GUI/GLCanvas3D.cpp src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +src/slic3r/GUI/Gizmos/GLGizmoCut.hpp src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp src/slic3r/GUI/Gizmos/GLGizmoMove.cpp src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp src/slic3r/GUI/Gizmos/GLGizmoScale.cpp src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp src/slic3r/GUI/Gizmos/GLGizmosManager.cpp src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp diff --git a/resources/profiles/Voron.idx b/resources/profiles/Voron.idx index a593d2fb4b..a169eb751e 100644 --- a/resources/profiles/Voron.idx +++ b/resources/profiles/Voron.idx @@ -1,2 +1,4 @@ +min_slic3r_version = 2.4.2 +1.0.1 Added 350mm Voron v1 variant. Updated max print heights. Removed redundant v1 volcano nozzle variants. min_slic3r_version = 2.4.0-beta0 1.0.0 Initial version diff --git a/resources/profiles/Voron.ini b/resources/profiles/Voron.ini index cd0fbd2d9a..e34da50100 100644 --- a/resources/profiles/Voron.ini +++ b/resources/profiles/Voron.ini @@ -7,7 +7,7 @@ name = Voron # 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/Voron/ @@ -72,21 +72,30 @@ default_materials = Basic PLA @VORON; Basic PLA VOLCANO @VORON; Basic PET @VORON [printer_model:Voron_v1_250_afterburner] name = Voron v1 250mm3 -variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8; volcano 0.6; volcano 0.8; volcano 1.0; volcano 1.2 +variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8 technology = FFF family = Voron v1 Afterburner bed_model = printbed-v1-250.stl bed_texture = bedtexture-v1-250.png -default_materials = Basic PLA @VORON; Basic PLA VOLCANO @VORON; Basic PET @VORON; Basic PET VOLCANO @VORON; Basic ABS @VORON; Basic ABS VOLCANO @VORON +default_materials = Basic PLA @VORON; Basic PET @VORON; Basic ABS @VORON [printer_model:Voron_v1_300_afterburner] name = Voron v1 300mm3 -variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8; volcano 0.6; volcano 0.8; volcano 1.0; volcano 1.2 +variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8 technology = FFF family = Voron v1 Afterburner bed_model = printbed-v1-300.stl bed_texture = bedtexture-v1-300.png -default_materials = Basic PLA @VORON; Basic PLA VOLCANO @VORON; Basic PET @VORON; Basic PET VOLCANO @VORON; Basic ABS @VORON; Basic ABS VOLCANO @VORON +default_materials = Basic PLA @VORON; Basic PET @VORON; Basic ABS @VORON + +[printer_model:Voron_v1_350_afterburner] +name = Voron v1 350mm3 +variants = 0.4; 0.25; 0.3; 0.5; 0.6; 0.8 +technology = FFF +family = Voron v1 Afterburner +bed_model = printbed-v1-350.stl +bed_texture = bedtexture-v2-350.png +default_materials = Basic PLA @VORON; Basic PET @VORON; Basic ABS @VORON [printer_model:Voron_v0_120] name = Voron Zero 120mm3 @@ -239,21 +248,21 @@ retract_speed = 50 [printer:*Voron_v2_250*] inherits = *common* bed_shape = 0x0,250x0,250x250,0x250 -max_print_height = 250 +max_print_height = 230 printer_model = Voron_v2_250 printer_notes = Unoffical profile.\nPRINTER_HAS_BOWDEN\nE3DV6 [printer:*Voron_v2_300*] inherits = *common* bed_shape = 0x0,300x0,300x300,0x300 -max_print_height = 300 +max_print_height = 280 printer_model = Voron_v2_300 printer_notes = Unoffical profile.\nPRINTER_HAS_BOWDEN\nE3DV6 [printer:*Voron_v2_350*] inherits = *common* bed_shape = 0x0,350x0,350x350,0x350 -max_print_height = 350 +max_print_height = 330 printer_model = Voron_v2_350 printer_notes = Unoffical profile.\nPRINTER_HAS_BOWDEN\nE3DV6 @@ -282,10 +291,17 @@ printer_notes = Unoffical profile.\nE3DV6 [printer:*Voron_v1_300_afterburner*] inherits = *common*; *afterburner* bed_shape = 0x0,300x0,300x300,0x300 -max_print_height = 230 +max_print_height = 280 printer_model = Voron_v1_300_afterburner printer_notes = Unoffical profile.\nE3DV6 +[printer:*Voron_v1_350_afterburner*] +inherits = *common*; *afterburner* +bed_shape = 0x0,350x0,350x350,0x350 +max_print_height = 330 +printer_model = Voron_v1_350_afterburner +printer_notes = Unoffical profile.\nE3DV6 + [printer:*Voron_v0_120*] inherits = *common* bed_shape = 0x0,120x0,120x120,0x120 @@ -455,6 +471,24 @@ inherits = *Voron_v1_300_afterburner*; *0.6nozzle* [printer:Voron_v1_300_afterburner 0.8 nozzle] inherits = *Voron_v1_300_afterburner*; *0.8nozzle* +[printer:Voron_v1_350_afterburner 0.25 nozzle] +inherits = *Voron_v1_350_afterburner*; *0.25nozzle* + +[printer:Voron_v1_350_afterburner 0.3 nozzle] +inherits = *Voron_v1_350_afterburner*; *0.3nozzle* + +[printer:Voron_v1_350_afterburner 0.4 nozzle] +inherits = *Voron_v1_350_afterburner*; *0.4nozzle* + +[printer:Voron_v1_350_afterburner 0.5 nozzle] +inherits = *Voron_v1_350_afterburner*; *0.5nozzle* + +[printer:Voron_v1_350_afterburner 0.6 nozzle] +inherits = *Voron_v1_350_afterburner*; *0.6nozzle* + +[printer:Voron_v1_350_afterburner 0.8 nozzle] +inherits = *Voron_v1_350_afterburner*; *0.8nozzle* + [printer:Voron_v2_250_afterburner 0.25 nozzle] inherits = *Voron_v2_250_afterburner*; *0.25nozzle* @@ -652,7 +686,7 @@ fill_angle = 45 fill_density = 15% fill_pattern = gyroid first_layer_acceleration = 1000 -first_layer_height = 75% +first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 diff --git a/resources/profiles/Voron/Voron_v1_250_afterburner_thumbnail.png b/resources/profiles/Voron/Voron_v1_250_afterburner_thumbnail.png index 07c3202cac..2b93390975 100644 Binary files a/resources/profiles/Voron/Voron_v1_250_afterburner_thumbnail.png and b/resources/profiles/Voron/Voron_v1_250_afterburner_thumbnail.png differ diff --git a/resources/profiles/Voron/Voron_v1_300_afterburner_thumbnail.png b/resources/profiles/Voron/Voron_v1_300_afterburner_thumbnail.png index 07c3202cac..2b93390975 100644 Binary files a/resources/profiles/Voron/Voron_v1_300_afterburner_thumbnail.png and b/resources/profiles/Voron/Voron_v1_300_afterburner_thumbnail.png differ diff --git a/resources/profiles/Voron/Voron_v1_350_afterburner_thumbnail.png b/resources/profiles/Voron/Voron_v1_350_afterburner_thumbnail.png index 07c3202cac..2b93390975 100644 Binary files a/resources/profiles/Voron/Voron_v1_350_afterburner_thumbnail.png and b/resources/profiles/Voron/Voron_v1_350_afterburner_thumbnail.png differ diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 4483d60102..c79d088435 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -837,6 +837,9 @@ extern "C" { "leak:libnvidia-tls.so\n" // For NVidia driver. "leak:terminator_CreateDevice\n" // For Intel Vulkan drivers. "leak:swrast_dri.so\n" // For Mesa 3D software driver. + "leak:amdgpu_dri.so\n" // For AMD driver. + "leak:libdrm_amdgpu.so\n" // For AMD driver. + "leak:libdbus-1.so\n" // For D-Bus library. Unsure if it is a leak or not. ; } } diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 3ea18de8dc..014b49af11 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -771,8 +771,8 @@ inline bool is_any_triangle_in_radius( auto distancer = detail::IndexedTriangleSetDistancer { vertices, faces, tree, point }; - size_t hit_idx; - VectorType hit_point = VectorType::Ones() * (std::nan("")); + size_t hit_idx; + VectorType hit_point = VectorType::Ones() * (NaN); if(tree.empty()) { @@ -828,22 +828,22 @@ struct Intersecting> { template auto intersecting(const G &g) { return Intersecting{g}; } -template struct Containing {}; +template struct Within {}; // Intersection predicate specialization for box-box intersections template -struct Containing> { +struct Within> { Eigen::AlignedBox box; - Containing(const Eigen::AlignedBox &bb): box{bb} {} + Within(const Eigen::AlignedBox &bb): box{bb} {} bool operator() (const typename Tree::Node &node) const { - return box.contains(node.bbox); + return node.is_leaf() ? box.contains(node.bbox) : box.intersects(node.bbox); } }; -template auto containing(const G &g) { return Containing{g}; } +template auto within(const G &g) { return Within{g}; } namespace detail { @@ -858,7 +858,7 @@ void traverse_recurse(const Tree &tree, if (!pred(tree.node(idx))) return; if (tree.node(idx).is_leaf()) { - callback(tree.node(idx).idx); + callback(tree.node(idx)); } else { // call this with left and right node idx: diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 0ff87c88d5..47171007ac 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -15,7 +15,7 @@ class CircleBed { double radius_; public: - inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} + inline CircleBed(): center_(0, 0), radius_(NaNd) {} explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} inline double radius() const { return radius_; } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d2ecefb9c7..242378d6db 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -212,6 +212,7 @@ set(SLIC3R_SOURCES PrintObject.cpp PrintObjectSlice.cpp PrintRegion.cpp + PointGrid.hpp PNGReadWrite.hpp PNGReadWrite.cpp QuadricEdgeCollapse.cpp diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 4b8dfa2343..2cfb0740ca 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -402,6 +402,42 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s return out; } +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (int v : values) { + if (i ++ > 0) + out += ", "; + out += std::to_string(v); + } + return out; +} + +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (float v : values) { + if (i ++ > 0) + out += ", "; + out += float_to_string_decimal_point(double(v)); + } + return out; +} + +std::string ConfigBase::SetDeserializeItem::format(std::initializer_list values) +{ + std::string out; + int i = 0; + for (float v : values) { + if (i ++ > 0) + out += ", "; + out += float_to_string_decimal_point(v); + } + return out; +} + void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) { // loop through options and apply them diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index bfd307de3d..b8c046ceba 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1950,6 +1950,11 @@ public: throw BadOptionTypeException("Conversion to a wrong type"); return static_cast(opt); } + + template T* opt(const t_config_option_key &opt_key, bool create = false) + { return dynamic_cast(this->optptr(opt_key, create)); } + template const T* opt(const t_config_option_key &opt_key) const + { return dynamic_cast(this->optptr(opt_key)); } // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), @@ -1995,11 +2000,23 @@ public: 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) {} SetDeserializeItem(const std::string &opt_key, const int value, bool append = false) : opt_key(opt_key), opt_value(std::to_string(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} SetDeserializeItem(const char *opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const float value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const char *opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} SetDeserializeItem(const std::string &opt_key, const double value, bool append = false) : opt_key(opt_key), opt_value(float_to_string_decimal_point(value)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const char *opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + SetDeserializeItem(const std::string &opt_key, const std::initializer_list values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {} + std::string opt_key; std::string opt_value; bool append = false; + + private: + static std::string format(std::initializer_list values); + static std::string format(std::initializer_list values); + static std::string format(std::initializer_list values); }; // May throw BadOptionTypeException() if the operation fails. void set_deserialize(std::initializer_list items, ConfigSubstitutionContext& substitutions); @@ -2008,7 +2025,31 @@ public: double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; - void setenv_() const; + + std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } + const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } + std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } + + double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } + const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + + int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } + int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } + int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + + // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. + // Thus the virtual method getInt() is used to retrieve the enum value. + template + ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } + + bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } + bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } + + void setenv_() const; ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule); @@ -2017,10 +2058,10 @@ public: ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule); - void save(const std::string &file) const; + void save(const std::string &file) const; // Set all the nullable values to nils. - void null_nullables(); + void null_nullables(); static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions); @@ -2129,10 +2170,6 @@ public: // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. const ConfigDef* def() const override { return nullptr; } - template T* opt(const t_config_option_key &opt_key, bool create = false) - { return dynamic_cast(this->option(opt_key, create)); } - template const T* opt(const t_config_option_key &opt_key) const - { return dynamic_cast(this->option(opt_key)); } // Overrides ConfigResolver::optptr(). const ConfigOption* optptr(const t_config_option_key &opt_key) const override; // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. @@ -2163,29 +2200,6 @@ public: // Returns options being equal in the two configs, ignoring options not present in both configs. t_config_option_keys equal(const DynamicConfig &other) const; - std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } - const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } - std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } - - double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } - const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } - double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } - - int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } - int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } - int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } - int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } - - // In ConfigManipulation::toggle_print_fff_options, it is called on option with type ConfigOptionEnumGeneric* and also ConfigOptionEnum*. - // Thus the virtual method getInt() is used to retrieve the enum value. - template - ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } - - bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } - bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } - // Command line processing bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 344450c4a9..7eccf2ec82 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -67,6 +67,8 @@ public: void simplify(double tolerance, ExPolygons* expolygons) const; void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const; void medial_axis(double max_width, double min_width, Polylines* polylines) const; + Polylines medial_axis(double max_width, double min_width) const + { Polylines out; this->medial_axis(max_width, min_width, &out); return out; } Lines lines() const; // Number of contours (outer contour with holes). diff --git a/src/libslic3r/Execution/Execution.hpp b/src/libslic3r/Execution/Execution.hpp index dcfd86bde8..57ad4b41b9 100644 --- a/src/libslic3r/Execution/Execution.hpp +++ b/src/libslic3r/Execution/Execution.hpp @@ -30,8 +30,8 @@ template using AsTraits = Traits>; // Each execution policy should declare two types of mutexes. A a spin lock and // a blocking mutex. These types should satisfy the BasicLockable concept. -template using SpinningMutex = typename Traits::SpinningMutex; -template using BlockingMutex = typename Traits::BlockingMutex; +template using SpinningMutex = typename AsTraits::SpinningMutex; +template using BlockingMutex = typename AsTraits::BlockingMutex; // Query the available threads for concurrency. template > diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 6a4a675cbe..0bdd1c7e88 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -63,7 +63,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object Polygons infill_area_here; for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) for (const Surface& surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) + if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_area_here, infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); //Remove the part of the infill area that is already supported by the walls. @@ -92,7 +92,7 @@ void Generator::generateTrees(const PrintObject &print_object) for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternal) + if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_outlines[layer_id], infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); // For various operations its beneficial to quickly locate nearby features on the polygon: @@ -116,8 +116,12 @@ void Generator::generateTrees(const PrintObject &print_object) if (layer_id == 0) return; - const Polygons& below_outlines = infill_outlines[layer_id - 1]; - outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON)); + const Polygons &below_outlines = infill_outlines[layer_id - 1]; + BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON); + if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined) + below_outlines_bbox.merge(outlines_locator_bbox); + + outlines_locator.set_bbox(below_outlines_bbox); outlines_locator.create(below_outlines, locator_cell_size); std::vector& lower_trees = m_lightning_layers[layer_id - 1].tree_roots; diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 1e1127a792..c996b9b7b4 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -6,6 +6,7 @@ #include "DistanceField.hpp" #include "TreeNode.hpp" +#include "../../ClipperUtils.hpp" #include "../../Geometry.hpp" #include "Utils.hpp" @@ -271,6 +272,7 @@ void Layer::reconnectRoots } } +#if 0 /*! * Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance. * Given a \p distance more than zero, the point will end up inside, and conversely outside. @@ -398,6 +400,7 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan } return static_cast(-1); } +#endif // Returns 'added someting'. Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const @@ -405,31 +408,11 @@ Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t if (tree_roots.empty()) return {}; - Polygons result_lines; - for (const auto& tree : tree_roots) { - // If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon. - // (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.) - constexpr coord_t epsilon = 5; - Point should_be_inside = tree->getLocation(); - moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon); - if (inside(limit_to_outline, should_be_inside)) - tree->convertToPolylines(result_lines, line_width); - } + Polylines result_lines; + for (const auto &tree : tree_roots) + tree->convertToPolylines(result_lines, line_width); - // TODO: allow for polylines! - Polylines split_lines; - for (Polygon &line : result_lines) { - if (line.size() <= 1) - continue; - Point last = line[0]; - for (size_t point_idx = 1; point_idx < line.size(); point_idx++) { - Point here = line[point_idx]; - split_lines.push_back({ last, here }); - last = here; - } - } - - return split_lines; + return intersection_pl(result_lines, limit_to_outline); } } // namespace Slic3r::Lightning diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index d1820410ee..822550fc49 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -343,16 +343,16 @@ coord_t Node::prune(const coord_t& pruning_distance) return max_distance_pruned; } -void Node::convertToPolylines(Polygons& output, const coord_t line_width) const +void Node::convertToPolylines(Polylines &output, const coord_t line_width) const { - Polygons result; + Polylines result; result.emplace_back(); convertToPolylines(0, result); removeJunctionOverlap(result, line_width); append(output, std::move(result)); } -void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const +void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const { if (m_children.empty()) { output[long_line_idx].points.push_back(m_p); @@ -372,12 +372,12 @@ void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const } } -void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const +void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_width) const { const coord_t reduction = line_width / 2; // TODO make configurable? size_t res_line_idx = 0; while (res_line_idx < result_lines.size()) { - Polygon &polyline = result_lines[res_line_idx]; + Polyline &polyline = result_lines[res_line_idx]; if (polyline.size() <= 1) { polyline = std::move(result_lines.back()); result_lines.pop_back(); @@ -387,7 +387,7 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt coord_t to_be_reduced = reduction; Point a = polyline.back(); for (int point_idx = int(polyline.size()) - 2; point_idx >= 0; point_idx--) { - const Point b = polyline[point_idx]; + const Point b = polyline.points[point_idx]; const Point ab = b - a; const auto ab_len = coord_t(ab.cast().norm()); if (ab_len >= to_be_reduced) { diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index 55ef35e0a8..fdb80d2e6f 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -239,7 +239,7 @@ public: * * \param output all branches in this tree connected into polylines */ - void convertToPolylines(Polygons& output, coord_t line_width) const; + void convertToPolylines(Polylines &output, coord_t line_width) const; /*! If this was ever a direct child of the root, it'll have a previous grounding location. * @@ -258,9 +258,9 @@ protected: * \param long_line a reference to a polyline in \p output which to continue building on in the recursion * \param output all branches in this tree connected into polylines */ - void convertToPolylines(size_t long_line_idx, Polygons& output) const; + void convertToPolylines(size_t long_line_idx, Polylines &output) const; - void removeJunctionOverlap(Polygons& polylines, coord_t line_width) const; + void removeJunctionOverlap(Polylines &polylines, coord_t line_width) const; bool m_is_root; Point m_p; diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 1fe0405184..91a81c7f31 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -26,6 +26,8 @@ public: void reset(const Vec3d &position); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); + std::string process_layer(const std::string &gcode, size_t layer_id, bool flush) + { return this->process_layer(std::string(gcode), layer_id, flush); } private: CoolingBuffer& operator=(const CoolingBuffer&) = delete; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 92e0a698e3..1611b23254 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1991,14 +1991,15 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers if (comment == reserved_tag(ETags::Layer_Change)) { ++m_layer_id; if (m_spiral_vase_active) { - if (m_result.moves.empty()) - m_result.spiral_vase_layers.push_back({ m_first_layer_height, { 0, 0 } }); + if (m_result.moves.empty() || m_result.spiral_vase_layers.empty()) + // add a placeholder for layer height. the actual value will be set inside process_G1() method + m_result.spiral_vase_layers.push_back({ FLT_MAX, { 0, 0 } }); else { const size_t move_id = m_result.moves.size() - 1; - if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first) + if (!m_result.spiral_vase_layers.empty()) m_result.spiral_vase_layers.back().second.second = move_id; - else - m_result.spiral_vase_layers.push_back({ static_cast(m_end_position[Z]), { move_id, move_id } }); + // add a placeholder for layer height. the actual value will be set inside process_G1() method + m_result.spiral_vase_layers.push_back({ FLT_MAX, { move_id, move_id } }); } } return; @@ -2828,8 +2829,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); } - if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty() && !m_result.moves.empty()) - m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; + if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty()) { + if (m_result.spiral_vase_layers.back().first == FLT_MAX && delta_pos[Z] > 0.0) + // replace layer height placeholder with correct value + m_result.spiral_vase_layers.back().first = static_cast(m_end_position[Z]); + if (!m_result.moves.empty()) + m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; + } // store move #if ENABLE_PROCESS_G2_G3_LINES diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp index 9ba957824e..94f4e4cf21 100644 --- a/src/libslic3r/Geometry/ConvexHull.hpp +++ b/src/libslic3r/Geometry/ConvexHull.hpp @@ -1,9 +1,15 @@ #ifndef slic3r_Geometry_ConvexHull_hpp_ #define slic3r_Geometry_ConvexHull_hpp_ +#include + #include "../Polygon.hpp" namespace Slic3r { + +class ExPolygon; +using ExPolygons = std::vector; + namespace Geometry { Pointf3s convex_hull(Pointf3s points); diff --git a/src/libslic3r/KDTreeIndirect.hpp b/src/libslic3r/KDTreeIndirect.hpp index 36a157456f..37c10827b1 100644 --- a/src/libslic3r/KDTreeIndirect.hpp +++ b/src/libslic3r/KDTreeIndirect.hpp @@ -11,231 +11,276 @@ namespace Slic3r { +enum class VisitorReturnMask : unsigned int { + CONTINUE_LEFT = 1, + CONTINUE_RIGHT = 2, + STOP = 4, +}; + // KD tree for N-dimensional closest point search. template class KDTreeIndirect { public: - static constexpr size_t NumDimensions = ANumDimensions; - using CoordinateFn = ACoordinateFn; - using CoordType = ACoordType; + static constexpr size_t NumDimensions = ANumDimensions; + using CoordinateFn = ACoordinateFn; + using CoordType = ACoordType; // Following could be static constexpr size_t, but that would not link in C++11 enum : size_t { npos = size_t(-1) }; - KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {} - KDTreeIndirect(CoordinateFn coordinate, std::vector indices) : coordinate(coordinate) { this->build(std::move(indices)); } - KDTreeIndirect(CoordinateFn coordinate, std::vector &&indices) : coordinate(coordinate) { this->build(std::move(indices)); } - KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); } - KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {} - KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; } - void clear() { m_nodes.clear(); } + KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {} + KDTreeIndirect(CoordinateFn coordinate, std::vector indices) : coordinate(coordinate) { this->build(indices); } + KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); } + KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {} + KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; } + void clear() { m_nodes.clear(); } - void build(size_t num_indices) - { - std::vector indices; - indices.reserve(num_indices); - for (size_t i = 0; i < num_indices; ++ i) - indices.emplace_back(i); - this->build(std::move(indices)); - } + void build(size_t num_indices) + { + std::vector indices; + indices.reserve(num_indices); + for (size_t i = 0; i < num_indices; ++ i) + indices.emplace_back(i); + this->build(indices); + } - void build(std::vector &&indices) - { - if (indices.empty()) - clear(); - else { - // Allocate enough memory for a full binary tree. - m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos); - build_recursive(indices, 0, 0, 0, indices.size() - 1); - } - indices.clear(); - } + void build(std::vector &indices) + { + if (indices.empty()) + clear(); + else { + // Allocate enough memory for a full binary tree. + m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos); + build_recursive(indices, 0, 0, 0, indices.size() - 1); + } + indices.clear(); + } - enum class VisitorReturnMask : unsigned int - { - CONTINUE_LEFT = 1, - CONTINUE_RIGHT = 2, - STOP = 4, - }; - template - unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const - { - CoordType dist = point_coord - this->coordinate(idx, dimension); - return (dist * dist < search_radius + CoordType(EPSILON)) ? - // The plane intersects a hypersphere centered at point_coord of search_radius. - ((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) : - // The plane does not intersect the hypersphere. - (dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT); - } + template + unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const + { + CoordType dist = point_coord - this->coordinate(idx, dimension); + return (dist * dist < search_radius + CoordType(EPSILON)) ? + // The plane intersects a hypersphere centered at point_coord of search_radius. + ((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) : + // The plane does not intersect the hypersphere. + (dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT); + } - // Visitor is supposed to return a bit mask of VisitorReturnMask. - template - void visit(Visitor &visitor) const - { + // Visitor is supposed to return a bit mask of VisitorReturnMask. + template + void visit(Visitor &visitor) const + { visit_recursive(0, 0, visitor); - } + } - CoordinateFn coordinate; + CoordinateFn coordinate; private: - // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. - void build_recursive(std::vector &input, size_t node, const size_t dimension, const size_t left, const size_t right) - { - if (left > right) - return; + // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. + void build_recursive(std::vector &input, size_t node, const size_t dimension, const size_t left, const size_t right) + { + if (left > right) + return; - assert(node < m_nodes.size()); + assert(node < m_nodes.size()); - if (left == right) { - // Insert a node into the balanced tree. - m_nodes[node] = input[left]; - return; - } + if (left == right) { + // Insert a node into the balanced tree. + m_nodes[node] = input[left]; + return; + } - // Partition the input to left / right pieces of the same length to produce a balanced tree. - size_t center = (left + right) / 2; - partition_input(input, dimension, left, right, center); - // Insert a node into the tree. - m_nodes[node] = input[center]; - // Build up the left / right subtrees. - size_t next_dimension = dimension; - if (++ next_dimension == NumDimensions) - next_dimension = 0; - if (center > left) - build_recursive(input, node * 2 + 1, next_dimension, left, center - 1); - build_recursive(input, node * 2 + 2, next_dimension, center + 1, right); - } + // Partition the input to left / right pieces of the same length to produce a balanced tree. + size_t center = (left + right) / 2; + partition_input(input, dimension, left, right, center); + // Insert a node into the tree. + m_nodes[node] = input[center]; + // Build up the left / right subtrees. + size_t next_dimension = dimension; + if (++ next_dimension == NumDimensions) + next_dimension = 0; + if (center > left) + build_recursive(input, node * 2 + 1, next_dimension, left, center - 1); + build_recursive(input, node * 2 + 2, next_dimension, center + 1, right); + } - // Partition the input m_nodes at "k" and "dimension" using the QuickSelect method: - // https://en.wikipedia.org/wiki/Quickselect - // Items left of the k'th item are lower than the k'th item in the "dimension", - // items right of the k'th item are higher than the k'th item in the "dimension", - void partition_input(std::vector &input, const size_t dimension, size_t left, size_t right, const size_t k) const - { - while (left < right) { - size_t center = (left + right) / 2; - CoordType pivot; - { - // Bubble sort the input[left], input[center], input[right], so that a median of the three values - // will end up in input[center]. - CoordType left_value = this->coordinate(input[left], dimension); - CoordType center_value = this->coordinate(input[center], dimension); - CoordType right_value = this->coordinate(input[right], dimension); - if (left_value > center_value) { - std::swap(input[left], input[center]); - std::swap(left_value, center_value); - } - if (left_value > right_value) { - std::swap(input[left], input[right]); - right_value = left_value; - } - if (center_value > right_value) { - std::swap(input[center], input[right]); - center_value = right_value; - } - pivot = center_value; - } - if (right <= left + 2) - // The interval is already sorted. - break; - size_t i = left; - size_t j = right - 1; - std::swap(input[center], input[j]); - // Partition the set based on the pivot. - for (;;) { - // Skip left points that are already at correct positions. - // Search will certainly stop at position (right - 1), which stores the pivot. - while (this->coordinate(input[++ i], dimension) < pivot) ; - // Skip right points that are already at correct positions. - while (this->coordinate(input[-- j], dimension) > pivot && i < j) ; - if (i >= j) - break; - std::swap(input[i], input[j]); - } - // Restore pivot to the center of the sequence. - std::swap(input[i], input[right - 1]); - // Which side the kth element is in? - if (k < i) - right = i - 1; - else if (k == i) - // Sequence is partitioned, kth element is at its place. - break; - else - left = i + 1; - } - } + // Partition the input m_nodes at "k" and "dimension" using the QuickSelect method: + // https://en.wikipedia.org/wiki/Quickselect + // Items left of the k'th item are lower than the k'th item in the "dimension", + // items right of the k'th item are higher than the k'th item in the "dimension", + void partition_input(std::vector &input, const size_t dimension, size_t left, size_t right, const size_t k) const + { + while (left < right) { + size_t center = (left + right) / 2; + CoordType pivot; + { + // Bubble sort the input[left], input[center], input[right], so that a median of the three values + // will end up in input[center]. + CoordType left_value = this->coordinate(input[left], dimension); + CoordType center_value = this->coordinate(input[center], dimension); + CoordType right_value = this->coordinate(input[right], dimension); + if (left_value > center_value) { + std::swap(input[left], input[center]); + std::swap(left_value, center_value); + } + if (left_value > right_value) { + std::swap(input[left], input[right]); + right_value = left_value; + } + if (center_value > right_value) { + std::swap(input[center], input[right]); + center_value = right_value; + } + pivot = center_value; + } + if (right <= left + 2) + // The interval is already sorted. + break; + size_t i = left; + size_t j = right - 1; + std::swap(input[center], input[j]); + // Partition the set based on the pivot. + for (;;) { + // Skip left points that are already at correct positions. + // Search will certainly stop at position (right - 1), which stores the pivot. + while (this->coordinate(input[++ i], dimension) < pivot) ; + // Skip right points that are already at correct positions. + while (this->coordinate(input[-- j], dimension) > pivot && i < j) ; + if (i >= j) + break; + std::swap(input[i], input[j]); + } + // Restore pivot to the center of the sequence. + std::swap(input[i], input[right - 1]); + // Which side the kth element is in? + if (k < i) + right = i - 1; + else if (k == i) + // Sequence is partitioned, kth element is at its place. + break; + else + left = i + 1; + } + } - template - void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const - { - assert(! m_nodes.empty()); - if (node >= m_nodes.size() || m_nodes[node] == npos) - return; + template + void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const + { + assert(! m_nodes.empty()); + if (node >= m_nodes.size() || m_nodes[node] == npos) + return; - // Left / right child node index. - size_t left = node * 2 + 1; - size_t right = left + 1; - unsigned int mask = visitor(m_nodes[node], dimension); - if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) { - size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension; - if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT) - visit_recursive(left, next_dimension, visitor); - if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT) - visit_recursive(right, next_dimension, visitor); - } - } + // Left / right child node index. + size_t left = node * 2 + 1; + size_t right = left + 1; + unsigned int mask = visitor(m_nodes[node], dimension); + if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) { + size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension; + if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT) + visit_recursive(left, next_dimension, visitor); + if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT) + visit_recursive(right, next_dimension, visitor); + } + } - std::vector m_nodes; + std::vector m_nodes; }; // Find a closest point using Euclidian metrics. // Returns npos if not found. -template -size_t find_closest_point(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) +template +std::array find_closest_points( + const KDTreeIndirect &kdtree, + const PointType &point, + FilterFn filter) { - using CoordType = typename KDTreeIndirectType::CoordType; + using Tree = KDTreeIndirect; - struct Visitor { - const KDTreeIndirectType &kdtree; - const PointType &point; - const FilterFn filter; - size_t min_idx = KDTreeIndirectType::npos; - CoordType min_dist = std::numeric_limits::max(); + struct Visitor + { + const Tree &kdtree; + const PointType &point; + const FilterFn filter; - Visitor(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) : kdtree(kdtree), point(point), filter(filter) {} - unsigned int operator()(size_t idx, size_t dimension) { - if (this->filter(idx)) { - auto dist = CoordType(0); - for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++ i) { - CoordType d = point[i] - kdtree.coordinate(idx, i); - dist += d * d; - } - if (dist < min_dist) { - min_dist = dist; - min_idx = idx; - } - } - return kdtree.descent_mask(point[dimension], min_dist, idx, dimension); - } - } visitor(kdtree, point, filter); + std::array, K> results; - kdtree.visit(visitor); - return visitor.min_idx; + Visitor(const Tree &kdtree, const PointType &point, FilterFn filter) + : kdtree(kdtree), point(point), filter(filter) + { + results.fill(std::make_pair(Tree::npos, + std::numeric_limits::max())); + } + unsigned int operator()(size_t idx, size_t dimension) + { + if (this->filter(idx)) { + auto dist = CoordT(0); + for (size_t i = 0; i < D; ++i) { + CoordT d = point[i] - kdtree.coordinate(idx, i); + dist += d * d; + } + + auto res = std::make_pair(idx, dist); + auto it = std::lower_bound(results.begin(), results.end(), + res, [](auto &r1, auto &r2) { + return r1.second < r2.second; + }); + + if (it != results.end()) { + std::rotate(it, std::prev(results.end()), results.end()); + *it = res; + } + } + return kdtree.descent_mask(point[dimension], + results.front().second, idx, + dimension); + } + } visitor(kdtree, point, filter); + + kdtree.visit(visitor); + std::array ret; + for (size_t i = 0; i < K; i++) ret[i] = visitor.results[i].first; + + return ret; +} + +template +std::array find_closest_points( + const KDTreeIndirect &kdtree, const PointType &point) +{ + return find_closest_points(kdtree, point, [](size_t) { return true; }); +} + +template +size_t find_closest_point(const KDTreeIndirect &kdtree, + const PointType &point, + FilterFn filter) +{ + return find_closest_points<1>(kdtree, point, filter)[0]; } template size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& point) { - return find_closest_point(kdtree, point, [](size_t) { return true; }); + return find_closest_point(kdtree, point, [](size_t) { return true; }); } // Find nearby points (spherical neighbourhood) using Euclidian metrics. template std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, - const typename KDTreeIndirectType::CoordType& max_distance, FilterFn filter) - { + const typename KDTreeIndirectType::CoordType& max_distance, FilterFn filter) +{ using CoordType = typename KDTreeIndirectType::CoordType; struct Visitor { @@ -247,7 +292,7 @@ std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const P Visitor(const KDTreeIndirectType &kdtree, const PointType& center, const CoordType &max_distance, FilterFn filter) : - kdtree(kdtree), center(center), max_distance_squared(max_distance*max_distance), filter(filter) { + kdtree(kdtree), center(center), max_distance_squared(max_distance*max_distance), filter(filter) { } unsigned int operator()(size_t idx, size_t dimension) { if (this->filter(idx)) { @@ -260,7 +305,7 @@ std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const P result.push_back(idx); } } - return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension); + return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension); } } visitor(kdtree, center, max_distance, filter); @@ -270,13 +315,59 @@ std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const P template std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, - const typename KDTreeIndirectType::CoordType& max_distance) - { + const typename KDTreeIndirectType::CoordType& max_distance) +{ return find_nearby_points(kdtree, center, max_distance, [](size_t) { return true; }); } +// Find nearby points (spherical neighbourhood) using Euclidian metrics. +template +std::vector find_nearby_points(const KDTreeIndirectType &kdtree, + const PointType &bb_min, + const PointType &bb_max, + FilterFn filter) +{ + struct Visitor { + const KDTreeIndirectType &kdtree; + const PointType &bb_min, &bb_max; + const FilterFn filter; + std::vector result; + + Visitor(const KDTreeIndirectType &kdtree, const PointType& bbmin, const PointType& bbmax, + FilterFn filter) : + kdtree(kdtree), bb_min{bbmin}, bb_max{bbmax}, filter(filter) { + } + unsigned int operator()(size_t idx, size_t dimension) { + unsigned int ret = + static_cast(VisitorReturnMask::CONTINUE_LEFT) | + static_cast(VisitorReturnMask::CONTINUE_RIGHT); + + if (this->filter(idx)) { + PointType p; + bool contains = true; + for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) { + p(i) = kdtree.coordinate(idx, i); + contains = contains && bb_min(i) <= p(i) && p(i) <= bb_max(i); + } + + if (p(dimension) < bb_min(dimension)) + ret = static_cast(VisitorReturnMask::CONTINUE_RIGHT); + if (p(dimension) > bb_max(dimension)) + ret = static_cast(VisitorReturnMask::CONTINUE_LEFT); + + if (contains) + result.emplace_back(idx); + } + + return ret; + } + } visitor(kdtree, bb_min, bb_max, filter); + + kdtree.visit(visitor); + return visitor.result; +} } // namespace Slic3r diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index 8ae55c61c5..bf95d9ee07 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -41,13 +41,13 @@ template using Bounds = std::array; class StopCriteria { // If the absolute value difference between two scores. - double m_abs_score_diff = std::nan(""); + double m_abs_score_diff = NaNd; // If the relative value difference between two scores. - double m_rel_score_diff = std::nan(""); + double m_rel_score_diff = NaNd; // Stop if this value or better is found. - double m_stop_score = std::nan(""); + double m_stop_score = NaNd; // A predicate that if evaluates to true, the optimization should terminate // and the best result found prior to termination should be returned. diff --git a/src/libslic3r/PointGrid.hpp b/src/libslic3r/PointGrid.hpp new file mode 100644 index 0000000000..acffd2b871 --- /dev/null +++ b/src/libslic3r/PointGrid.hpp @@ -0,0 +1,74 @@ +#ifndef POINTGRID_HPP +#define POINTGRID_HPP + +#include +#include +#include + +namespace Slic3r { + +template +class PointGrid { + Vec3i m_size; + std::vector> m_data; + const int XY; + +public: + explicit PointGrid(std::vector> data, const Vec3i &size) + : m_data(std::move(data)), m_size{size}, XY{m_size.x() * m_size.y()} + {} + + const Vec<3, T> & get(size_t idx) const { return m_data[idx]; } + const Vec<3, T> & get(const Vec3i &coord) const + { + return m_data[get_idx(coord)]; + } + + size_t get_idx(const Vec3i &coord) const + { + size_t ret = coord.z() * XY + coord.y() * m_size.x() + coord.x(); + + return ret; + } + + Vec3i get_coord(size_t idx) const { + size_t iz = idx / XY; + size_t iy = (idx / m_size.x()) % m_size.y(); + size_t ix = idx % m_size.x(); + + return {ix, iy, iz}; + } + + const std::vector> & data() const { return m_data; } + size_t point_count() const { return m_data.size(); } + bool empty() const { return m_data.empty(); } +}; + +template +PointGrid point_grid(Ex policy, + const BoundingBox3Base> &bounds, + const Vec<3, CoordT> &stride) +{ + Vec3i numpts = Vec3i::Zero(); + + for (int n = 0; n < 3; ++n) + numpts(n) = (bounds.max(n) - bounds.min(n)) / stride(n); + + std::vector> out(numpts.x() * numpts.y() * numpts.z()); + + size_t XY = numpts[X] * numpts[Y]; + + execution::for_each(policy, size_t(0), out.size(), [&](size_t i) { + size_t iz = i / XY; + size_t iy = (i / numpts[X]) % numpts[Y]; + size_t ix = i % numpts[X]; + + out[i] = Vec<3, CoordT>(ix * stride.x(), iy * stride.y(), iz * stride.z()); + }); + + return PointGrid{std::move(out), numpts}; +} + +} // namespace Slic3r + +#endif // POINTGRID_HPP diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ab12bc8ce2..0c1060b7de 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -220,6 +220,16 @@ public: config.set_deserialize_strict(items); return config; } + static DynamicPrintConfig new_with(const t_config_option_key &opt_key, const std::string &str, bool append = false) { + DynamicPrintConfig config; + config.set_deserialize_strict(opt_key, str, append); + return config; + } + static DynamicPrintConfig new_with(std::initializer_list items) { + DynamicPrintConfig config; + config.set_deserialize_strict(items); + return config; + } static DynamicPrintConfig* new_from_defaults_keys(const std::vector &keys); // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. @@ -1131,6 +1141,8 @@ public: void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); } void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false) { m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); } + void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false) + { m_data.set_deserialize_strict(opt_key, str, append); this->touch(); } bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; } // Getters are thread safe. diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 196646dc9e..847e638e6f 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -76,7 +76,7 @@ struct Facestats { // Try to guess the number of support points needed to support a mesh double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr) { - if (mesh.its.vertices.empty()) return std::nan(""); + if (mesh.its.vertices.empty()) return NaNd; auto accessfn = [&mesh, &tr](size_t fi) { Facestats fc{get_transformed_triangle(mesh, tr, fi)}; @@ -117,7 +117,7 @@ inline double get_supportedness_score(const Facestats &fc) // Try to guess the number of support points needed to support a mesh double get_supportedness_score(const TriangleMesh &mesh, const Transform3f &tr) { - if (mesh.its.vertices.empty()) return std::nan(""); + if (mesh.its.vertices.empty()) return NaNd; auto accessfn = [&mesh, &tr](size_t fi) { Facestats fc{get_transformed_triangle(mesh, tr, fi)}; @@ -149,10 +149,10 @@ float find_ground_level(const TriangleMesh &mesh, return execution::reduce(ex_tbb, size_t(0), vsize, zmin, minfn, accessfn, granularity); } -float get_supportedness_onfloor_score(const TriangleMesh &mesh, - const Transform3f & tr) +double get_supportedness_onfloor_score(const TriangleMesh &mesh, + const Transform3f &tr) { - if (mesh.its.vertices.empty()) return std::nan(""); + if (mesh.its.vertices.empty()) return NaNd; size_t Nthreads = std::thread::hardware_concurrency(); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index aa69fdc777..e4ca3f59cd 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -654,7 +654,7 @@ void SupportTreeBuildsteps::filter() for (const SupportPoint &sp : m_support_pts) { m_thr(); heads.emplace_back( - std::nan(""), + NaNd, sp.head_front_radius, 0., m_cfg.head_penetration_mm, diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index de3e3ae96d..c3ec3e3997 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -408,9 +408,9 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) AABBTreeIndirect::traverse( tree, AABBTreeIndirect::intersecting(ebb), - [&part_to_drill, &hollowed_mesh](size_t faceid) + [&part_to_drill, &hollowed_mesh](const auto& node) { - part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[faceid]); + part_to_drill.indices.emplace_back(hollowed_mesh.its.indices[node.idx]); }); auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal( @@ -1036,7 +1036,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Estimated printing time // A layers count o the highest object if (printer_input.size() == 0) - print_statistics.estimated_print_time = std::nan(""); + print_statistics.estimated_print_time = NaNd; else { print_statistics.estimated_print_time = estim_time; print_statistics.layers_times = layers_times; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 2131a9233a..06cad840e5 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -331,6 +331,12 @@ public: inline bool empty() const { return size() == 0; } }; +template> +constexpr T NaN = std::numeric_limits::quiet_NaN(); + +constexpr float NaNf = NaN; +constexpr double NaNd = NaN; + } // namespace Slic3r #endif diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp index 81c681bc3a..7f99a505c6 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.cpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp @@ -10,6 +10,7 @@ #include "libslic3r/Platform.hpp" #include "libslic3r/Config.hpp" +#include #include #include #include @@ -503,4 +504,4 @@ DesktopIntegrationDialog::~DesktopIntegrationDialog() } // namespace GUI } // namespace Slic3r -#endif // __linux__ \ No newline at end of file +#endif // __linux__ diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 20a3d83960..dda50ec053 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1163,7 +1163,7 @@ void Control::draw_ruler(wxDC& dc) } }; - double short_tick = std::nan(""); + double short_tick = NaNd; int tick = 0; double value = 0.0; size_t sequence = 0; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index afcc3f9541..652410589f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3942,7 +3942,7 @@ void GCodeViewer::render_legend(float& legend_height) 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; std::vector last_color(m_extruders_count); - for (int i = 0; i < m_extruders_count; ++i) { + for (size_t 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.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 09659faea1..1876500e23 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -822,7 +822,7 @@ public: class WipeTowerInfo { protected: - Vec2d m_pos = {std::nan(""), std::nan("")}; + Vec2d m_pos = {NaNd, NaNd}; double m_rotation = 0.; BoundingBoxf m_bb; friend class GLCanvas3D; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8533854f4d..4435f836b3 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index a861f478f3..257fe2532c 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -338,10 +338,12 @@ void OptionsGroup::activate_line(Line& line) wxBOTTOM | wxTOP | (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), (wxOSX || !staticbox) ? 0 : 2); if (is_sizer_field(field)) sizer->Add(field->getSizer(), 1, (option.opt.full_width ? int(wxEXPAND) : int(wxALIGN_CENTER_VERTICAL)), 0); - } + } else + delete sizer; return; } + bool sizer_is_used = false; bool is_multioption_line = option_set.size() > 1; for (auto opt : option_set) { ConfigOptionDef option = opt.opt; @@ -356,8 +358,9 @@ void OptionsGroup::activate_line(Line& line) wxSize(sublabel_width != -1 ? sublabel_width * wxGetApp().em_unit() : -1, -1), wxALIGN_RIGHT); label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(wxGetApp().normal_font()); - sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); - } + sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_is_used = true; + } // add field const Option& opt_ref = opt; @@ -412,6 +415,9 @@ void OptionsGroup::activate_line(Line& line) if (!custom_ctrl) sizer->Add(line.extra_widget_sizer, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification } + + if (custom_ctrl && !sizer_is_used) + delete sizer; } // create all controls for the option group from the m_lines diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 75c5c116fd..660cfc7f0c 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -9,6 +9,7 @@ #include "Notebook.hpp" #include "ButtonsDescription.hpp" #include "OG_CustomCtrl.hpp" +#include "GLCanvas3D.hpp" namespace Slic3r { @@ -54,6 +55,14 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent) : m_highlighter.set_timer_owner(this, 0); } +static void update_color(wxColourPickerCtrl* color_pckr, const wxColour& color) +{ + if (color_pckr->GetColour() != color) { + color_pckr->SetColour(color); + wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED)); + } +} + void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/) { int selected_tab = 0; @@ -66,6 +75,14 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin if (!highlight_opt_key.empty()) init_highlighter(highlight_opt_key); + // cache input values for custom toolbar size + m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); + m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; + + // update colors for color pickers + update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); + update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + this->ShowModal(); } @@ -173,6 +190,10 @@ void PreferencesDialog::build() // Add "General" tab m_optgroup_general = create_options_tab(L("General"), tabs); m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project") m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; else if (opt_key == "default_action_on_dirty_project") @@ -335,6 +356,10 @@ void PreferencesDialog::build() // Add "Camera" tab m_optgroup_camera = create_options_tab(L("Camera"), tabs); m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key);it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -358,25 +383,42 @@ void PreferencesDialog::build() // Add "GUI" tab m_optgroup_gui = create_options_tab(L("GUI"), tabs); m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "suppress_hyperlinks") - m_values[opt_key] = boost::any_cast(value) ? "1" : ""; - else if (opt_key == "notify_release") { + if (opt_key == "notify_release") { int val_int = boost::any_cast(value); for (const auto& item : s_keys_map_NotifyReleaseMode) { if (item.second == val_int) { m_values[opt_key] = item.first; - break; + return; } } - } else - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - + } if (opt_key == "use_custom_toolbar_size") { m_icon_size_sizer->ShowItems(boost::any_cast(value)); - m_optgroup_gui->parent()->Layout(); - tabs->Layout(); - this->layout(); + refresh_og(m_optgroup_gui); + get_app_config()->set("use_custom_toolbar_size", boost::any_cast(value) ? "1" : "0"); + get_app_config()->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); + return; } + if (opt_key == "tabs_as_menu") { + bool disable_new_layout = boost::any_cast(value); + m_rb_new_settings_layout_mode->Show(!disable_new_layout); + if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } + refresh_og(m_optgroup_gui); + } + + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + + if (opt_key == "suppress_hyperlinks") + m_values[opt_key] = boost::any_cast(value) ? "1" : ""; + else + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; append_bool_option(m_optgroup_gui, "seq_top_layer_only", @@ -464,6 +506,10 @@ void PreferencesDialog::build() // Add "Render" tab m_optgroup_render = create_options_tab(L("Render"), tabs); m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -479,6 +525,10 @@ void PreferencesDialog::build() // Add "Dark Mode" tab m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs); m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; }; @@ -509,6 +559,7 @@ void PreferencesDialog::build() auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK); + this->Bind(wxEVT_BUTTON, &PreferencesDialog::revert, this, wxID_CANCEL); for (int id : {wxID_OK, wxID_CANCEL}) wxGetApp().UpdateDarkUI(static_cast(FindWindowById(id, this))); @@ -592,19 +643,6 @@ void PreferencesDialog::accept(wxEvent&) } } - for (const std::string& key : { "default_action_on_close_application", - "default_action_on_select_preset", - "default_action_on_new_project" }) { - auto it = m_values.find(key); - if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none") - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - } - { - auto it = m_values.find("default_action_on_dirty_project"); - if (it != m_values.end() && !it->second.empty() && !app_config->get("default_action_on_dirty_project").empty()) - m_values.erase(it); // we shouldn't change value, if this parameter was selected, and then deselected - } - #if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application if (m_values.find("always_dark_color_mode") != m_values.end()) wxGetApp().force_sys_colors_update(); @@ -629,11 +667,82 @@ void PreferencesDialog::accept(wxEvent&) wxGetApp().force_menu_update(); #endif //_MSW_DARK_MODE #endif // _WIN32 - if (m_settings_layout_changed) - ;// application will be recreated after Preference dialog will be destroyed - else - // Nothify the UI to update itself from the ini file. - wxGetApp().update_ui_from_settings(); + + wxGetApp().update_ui_from_settings(); + clear_cache(); +} + +void PreferencesDialog::revert(wxEvent&) +{ + auto app_config = get_app_config(); + + bool save_app_config = false; + if (m_custom_toolbar_size != atoi(app_config->get("custom_toolbar_size").c_str())) { + app_config->set("custom_toolbar_size", (boost::format("%d") % m_custom_toolbar_size).str()); + m_icon_size_slider->SetValue(m_custom_toolbar_size); + save_app_config |= true; + } + if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) { + app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0"); + save_app_config |= true; + + m_optgroup_gui->set_value("use_custom_toolbar_size", m_use_custom_toolbar_size); + m_icon_size_sizer->ShowItems(m_use_custom_toolbar_size); + refresh_og(m_optgroup_gui); + } + if (save_app_config) + app_config->save(); + + + for (auto value : m_values) { + bool reverted = false; + const std::string& key = value.first; + + if (key == "default_action_on_dirty_project") { + m_optgroup_general->set_value(key, app_config->get(key).empty()); + continue; + } + if (key == "default_action_on_close_application" || key == "default_action_on_select_preset" || key == "default_action_on_new_project") { + m_optgroup_general->set_value(key, app_config->get(key) == "none"); + continue; + } + if (key == "notify_release") { + m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key))); + continue; + } + if (key == "old_settings_layout_mode") { + m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "new_settings_layout_mode") { + m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "dlg_settings_layout_mode") { + m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + + for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui +#ifdef _WIN32 + , m_optgroup_dark_mode +#endif // _WIN32 +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render +#endif // ENABLE_ENVIRONMENT_MAP + }) { + if (opt_group->set_value(key, app_config->get(key) == "1")) + break; + } + if (key == "tabs_as_menu") { + m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1"); + refresh_og(m_optgroup_gui); + continue; + } + } + + clear_cache(); + EndModal(wxID_CANCEL); } void PreferencesDialog::msw_rescale() @@ -669,6 +778,19 @@ void PreferencesDialog::layout() Refresh(); } +void PreferencesDialog::clear_cache() +{ + m_values.clear(); + m_custom_toolbar_size = -1; +} + +void PreferencesDialog::refresh_og(std::shared_ptr og) +{ + og->parent()->Layout(); + tabs->Layout(); + this->layout(); +} + void PreferencesDialog::create_icon_size_slider() { const auto app_config = get_app_config(); @@ -695,14 +817,14 @@ void PreferencesDialog::create_icon_size_slider() if (!isOSX) style |= wxSL_LABELS | wxSL_AUTOTICKS; - auto slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, + m_icon_size_slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, wxDefaultPosition, wxDefaultSize, style); - slider->SetTickFreq(10); - slider->SetPageSize(10); - slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); + m_icon_size_slider->SetTickFreq(10); + m_icon_size_slider->SetPageSize(10); + m_icon_size_slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); - m_icon_size_sizer->Add(slider, 1, wxEXPAND); + m_icon_size_sizer->Add(m_icon_size_slider, 1, wxEXPAND); wxStaticText* val_label{ nullptr }; if (isOSX) { @@ -710,15 +832,18 @@ void PreferencesDialog::create_icon_size_slider() m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em); } - slider->Bind(wxEVT_SLIDER, ([this, slider, val_label](wxCommandEvent e) { - auto val = slider->GetValue(); - m_values["custom_toolbar_size"] = (boost::format("%d") % val).str(); + m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label, app_config](wxCommandEvent e) { + auto val = m_icon_size_slider->GetValue(); + + app_config->set("custom_toolbar_size", (boost::format("%d") % val).str()); + app_config->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); if (val_label) val_label->SetLabelText(wxString::Format("%d", val)); - }), slider->GetId()); + }), m_icon_size_slider->GetId()); - for (wxWindow* win : std::vector{ slider, label, val_label }) { + for (wxWindow* win : std::vector{ m_icon_size_slider, label, val_label }) { if (!win) continue; win->SetFont(wxGetApp().normal_font()); @@ -731,26 +856,6 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { -#ifdef _MSW_DARK_MODE - bool disable_new_layout = wxGetApp().tabs_as_menu(); -#endif - std::vector choices = { _L("Old regular layout with the tab bar"), - _L("New layout, access via settings button in the top menu"), - _L("Settings in non-modal window") }; - - auto app_config = get_app_config(); - int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : - app_config->get("new_settings_layout_mode") == "1" ? 1 : - app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; - -#ifdef _MSW_DARK_MODE - if (disable_new_layout) { - choices = { _L("Old regular layout with the tab bar"), - _L("Settings in non-modal window") }; - selection = app_config->get("dlg_settings_layout_mode") == "1" ? 1 : 0; - } -#endif - wxWindow* parent = m_optgroup_gui->parent(); wxGetApp().UpdateDarkUI(parent); @@ -762,32 +867,35 @@ void PreferencesDialog::create_settings_mode_widget() wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); - int id = 0; - for (const wxString& label : choices) { - wxRadioButton* btn = new wxRadioButton(parent, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id==0 ? wxRB_GROUP : 0); - stb_sizer->Add(btn); - btn->SetValue(id == selection); - - int dlg_id = 2; -#ifdef _MSW_DARK_MODE - if (disable_new_layout) - dlg_id = 1; -#endif - - btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id -#ifdef _MSW_DARK_MODE - , disable_new_layout -#endif - ](wxCommandEvent& ) { - m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; -#ifdef _MSW_DARK_MODE - if (!disable_new_layout) -#endif - m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; - m_values["dlg_settings_layout_mode"] = (id == dlg_id) ? "1" : "0"; + auto app_config = get_app_config(); + std::vector choices = { _L("Old regular layout with the tab bar"), + _L("New layout, access via settings button in the top menu"), + _L("Settings in non-modal window") }; + int id = -1; + auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) { + *rb = new wxRadioButton(parent, wxID_ANY, choices[id], wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0); + stb_sizer->Add(*rb); + (*rb)->SetValue(select); + (*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { + m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; + m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; + m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0"; }); - id++; + }; + + add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get("old_settings_layout_mode") == "1"); + add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1"); + add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1"); + +#ifdef _MSW_DARK_MODE + if (app_config->get("tabs_as_menu") == "1") { + m_rb_new_settings_layout_mode->Hide(); + if (m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } } +#endif std::string opt_key = "settings_layout_mode"; m_blinkers[opt_key] = new BlinkingBitmap(parent); diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index f8b1d1237a..4a82cee003 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -12,6 +12,8 @@ class wxColourPickerCtrl; class wxBookCtrlBase; +class wxSlider; +class wxRadioButton; namespace Slic3r { @@ -39,6 +41,11 @@ class PreferencesDialog : public DPIDialog std::shared_ptr m_optgroup_render; #endif // ENABLE_ENVIRONMENT_MAP wxSizer* m_icon_size_sizer; + wxSlider* m_icon_size_slider {nullptr}; + wxRadioButton* m_rb_old_settings_layout_mode {nullptr}; + wxRadioButton* m_rb_new_settings_layout_mode {nullptr}; + wxRadioButton* m_rb_dlg_settings_layout_mode {nullptr}; + wxColourPickerCtrl* m_sys_colour {nullptr}; wxColourPickerCtrl* m_mod_colour {nullptr}; wxBookCtrlBase* tabs {nullptr}; @@ -48,6 +55,9 @@ class PreferencesDialog : public DPIDialog bool m_seq_top_layer_only_changed{ false }; bool m_recreate_GUI{false}; + int m_custom_toolbar_size{-1}; + bool m_use_custom_toolbar_size{false}; + public: explicit PreferencesDialog(wxWindow* paren); ~PreferencesDialog() = default; @@ -58,6 +68,7 @@ public: void build(); void update_ctrls_alignment(); void accept(wxEvent&); + void revert(wxEvent&); void show(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string()); protected: @@ -65,6 +76,8 @@ protected: void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); } void on_sys_color_changed() override; void layout(); + void clear_cache(); + void refresh_og(std::shared_ptr og); void create_icon_size_slider(); void create_settings_mode_widget(); void create_settings_text_color_widget(); diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index 60739ccb39..27f2e34bc8 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -242,7 +243,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d tmp_path += format(".%1%%2%", get_current_pid(), ".download"); try { - boost::filesystem::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + boost::nowide::fstream file(tmp_path.string(), std::ios::out | std::ios::binary | std::ios::trunc); file.write(body.c_str(), body.size()); file.close(); boost::filesystem::rename(tmp_path, dest_path); diff --git a/t/clean_polylines.t b/t/clean_polylines.t deleted file mode 100644 index 50c6f5bbdb..0000000000 --- a/t/clean_polylines.t +++ /dev/null @@ -1,83 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 6; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; - -{ - my $polyline = Slic3r::Polyline->new( - [0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[0,0], - ); - $polyline->simplify(1); - is_deeply $polyline->pp, [ [0, 0], [2, 0], [2, 2], [0, 2], [0, 0] ], 'Douglas-Peucker'; -} - -{ - my $polyline = Slic3r::Polyline->new( - [0,0], [50,50], [100,0], [125,-25], [150,50], - ); - $polyline->simplify(25); - is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; -} - -{ - my $gear = Slic3r::Polygon->new_scale( - [144.9694,317.1543], [145.4181,301.5633], [146.3466,296.921], [131.8436,294.1643], [131.7467,294.1464], - [121.7238,291.5082], [117.1631,290.2776], [107.9198,308.2068], [100.1735,304.5101], [104.9896,290.3672], - [106.6511,286.2133], [93.453,279.2327], [81.0065,271.4171], [67.7886,286.5055], [60.7927,280.1127], - [69.3928,268.2566], [72.7271,264.9224], [61.8152,253.9959], [52.2273,242.8494], [47.5799,245.7224], - [34.6577,252.6559], [30.3369,245.2236], [42.1712,236.3251], [46.1122,233.9605], [43.2099,228.4876], - [35.0862,211.5672], [33.1441,207.0856], [13.3923,212.1895], [10.6572,203.3273], [6.0707,204.8561], - [7.2775,204.4259], [29.6713,196.3631], [25.9815,172.1277], [25.4589,167.2745], [19.8337,167.0129], - [5.0625,166.3346], [5.0625,156.9425], [5.3701,156.9282], [21.8636,156.1628], [25.3713,156.4613], - [25.4243,155.9976], [29.3432,155.8157], [30.3838,149.3549], [26.3596,147.8137], [27.1085,141.2604], - [29.8466,126.8337], [24.5841,124.9201], [10.6664,119.8989], [13.4454,110.9264], [33.1886,116.0691], - [38.817,103.1819], [45.8311,89.8133], [30.4286,76.81], [35.7686,70.0812], [48.0879,77.6873], - [51.564,81.1635], [61.9006,69.1791], [72.3019,58.7916], [60.5509,42.5416], [68.3369,37.1532], - [77.9524,48.1338], [80.405,52.2215], [92.5632,44.5992], [93.0123,44.3223], [106.3561,37.2056], - [100.8631,17.4679], [108.759,14.3778], [107.3148,11.1283], [117.0002,32.8627], [140.9109,27.3974], - [145.7004,26.4994], [145.1346,6.1011], [154.502,5.4063], [156.9398,25.6501], [171.0557,26.2017], - [181.3139,27.323], [186.2377,27.8532], [191.6031,8.5474], [200.6724,11.2756], [197.2362,30.2334], - [220.0789,39.1906], [224.3261,41.031], [236.3506,24.4291], [243.6897,28.6723], [234.2956,46.7747], - [245.6562,55.1643], [257.2523,65.0901], [261.4374,61.5679], [273.1709,52.8031], [278.555,59.5164], - [268.4334,69.8001], [264.1615,72.3633], [268.2763,77.9442], [278.8488,93.5305], [281.4596,97.6332], - [286.4487,95.5191], [300.2821,90.5903], [303.4456,98.5849], [286.4523,107.7253], [293.7063,131.1779], - [294.9748,135.8787], [314.918,133.8172], [315.6941,143.2589], [300.9234,146.1746], [296.6419,147.0309], - [297.1839,161.7052], [296.6136,176.3942], [302.1147,177.4857], [316.603,180.3608], [317.1658,176.7341], - [315.215,189.6589], [315.1749,189.6548], [294.9411,187.5222], [291.13,201.7233], [286.2615,215.5916], - [291.1944,218.2545], [303.9158,225.1271], [299.2384,233.3694], [285.7165,227.6001], [281.7091,225.1956], - [273.8981,237.6457], [268.3486,245.2248], [267.4538,246.4414], [264.8496,250.0221], [268.6392,253.896], - [278.5017,265.2131], [272.721,271.4403], [257.2776,258.3579], [234.4345,276.5687], [242.6222,294.8315], - [234.9061,298.5798], [227.0321,286.2841], [225.2505,281.8301], [211.5387,287.8187], [202.3025,291.0935], - [197.307,292.831], [199.808,313.1906], [191.5298,315.0787], [187.3082,299.8172], [186.4201,295.3766], - [180.595,296.0487], [161.7854,297.4248], [156.8058,297.6214], [154.3395,317.8592], - ); - - my $num_points = scalar @$gear; - my $simplified = $gear->simplify(1000); - ok @$simplified == 1, 'gear simplified to a single polygon'; - ###note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); - ok @{$simplified->[0]} < $num_points, 'gear was further simplified using Douglas-Peucker'; -} - -{ - - my $hole_in_square = Slic3r::Polygon->new( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ); - my $simplified = $hole_in_square->simplify(2); - is scalar(@$simplified), 1, 'hole simplification returns one polygon'; - ok $simplified->[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon'; -} - diff --git a/t/combineinfill.t b/t/combineinfill.t index a19e817a14..ebb4304196 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -107,60 +107,4 @@ plan tests => 8; 'infill combination is idempotent'; } -# the following needs to be adapted to the new API -if (0) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $config->set('top_solid_layers', 0); - $config->set('infill_every_layers', 6); - $config->set('layer_height', 0.06); - $config->set('perimeters', 1); - - my $test = sub { - my ($shift) = @_; - - my $self = Slic3r::Test::init_print('20mm_cube', config => $config); - - $shift /= &Slic3r::SCALING_FACTOR; - my $scale = 4; # make room for fat infill lines with low layer height - - # Put a slope on the box's sides by shifting x and y coords by $tilt * (z / boxheight). - # The test here is to put such a slight slope on the walls that it should - # not trigger any extra fill on fill layers that should be empty when - # combine infill is enabled. - $_->[0] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; - $_->[1] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; - $_ = [$_->[0]*$scale, $_->[1]*$scale, $_->[2]] for @{$self->objects->[0]->meshes->[0]->vertices}; - - # copy of Print::export_gcode() up to the point - # after fill surfaces are combined - $_->slice for @{$self->objects}; - $_->make_perimeters for @{$self->objects}; - $_->detect_surfaces_type for @{$self->objects}; - $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; - $_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; - $_->discover_horizontal_shells for @{$self->objects}; - $_->combine_infill for @{$self->objects}; - - # Only layers with id % 6 == 0 should have fill. - my $spurious_infill = 0; - foreach my $layer (map @{$_->layers}, @{$self->objects}) { - ++$spurious_infill if ($layer->id % 6 && grep @{$_->fill_surfaces} > 0, @{$layer->regions}); - } - - $spurious_infill -= scalar(@{$self->objects->[0]->layers} - 1) % 6; - - fail "spurious fill surfaces found on layers that should have none (walls " . sprintf("%.4f", Slic3r::Geometry::rad2deg(atan2($shift, 20/&Slic3r::SCALING_FACTOR))) . " degrees off vertical)" - unless $spurious_infill == 0; - 1; - }; - - # Test with mm skew offsets for the top of the 20mm-high box - for my $shift (0, 0.0001, 1) { - ok $test->($shift), "no spurious fill surfaces with box walls " . sprintf("%.4f",Slic3r::Geometry::rad2deg(atan2($shift, 20))) . " degrees off of vertical"; - } -} - __END__ diff --git a/t/config.t b/t/config.t deleted file mode 100644 index f4a1867de6..0000000000 --- a/t/config.t +++ /dev/null @@ -1,20 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('perimeter_extrusion_width', '250%'); - ok $config->validate, 'percent extrusion width is validated'; -} - -__END__ diff --git a/t/cooling.t b/t/cooling.t deleted file mode 100644 index e46cfa2f77..0000000000 --- a/t/cooling.t +++ /dev/null @@ -1,214 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 14; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(none all); -use Slic3r; -use Slic3r::Test; - -my $gcodegen; -sub buffer { - my $config = shift; - if (defined($config)) { - $config = $config->clone(); - } else { - $config = Slic3r::Config->new; - } - my $config_override = shift; - foreach my $key (keys %{$config_override}) { - $config->set($key, ${$config_override}{$key}); - } - - my $print_config = Slic3r::Config::Print->new; - $print_config->apply_dynamic($config); - - $gcodegen = Slic3r::GCode->new; - $gcodegen->apply_print_config($print_config); - $gcodegen->set_layer_count(10); - - my $extruders_ref = shift; - $extruders_ref = [ 0 ] if !defined $extruders_ref; - $gcodegen->set_extruders($extruders_ref); - return Slic3r::GCode::CoolingBuffer->new($gcodegen); -} - -my $gcode1 = "G1 X100 E1 F3000\n"; -my $print_time1 = 100 / (3000 / 60); # 2 sec -my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n"; -my $print_time2 = 2 * $print_time1; # 4 sec - -my $config = Slic3r::Config::new_from_defaults; -# Default cooling settings. -$config->set('bridge_fan_speed', [ 100 ]); -$config->set('cooling', [ 1 ]); -$config->set('fan_always_on', [ 0 ]); -$config->set('fan_below_layer_time', [ 60 ]); -$config->set('max_fan_speed', [ 100 ]); -$config->set('min_print_speed', [ 10 ]); -$config->set('slowdown_below_layer_time', [ 5 ]); -# Default print speeds. -$config->set('bridge_speed', 60); -$config->set('external_perimeter_speed', '50%'); -$config->set('first_layer_speed', 30); -$config->set('gap_fill_speed', 20); -$config->set('infill_speed', 80); -$config->set('perimeter_speed', 60); -$config->set('small_perimeter_speed', 15); -$config->set('solid_infill_speed', 20); -$config->set('top_solid_infill_speed', 15); -$config->set('max_print_speed', 80); -# Override for tests. -$config->set('disable_fan_first_layers', [ 0 ]); - -{ - my $gcode_src = "G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1"; - # Print time of $gcode. - my $print_time = 100 / (3000 / 60); - my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 0.999 ] }); - my $gcode = $buffer->process_layer($gcode_src, 0); - like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; -} - -{ - my $gcode_src = - "G1 X50 F2500\n" . - "G1 F3000;_EXTRUDE_SET_SPEED\n" . - "G1 X100 E1\n" . - ";_EXTRUDE_END\n" . - "G1 E4 F400", - # Print time of $gcode. - my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60); - my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 1.001 ] }); - my $gcode = $buffer->process_layer($gcode_src, 0); - unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; - like $gcode, qr/F2500/, 'speed is not altered for travel moves'; - like $gcode, qr/F400/, 'speed is not altered for extruder-only moves'; -} - -{ - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time1 * 0.88 ], - 'slowdown_below_layer_time' => [ $print_time1 * 0.99 ] - }); - my $gcode = $buffer->process_layer($gcode1, 0); - unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; -} - -{ - my $gcode .= buffer($config, { 'slowdown_below_layer_time', [ $print_time2 * 0.99 ] })->process_layer($gcode2, 0); - like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at the same Z'; -} - -{ - # use an elapsed time which is < the threshold but greater than it when summed twice - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time2 * 0.65], - 'slowdown_below_layer_time' => [ $print_time2 * 0.7 ] - }); - my $gcode = $buffer->process_layer($gcode2, 0) . - $buffer->process_layer($gcode2, 1); - unlike $gcode, qr/M106/, 'fan is not activated on all objects printing at different Z'; -} - -{ - # use an elapsed time which is < the threshold even when summed twice - my $buffer = buffer($config, { - 'fan_below_layer_time' => [ $print_time2 + 1 ], - 'slowdown_below_layer_time' => [ $print_time2 + 2 ] - }); - my $gcode = $buffer->process_layer($gcode2, 0) . - $buffer->process_layer($gcode2, 1); - like $gcode, qr/M106/, 'fan is activated on all objects printing at different Z'; -} - -{ - my $buffer = buffer($config, { - 'cooling' => [ 1 , 0 ], - 'fan_below_layer_time' => [ $print_time2 + 1, $print_time2 + 1 ], - 'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ] - }, - [ 0, 1]); - my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0); - like $gcode, qr/^M106/, 'fan is activated for the 1st tool'; - like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('cooling', [ 1 ]); - $config->set('bridge_fan_speed', [ 100 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 0 ]); - $config->set('bridge_speed', 99); - $config->set('top_solid_layers', 1); # internal bridges use solid_infil speed - $config->set('bottom_solid_layers', 1); # internal bridges use solid_infil speed - - my $print = Slic3r::Test::init_print('overhang', config => $config); - my $fan = 0; - my $fan_with_incorrect_speeds = my $fan_with_incorrect_print_speeds = 0; - my $bridge_with_no_fan = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'M106') { - $fan = $args->{S}; - $fan_with_incorrect_speeds++ if $fan != 255; - } elsif ($cmd eq 'M107') { - $fan = 0; - } elsif ($info->{extruding} && $info->{dist_XY} > 0) { - $fan_with_incorrect_print_speeds++ - if ($fan > 0) && ($args->{F} // $self->F) != 60*$config->bridge_speed; - $bridge_with_no_fan++ - if !$fan && ($args->{F} // $self->F) == 60*$config->bridge_speed; - } - }); - ok !$fan_with_incorrect_speeds, 'bridge fan speed is applied correctly'; - ok !$fan_with_incorrect_print_speeds, 'bridge fan is only turned on for bridges'; - ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('cooling', [ 1 ]); - $config->set('fan_below_layer_time', [ 0 ]); - $config->set('slowdown_below_layer_time', [ 10 ]); - $config->set('min_print_speed', [ 0 ]); - $config->set('start_gcode', ''); - $config->set('first_layer_speed', '100%'); - $config->set('external_perimeter_speed', 99); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my @layer_times = (0); # in seconds - my %layer_external = (); # z => 1 - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($info->{dist_Z}) { - push @layer_times, 0; - $layer_external{ $args->{Z} } = 0; - } - $layer_times[-1] += abs($info->{dist_XY} || $info->{dist_E} || $info->{dist_Z} || 0) / ($args->{F} // $self->F) * 60; - if ($args->{F} && $args->{F} == $config->external_perimeter_speed*60) { - $layer_external{ $self->Z }++; - } - } - }); - @layer_times = grep $_, @layer_times; - my $all_below = none { $_ < $config->slowdown_below_layer_time->[0] } @layer_times; - ok $all_below, 'slowdown_below_layer_time is honored'; - - # check that all layers have at least one unaltered external perimeter speed -# my $external = all { $_ > 0 } values %layer_external; -# ok $external, 'slowdown_below_layer_time does not alter external perimeters'; -} - -__END__ diff --git a/t/custom_gcode.t b/t/custom_gcode.t deleted file mode 100644 index 1bb52b618e..0000000000 --- a/t/custom_gcode.t +++ /dev/null @@ -1,210 +0,0 @@ -use Test::More tests => 38; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print('2x20x10', config => $conf); - - my $last_move_was_z_change = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($last_move_was_z_change && $cmd ne $config->layer_gcode) { - fail 'custom layer G-code was not applied after Z change'; - } - if (!$last_move_was_z_change && $cmd eq $config->layer_gcode) { - fail 'custom layer G-code was not applied after Z change'; - } - - $last_move_was_z_change = (defined $info->{dist_Z} && $info->{dist_Z} > 0); - }); - - 1; - }; - - $config->set('start_gcode', '_MY_CUSTOM_START_GCODE_'); # to avoid dealing with the nozzle lift in start G-code - $config->set('layer_gcode', '_MY_CUSTOM_LAYER_GCODE_'); - ok $test->(), "custom layer G-code is applied after Z move and before other moves"; -} - -#========================================================== - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode'); - $config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n"); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - my $output_file = $print->print->output_filepath; - my ($t, $h) = map $config->$_, qw(travel_speed layer_height); - ok $output_file =~ /ts_${t}_/, 'print config options are replaced in output filename'; - ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename'; - - my $gcode = Slic3r::Test::gcode($print); - ok $gcode =~ /TRAVEL:$t/, 'print config options are replaced in custom G-code'; - ok $gcode =~ /HEIGHT:$h/, 'region config options are replaced in custom G-code'; -} - -{ - my $config = Slic3r::Config->new; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('extruder', 2); - $config->set('first_layer_temperature', [200,205]); - - { - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $gcode = Slic3r::Test::gcode($print); - ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for non-zero yet single extruder'; - ok $gcode !~ /M104 S\d+ T0/, 'unused extruder correctly ignored'; - } - - $config->set('infill_extruder', 1); - { - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $gcode = Slic3r::Test::gcode($print); - ok $gcode =~ /M104 S200 T0/, 'temperature set correctly for first extruder'; - ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder'; - } - - my @start_gcode = (qq! -;__temp0:[first_layer_temperature_0]__ -;__temp1:[first_layer_temperature_1]__ -;__temp2:[first_layer_temperature_2]__ - !, qq! -;__temp0:{first_layer_temperature[0]}__ -;__temp1:{first_layer_temperature[1]}__ -;__temp2:{first_layer_temperature[2]}__ - !); - my @syntax_description = (' (legacy syntax)', ' (new syntax)'); - for my $i (0, 1) { - $config->set('start_gcode', $start_gcode[$i]); - { - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $gcode = Slic3r::Test::gcode($print); - # we use the [infill_extruder] placeholder to make sure this test doesn't - # catch a false positive caused by the unparsed start G-code option itself - # being embedded in the G-code - ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i]; - ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i]; - ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i]; - } - } - - $config->set('start_gcode', qq! -;substitution:{if infill_extruder==1}extruder1 - {elsif infill_extruder==2}extruder2 - {else}extruder3{endif} - !); - { - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $gcode = Slic3r::Test::gcode($print); - ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned'; - } -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('before_layer_gcode', ';BEFORE [layer_num]'); - $config->set('layer_gcode', ';CHANGE [layer_num]'); - $config->set('support_material', 1); - $config->set('layer_height', 0.2); - my $print = Slic3r::Test::init_print('overhang', config => $config); - my $gcode = Slic3r::Test::gcode($print); - - my @before = (); - my @change = (); - foreach my $line (split /\R+/, $gcode) { - if ($line =~ /;BEFORE (\d+)/) { - push @before, $1; - } elsif ($line =~ /;CHANGE (\d+)/) { - push @change, $1; - fail 'inconsistent layer_num before and after layer change' - if $1 != $before[-1]; - } - } - is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes'; - ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change), - 'layer_num grows continously'; # i.e. no duplicates or regressions -} - -{ - my $config = Slic3r::Config->new; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6,0.6]); - $config->set('start_gcode', qq! -;substitution:{if infill_extruder==1}if block - {elsif infill_extruder==2}elsif block 1 - {elsif infill_extruder==3}elsif block 2 - {elsif infill_extruder==4}elsif block 3 - {else}endif block{endif} - !); - my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block'); - for my $i (1,2,3,4,5) { - $config->set('infill_extruder', $i); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $gcode = Slic3r::Test::gcode($print); - my $found_other = 0; - for my $j (1,2,3,4,5) { - next if $i == $j; - $found_other = 1 if $gcode =~ /substitution:$returned[$j]/; - } - ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned'; - ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned'; - } -} - -{ - my $config = Slic3r::Config->new; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('start_gcode', - ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' . - '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' . - '{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end'); - for my $i (1,2,3) { - $config->set('infill_extruder', $i); - for my $j (1,2) { - $config->set('perimeter_extruder', $j); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $gcode = Slic3r::Test::gcode($print); - ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned"; - } - } -} - -{ - my $config = Slic3r::Config->new; - $config->set('start_gcode', - ';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end'); - for my $printer_name ("MK2", "MK3", "MK1") { - $config->set('notes', $printer_name); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $gcode = Slic3r::Test::gcode($print); - ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched"; - } -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('complete_objects', 1); - $config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_'); - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3); - my $gcode = Slic3r::Test::gcode($print); - is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly'; -} - -__END__ diff --git a/t/flow.t b/t/flow.t deleted file mode 100644 index 50c4916049..0000000000 --- a/t/flow.t +++ /dev/null @@ -1,83 +0,0 @@ -use Test::More tests => 6; -use strict; -use warnings; - -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(scale PI); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 1); - $config->set('brim_width', 2); - $config->set('perimeters', 3); - $config->set('fill_density', 0.4); - $config->set('bottom_solid_layers', 1); - $config->set('first_layer_extrusion_width', 2); - $config->set('first_layer_height', $config->layer_height); - $config->set('filament_diameter', [ 3.0 ]); - $config->set('nozzle_diameter', [ 0.5 ]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my @E_per_mm = (); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($self->Z == $config->layer_height) { # only consider first layer - if ($info->{extruding} && $info->{dist_XY} > 0) { - push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; - } - } - }); - my $E_per_mm_avg = sum(@E_per_mm) / @E_per_mm; - # allow some tolerance because solid rectilinear infill might be adjusted/stretched - ok !(defined first { abs($_ - $E_per_mm_avg) > 0.015 } @E_per_mm), - 'first_layer_extrusion_width applies to everything on first layer'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('bridge_speed', 99); - $config->set('bridge_flow_ratio', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - - my $test = sub { - my $print = Slic3r::Test::init_print('overhang', config => $config); - my @E_per_mm = (); - Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - if (($args->{F} // $self->F) == $config->bridge_speed*60) { - push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; - } - } - }); - my $expected_mm3_per_mm = ($config->nozzle_diameter->[0]**2) * PI/4 * $config->bridge_flow_ratio; - my $expected_E_per_mm = $expected_mm3_per_mm / ((($config->filament_diameter->[0]/2)**2)*PI); - ok !(defined first { abs($_ - $expected_E_per_mm) > 0.01 } @E_per_mm), - 'expected flow when using bridge_flow_ratio = ' . $config->bridge_flow_ratio; - }; - - $config->set('bridge_flow_ratio', 0.5); - $test->(); - $config->set('bridge_flow_ratio', 2); - $test->(); - $config->set('extrusion_width', 0.4); - $config->set('bridge_flow_ratio', 1); - $test->(); - $config->set('bridge_flow_ratio', 0.5); - $test->(); - $config->set('bridge_flow_ratio', 2); - $test->(); -} - -__END__ diff --git a/t/gaps.t b/t/gaps.t deleted file mode 100644 index 2594ac0870..0000000000 --- a/t/gaps.t +++ /dev/null @@ -1,59 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Geometry qw(PI scale unscale convex_hull); -use Slic3r::Surface ':types'; -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeter_speed', 66); - $config->set('external_perimeter_speed', 66); - $config->set('small_perimeter_speed', 66); - $config->set('gap_fill_speed', 99); - $config->set('perimeters', 1); - $config->set('cooling', [ 0 ]); # to prevent speeds from being altered - $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered - $config->set('perimeter_extrusion_width', 0.35); - $config->set('first_layer_extrusion_width', 0.35); - - my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); - my @perimeter_points = (); - my $last = ''; # perimeter | gap - my $gap_fills_outside_last_perimeters = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && $info->{dist_XY} > 0) { - my $F = $args->{F} // $self->F; - my $point = Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}); - if ($F == $config->perimeter_speed*60) { - if ($last eq 'gap') { - @perimeter_points = (); - } - push @perimeter_points, $point; - $last = 'perimeter'; - } elsif ($F == $config->gap_fill_speed*60) { - my $convex_hull = convex_hull(\@perimeter_points); - if (!$convex_hull->contains_point($point)) { - $gap_fills_outside_last_perimeters++; - } - - $last = 'gap'; - } - } - }); - is $gap_fills_outside_last_perimeters, 0, 'gap fills are printed before leaving islands'; -} - -__END__ diff --git a/t/gcode.t b/t/gcode.t index b95505e436..902c40b834 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 24; +use Test::More tests => 23; use strict; use warnings; @@ -13,13 +13,6 @@ use Slic3r; use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Test; -{ - my $gcodegen = Slic3r::GCode->new(); - $gcodegen->set_layer_count(1); - $gcodegen->set_origin(Slic3r::Pointf->new(10, 10)); - is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly'; -} - { my $config = Slic3r::Config::new_from_defaults; $config->set('wipe', [1]); diff --git a/t/loops.t b/t/loops.t deleted file mode 100644 index e662469cad..0000000000 --- a/t/loops.t +++ /dev/null @@ -1,57 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan skip_all => 'temporarily disabled'; -plan tests => 4; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Test; - -{ - # We only need to slice at one height, so we'll build a non-manifold mesh - # that still produces complete loops at that height. Triangular walls are - # enough for this purpose. - # Basically we want to check what happens when three concentric loops happen - # to be at the same height, the two external ones being ccw and the other being - # a hole, thus cw. - my (@vertices, @facets) = (); - Slic3r::Test::add_facet($_, \@vertices, \@facets) for - # external surface below the slicing Z - [ [0,0,0], [20,0,10], [0,0,10] ], - [ [20,0,0], [20,20,10], [20,0,10] ], - [ [20,20,0], [0,20,10], [20,20,10] ], - [ [0,20,0], [0,0,10], [0,20,10] ], - - # external insetted surface above the slicing Z - [ [2,2,10], [18,2,10], [2,2,20] ], - [ [18,2,10], [18,18,10], [18,2,20] ], - [ [18,18,10], [2,18,10], [18,18,20] ], - [ [2,18,10], [2,2,10], [2,18,20] ], - - # insetted hole below the slicing Z - [ [15,5,0], [5,5,10], [15,5,10] ], - [ [15,15,0], [15,5,10], [15,15,10] ], - [ [5,15,0], [15,15,10], [5,15,10] ], - [ [5,5,0], [5,15,10], [5,5,10] ]; - - my $mesh = Slic3r::TriangleMesh->new; - $mesh->ReadFromPerl(\@vertices, \@facets); - $mesh->analyze; - my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets; - my $loops = Slic3r::TriangleMesh::make_loops(\@lines); - is scalar(@$loops), 3, 'correct number of loops detected'; - is scalar(grep $_->is_counter_clockwise, @$loops), 2, 'correct number of ccw loops detected'; - - my @surfaces = Slic3r::Layer::Region::_merge_loops($loops, 0); - is scalar(@surfaces), 1, 'one surface detected'; - is scalar(@{$surfaces[0]->expolygon})-1, 1, 'surface has one hole'; -} - -__END__ diff --git a/t/print.t b/t/print.t deleted file mode 100644 index 2144e80c1e..0000000000 --- a/t/print.t +++ /dev/null @@ -1,65 +0,0 @@ -use Test::More tests => 6; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Geometry qw(unscale X Y); -use Slic3r::Test; - -{ - my $config = Slic3r::Config::new_from_defaults; - my $print_center = [100,100]; - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, print_center => $print_center); - my @extrusion_points = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { - push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); - } - }); - my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@extrusion_points); - my $center = $bb->center; - ok abs(unscale($center->[X]) - $print_center->[X]) < 0.005, 'print is centered around print_center (X)'; - ok abs(unscale($center->[Y]) - $print_center->[Y]) < 0.005, 'print is centered around print_center (Y)'; -} - -{ - # this represents the aggregate config from presets - my $config = Slic3r::Config::new_from_defaults; - # Define 4 extruders. - $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); - - # user adds one object to the plater - my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); - - # user sets a per-region option - my $model2 = $model->clone; - $model2->get_object(0)->config->set('fill_density', 100); - $print->apply($model2, $config); - - is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config'; - - # user exports G-code, thus the default config is reapplied - $model2->get_object(0)->config->erase('fill_density'); - $print->apply($model2, $config); - - is $print->print->regions->[0]->config->fill_density, 20, 'region config is resetted'; - - # user assigns object extruders - $model2->get_object(0)->config->set('extruder', 3); - $model2->get_object(0)->config->set('perimeter_extruder', 2); - $print->apply($model2, $config); - - is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded'; - is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders'; -} - -__END__ diff --git a/t/thin.t b/t/thin.t deleted file mode 100644 index 50e7abc950..0000000000 --- a/t/thin.t +++ /dev/null @@ -1,185 +0,0 @@ -use Test::More tests => 23; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use List::Util qw(first sum none); -use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon Y); -use Slic3r::Test; - -# Disable this until a more robust implementation is provided. It currently -# fails on Linux 32bit because some spurious extrudates are generated. -if (0) { - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.2); - $config->set('first_layer_height', $config->layer_height); - $config->set('extrusion_width', 0.5); - $config->set('first_layer_extrusion_width', '200%'); # check this one too - $config->set('skirts', 0); - $config->set('thin_walls', 1); - - my $print = Slic3r::Test::init_print('gt2_teeth', config => $config); - - my %extrusion_paths = (); # Z => count of continuous extrusions - my $extruding = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($info->{extruding} && $info->{dist_XY}) { - if (!$extruding) { - $extrusion_paths{$self->Z} //= 0; - $extrusion_paths{$self->Z}++; - } - $extruding = 1; - } else { - $extruding = 0; - } - } - }); - - ok !(first { $_ != 3 } values %extrusion_paths), - 'no superfluous thin walls are generated for toothed profile'; -} - -{ - my $square = Slic3r::Polygon->new_scale( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], - ); - my $hole_in_square = Slic3r::Polygon->new_scale( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], - ); - my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - my $res = $expolygon->medial_axis(scale 40, scale 0.5); - is scalar(@$res), 1, 'medial axis of a square shape is a single path'; - isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline'; - ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop'; - ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length, - 'medial axis loop has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 200], - [100, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; - - $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 200], - [105, 200], # extra point in the short side - [100, 200], - )); - my $res2 = $expolygon->medial_axis(scale 1, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow rectangle with an extra vertex is still a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has still a reasonable length'; - ok !(grep { abs($_ - scale 150) < scaled_epsilon } map $_->[Y], map @$_, @$res2), "extra vertices don't influence medial axis"; -} - -{ - my $expolygon = Slic3r::ExPolygon->new( - Slic3r::Polygon->new([1185881,829367],[1421988,1578184],[1722442,2303558],[2084981,2999998],[2506843,3662186],[2984809,4285086],[3515250,4863959],[4094122,5394400],[4717018,5872368],[5379210,6294226],[6075653,6656769],[6801033,6957229],[7549842,7193328],[8316383,7363266],[9094809,7465751],[9879211,7500000],[10663611,7465750],[11442038,7363265],[12208580,7193327],[12957389,6957228],[13682769,6656768],[14379209,6294227],[15041405,5872366],[15664297,5394401],[16243171,4863960],[16758641,4301424],[17251579,3662185],[17673439,3000000],[18035980,2303556],[18336441,1578177],[18572539,829368],[18750748,0],[19758422,0],[19727293,236479],[19538467,1088188],[19276136,1920196],[18942292,2726179],[18539460,3499999],[18070731,4235755],[17539650,4927877],[16950279,5571067],[16307090,6160437],[15614974,6691519],[14879209,7160248],[14105392,7563079],[13299407,7896927],[12467399,8159255],[11615691,8348082],[10750769,8461952],[9879211,8500000],[9007652,8461952],[8142729,8348082],[7291022,8159255],[6459015,7896927],[5653029,7563079],[4879210,7160247],[4143447,6691519],[3451331,6160437],[2808141,5571066],[2218773,4927878],[1687689,4235755],[1218962,3499999],[827499,2748020],[482284,1920196],[219954,1088186],[31126,236479],[0,0],[1005754,0]), - ); - my $res = $expolygon->medial_axis(scale 1.324888, scale 0.25); - is scalar(@$res), 1, 'medial axis of a semicircumference is a single line'; - - # check whether turns are all CCW or all CW - my @lines = @{$res->[0]->lines}; - my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines; - ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles), - 'all medial axis segments of a semicircumference have the same orientation'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [112, 200], - [108, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [100, 100], - [120, 100], - [120, 180], - [200, 180], - [200, 200], - [100, 200], - )); - my $res = $expolygon->medial_axis(scale 20, scale 0.5); - is scalar(@$res), 1, 'medial axis of a L shape is a single polyline'; - my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends - ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [-203064906,-51459966],[-219312231,-51459966],[-219335477,-51459962],[-219376095,-51459962],[-219412047,-51459966], - [-219572094,-51459966],[-219624814,-51459962],[-219642183,-51459962],[-219656665,-51459966],[-220815482,-51459966], - [-220815482,-37738966],[-221117540,-37738966],[-221117540,-51762024],[-203064906,-51762024], - )); - my $polylines = $expolygon->medial_axis(819998, 102499.75); - - my $perimeter = $expolygon->contour->split_at_first_point->length; - ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length'; -} - -{ - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( - [50, 100], - [1000, 102], - [50, 104], - )); - my $res = $expolygon->medial_axis(scale 4, scale 0.5); - is scalar(@$res), 1, 'medial axis of a narrow triangle is a single line'; - ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; -} - -{ - # GH #2474 - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808] - )); - my $polylines = $expolygon->medial_axis(1871238, 500000); - is scalar(@$polylines), 1, 'medial axis is a single polyline'; - my $polyline = $polylines->[0]; - - my $expected_y = $expolygon->bounding_box->center->y; #;; - ok abs(sum(map $_->y, @$polyline) / @$polyline - $expected_y) < scaled_epsilon, #,, - 'medial axis is horizontal and is centered'; - - # order polyline from left to right - $polyline->reverse if $polyline->first_point->x > $polyline->last_point->x; - - my $polyline_bb = $polyline->bounding_box; - is $polyline->first_point->x, $polyline_bb->x_min, 'expected x_min'; - is $polyline->last_point->x, $polyline_bb->x_max, 'expected x_max'; - - is_deeply [ map $_->x, @$polyline ], [ sort map $_->x, @$polyline ], - 'medial axis is not self-overlapping'; -} - -__END__ diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 9e039c913b..4e8821287f 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -3,11 +3,14 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp test_avoid_crossing_perimeters.cpp test_bridges.cpp + test_cooling.cpp + test_custom_gcode.cpp test_data.cpp test_data.hpp test_extrusion_entity.cpp test_fill.cpp test_flow.cpp + test_gaps.cpp test_gcode.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp @@ -19,6 +22,7 @@ add_executable(${_TEST_NAME}_tests test_printobject.cpp test_skirt_brim.cpp test_support_material.cpp + test_thin_walls.cpp test_trianglemesh.cpp ) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp new file mode 100644 index 0000000000..f743783b11 --- /dev/null +++ b/tests/fff_print/test_cooling.cpp @@ -0,0 +1,274 @@ +#include + +#include +#include + +#include "test_data.hpp" // get access to init_print, etc + +#include "libslic3r/Config.hpp" +#include "libslic3r/GCode.hpp" +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/GCode/CoolingBuffer.hpp" +#include "libslic3r/libslic3r.h" + +using namespace Slic3r; + +std::unique_ptr make_cooling_buffer( + GCode &gcode, + const DynamicPrintConfig &config = DynamicPrintConfig{}, + const std::vector &extruder_ids = { 0 }) +{ + PrintConfig print_config; + print_config.apply(config, true); // ignore_nonexistent + gcode.apply_print_config(print_config); + gcode.set_layer_count(10); + gcode.writer().set_extruders(extruder_ids); + gcode.writer().set_extruder(0); + return std::make_unique(gcode); +} + +SCENARIO("Cooling unit tests", "[Cooling]") { + const std::string gcode1 = "G1 X100 E1 F3000\n"; + // 2 sec + const double print_time1 = 100. / (3000. / 60.); + const std::string gcode2 = gcode1 + "G1 X0 E1 F3000\n"; + // 4 sec + const double print_time2 = 2. * print_time1; + + auto config = DynamicPrintConfig::full_print_config_with({ + // Default cooling settings. + { "bridge_fan_speed", "100" }, + { "cooling", "1" }, + { "fan_always_on", "0" }, + { "fan_below_layer_time", "60" }, + { "max_fan_speed", "100" }, + { "min_print_speed", "10" }, + { "slowdown_below_layer_time", "5" }, + // Default print speeds. + { "bridge_speed", 60 }, + { "external_perimeter_speed", "50%" }, + { "first_layer_speed", 30 }, + { "gap_fill_speed", 20 }, + { "infill_speed", 80 }, + { "perimeter_speed", 60 }, + { "small_perimeter_speed", 15 }, + { "solid_infill_speed", 20 }, + { "top_solid_infill_speed", 15 }, + { "max_print_speed", 80 }, + // Override for tests. + { "disable_fan_first_layers", "0" } + }); + + WHEN("G-code block 3") { + THEN("speed is not altered when elapsed time is greater than slowdown threshold") { + // Print time of gcode. + const double print_time = 100. / (3000. / 60.); + //FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s. + config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true); + bool speed_not_altered = gcode.find("F3000") != gcode.npos; + REQUIRE(speed_not_altered); + } + } + + WHEN("G-code block 4") { + const std::string gcode_src = + "G1 X50 F2500\n" + "G1 F3000;_EXTRUDE_SET_SPEED\n" + "G1 X100 E1\n" + ";_EXTRUDE_END\n" + "G1 E4 F400"; + // Print time of gcode. + const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.); + config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode_src, 0, true); + THEN("speed is altered when elapsed time is lower than slowdown threshold") { + bool speed_is_altered = gcode.find("F3000") == gcode.npos; + REQUIRE(speed_is_altered); + } + THEN("speed is not altered for travel moves") { + bool speed_not_altered = gcode.find("F2500") != gcode.npos; + REQUIRE(speed_not_altered); + } + THEN("speed is not altered for extruder-only moves") { + bool speed_not_altered = gcode.find("F400") != gcode.npos; + REQUIRE(speed_not_altered); + } + } + + WHEN("G-code block 1") { + THEN("fan is not activated when elapsed time is greater than fan threshold") { + config.set_deserialize_strict({ + { "fan_below_layer_time" , int(print_time1 * 0.88) }, + { "slowdown_below_layer_time" , int(print_time1 * 0.99) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode1, 0, true); + bool fan_not_activated = gcode.find("M106") == gcode.npos; + REQUIRE(fan_not_activated); + } + } + WHEN("G-code block 1 with two extruders") { + config.set_deserialize_strict({ + { "cooling", "1, 0" }, + { "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } }, + { "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 }); + std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true); + THEN("fan is activated for the 1st tool") { + bool ok = gcode.find("M106") == 0; + REQUIRE(ok); + } + THEN("fan is disabled for the 2nd tool") { + bool ok = gcode.find("\nM107") > 0; + REQUIRE(ok); + } + } + WHEN("G-code block 2") { + THEN("slowdown is computed on all objects printing at the same Z") { + config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + std::string gcode = buffer->process_layer(gcode2, 0, true); + bool ok = gcode.find("F3000") != gcode.npos; + REQUIRE(ok); + } + THEN("fan is not activated on all objects printing at different Z") { + config.set_deserialize_strict({ + { "fan_below_layer_time", int(print_time2 * 0.65) }, + { "slowdown_below_layer_time", int(print_time2 * 0.7) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + // use an elapsed time which is < the threshold but greater than it when summed twice + std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); + bool fan_not_activated = gcode.find("M106") == gcode.npos; + REQUIRE(fan_not_activated); + } + THEN("fan is activated on all objects printing at different Z") { + // use an elapsed time which is < the threshold even when summed twice + config.set_deserialize_strict({ + { "fan_below_layer_time", int(print_time2 + 1) }, + { "slowdown_below_layer_time", int(print_time2 + 1) } + }); + GCode gcodegen; + auto buffer = make_cooling_buffer(gcodegen, config); + // use an elapsed time which is < the threshold but greater than it when summed twice + std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); + bool fan_activated = gcode.find("M106") != gcode.npos; + REQUIRE(fan_activated); + } + } +} + +SCENARIO("Cooling integration tests", "[Cooling]") { + GIVEN("overhang") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "cooling", { 1 } }, + { "bridge_fan_speed", { 100 } }, + { "fan_below_layer_time", { 0 } }, + { "slowdown_below_layer_time", { 0 } }, + { "bridge_speed", 99 }, + // internal bridges use solid_infil speed + { "bottom_solid_layers", 1 }, + // internal bridges use solid_infil speed + }); + + GCodeReader parser; + int fan = 0; + int fan_with_incorrect_speeds = 0; + int fan_with_incorrect_print_speeds = 0; + int bridge_with_no_fan = 0; + const double bridge_speed = config.opt_float("bridge_speed") * 60; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config), + [&fan, &fan_with_incorrect_speeds, &fan_with_incorrect_print_speeds, &bridge_with_no_fan, bridge_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("M106")) { + line.has_value('S', fan); + if (fan != 255) + ++ fan_with_incorrect_speeds; + } else if (line.cmd_is("M107")) { + fan = 0; + } else if (line.extruding(self) && line.dist_XY(self) > 0) { + if (is_approx(line.new_F(self), bridge_speed)) { + if (fan != 255) + ++ bridge_with_no_fan; + } else { + if (fan != 0) + ++ fan_with_incorrect_print_speeds; + } + } + }); + THEN("bridge fan speed is applied correctly") { + REQUIRE(fan_with_incorrect_speeds == 0); + } + THEN("bridge fan is only turned on for bridges") { + REQUIRE(fan_with_incorrect_print_speeds == 0); + } + THEN("bridge fan is turned on for all bridges") { + REQUIRE(bridge_with_no_fan == 0); + } + } + GIVEN("20mm cube") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "cooling", { 1 } }, + { "fan_below_layer_time", { 0 } }, + { "slowdown_below_layer_time", { 10 } }, + { "min_print_speed", { 0 } }, + { "start_gcode", "" }, + { "first_layer_speed", "100%" }, + { "external_perimeter_speed", 99 } + }); + GCodeReader parser; + const double external_perimeter_speed = config.opt("external_perimeter_speed")->value * 60; + std::vector layer_times; + // z => 1 + std::map layer_external; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), + [&layer_times, &layer_external, external_perimeter_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("G1")) { + if (line.dist_Z(self) != 0) { + layer_times.emplace_back(0.); + layer_external[scaled(line.new_Z(self))] = 0; + } + double l = line.dist_XY(self); + if (l == 0) + l = line.dist_E(self); + if (l == 0) + l = line.dist_Z(self); + if (l > 0.) { + if (layer_times.empty()) + layer_times.emplace_back(0.); + layer_times.back() += 60. * std::abs(l) / line.new_F(self); + } + if (line.has('F') && line.f() == external_perimeter_speed) + ++ layer_external[scaled(self.z())]; + } + }); + THEN("slowdown_below_layer_time is honored") { + // Account for some inaccuracies. + const double slowdown_below_layer_time = config.opt("slowdown_below_layer_time")->values.front() - 0.2; + size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(), + [slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; }); + REQUIRE(minimum_time_honored == layer_times.size()); + } + THEN("slowdown_below_layer_time does not alter external perimeters") { + // Broken by Vojtech + // check that all layers have at least one unaltered external perimeter speed + // my $external = all { $_ > 0 } values %layer_external; + // ok $external, ''; + } + } +} diff --git a/tests/fff_print/test_custom_gcode.cpp b/tests/fff_print/test_custom_gcode.cpp new file mode 100644 index 0000000000..37103b316d --- /dev/null +++ b/tests/fff_print/test_custom_gcode.cpp @@ -0,0 +1,261 @@ +#include + +#include +#include +#include + +#include + +#include "libslic3r/Config.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" + +#include "test_data.hpp" + +using namespace Slic3r; + +SCENARIO("Output file format", "[CustomGCode]") +{ + WHEN("output_file_format set") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "travel_speed", "130"}, + { "layer_height", "0.4"}, + { "output_filename_format", "ts_[travel_speed]_lh_[layer_height].gcode" }, + { "start_gcode", "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n" } + }); + + Print print; + Model model; + Test::init_print({ Test::TestMesh::cube_2x20x10 }, print, model, config); + + std::string output_file = print.output_filepath({}, {}); + THEN("print config options are replaced in output filename") { + REQUIRE(output_file == "ts_130_lh_0.4.gcode"); + } + } +} + +SCENARIO("Custom G-code", "[CustomGCode]") +{ + WHEN("start_gcode and layer_gcode set") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "start_gcode", "_MY_CUSTOM_START_GCODE_" }, // to avoid dealing with the nozzle lift in start G-code + { "layer_gcode", "_MY_CUSTOM_LAYER_GCODE_" } + }); + GCodeReader parser; + bool last_move_was_z_change = false; + int num_layer_changes_not_applied = 0; + parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config), + [&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) + ++ num_layer_changes_not_applied; + last_move_was_z_change = line.dist_Z(self) > 0; + }); + THEN("custom layer G-code is applied after Z move and before other moves") { + REQUIRE(num_layer_changes_not_applied == 0); + } + }; + + auto config = Slic3r::DynamicPrintConfig::new_with({ + { "nozzle_diameter", { 0.6,0.6,0.6,0.6 } }, + { "extruder", 2 }, + { "first_layer_temperature", { 200, 205 } } + }); + config.normalize_fdm(); + WHEN("Printing with single but non-zero extruder") { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("temperature set correctly for non-zero yet single extruder") { + REQUIRE(Slic3r::Test::contains(gcode, "\nM104 S205 T1 ;")); + } + THEN("unused extruder correctly ignored") { + REQUIRE(! Slic3r::Test::contains_regex(gcode, "M104 S\\d+ T0")); + } + } + WHEN("Printing with two extruders") { + config.opt_int("infill_extruder") = 1; + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("temperature set correctly for first extruder") { + REQUIRE(Slic3r::Test::contains(gcode, "\nM104 S200 T0 ;")); + }; + THEN("temperature set correctly for second extruder") { + REQUIRE(Slic3r::Test::contains(gcode, "\nM104 S205 T1 ;")); + }; + } + + auto test = [](DynamicPrintConfig &config) { + // we use the [infill_extruder] placeholder to make sure this test doesn't + // catch a false positive caused by the unparsed start G-code option itself + // being embedded in the G-code + config.opt_int("infill_extruder") = 1; + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("temperature placeholder for first extruder correctly populated") { + REQUIRE(Slic3r::Test::contains(gcode, "temp0:200")); + } + THEN("temperature placeholder for second extruder correctly populated") { + REQUIRE(Slic3r::Test::contains(gcode, "temp1:205")); + } + THEN("temperature placeholder for unused extruder populated with first value") { + REQUIRE(Slic3r::Test::contains(gcode, "temp2:200")); + } + }; + WHEN("legacy syntax") { + config.set_deserialize_strict("start_gcode", + ";__temp0:[first_layer_temperature_0]__\n" + ";__temp1:[first_layer_temperature_1]__\n" + ";__temp2:[first_layer_temperature_2]__\n"); + test(config); + } + WHEN("new syntax") { + config.set_deserialize_strict("start_gcode", + ";__temp0:{first_layer_temperature[0]}__\n" + ";__temp1:{first_layer_temperature[1]}__\n" + ";__temp2:{first_layer_temperature[2]}__\n"); + test(config); + } + WHEN("Vojtech's syntax") { + config.set_deserialize_strict({ + { "infill_extruder", 1 }, + { "start_gcode", + ";substitution:{if infill_extruder==1}extruder1" + "{elsif infill_extruder==2}extruder2" + "{else}extruder3{endif}" + } + }); + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("if / else / endif - first block returned") { + REQUIRE(Test::contains(gcode, "\n;substitution:extruder1\n")); + } + } + GIVEN("Layer change G-codes") + { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "before_layer_gcode", ";BEFORE [layer_num]" }, + { "layer_gcode", ";CHANGE [layer_num]" }, + { "support_material", 1 }, + { "layer_height", 0.2 } + }); + WHEN("before and after layer change G-codes set") { + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config); + GCodeReader parser; + std::vector before; + std::vector change; + parser.parse_buffer(gcode, [&before, &change](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line){ + int d; + if (sscanf(line.raw().c_str(), ";BEFORE %d", &d) == 1) + before.emplace_back(d); + else if (sscanf(line.raw().c_str(), ";CHANGE %d", &d) == 1) { + change.emplace_back(d); + if (d != before.back()) + throw std::runtime_error("inconsistent layer_num before and after layer change"); + } + }); + THEN("layer_num is consistent before and after layer changes") { + REQUIRE(before == change); + } + THEN("layer_num grows continously") { + // i.e. no duplicates or regressions + bool successive = true; + for (size_t i = 1; i < change.size(); ++ i) + if (change[i - 1] + 1 != change[i]) + successive = false; + REQUIRE(successive); + } + } + } + GIVEN("if / elsif / elsif / elsif / else / endif") + { + auto config = Slic3r::DynamicPrintConfig::new_with({ + { "nozzle_diameter", { 0.6,0.6,0.6,0.6,0.6 } }, + { "start_gcode", + ";substitution:{if infill_extruder==1}if block" + "{elsif infill_extruder==2}elsif block 1" + "{elsif infill_extruder==3}elsif block 2" + "{elsif infill_extruder==4}elsif block 3" + "{else}endif block{endif}" + ":end" + } + }); + std::string returned[] = { "" /* indexed by one based extruder ID */, "if block", "elsif block 1", "elsif block 2", "elsif block 3", "endif block" }; + auto test = [&config, &returned](int i) { + config.set_deserialize_strict({ { "infill_extruder", i } }); + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + int found_error = 0; + for (int j = 1; j <= 5; ++ j) + if (i != j && Slic3r::Test::contains(gcode, std::string("substitution:") + returned[j] + ":end")) + // failure + ++ found_error; + THEN(std::string("if / else / endif returned ") + returned[i]) { + REQUIRE(Slic3r::Test::contains(gcode, std::string("substitution:") + returned[i] + ":end")); + } + THEN(std::string("if / else / endif - only ") + returned[i] + "returned") { + REQUIRE(found_error == 0); + } + }; + WHEN("infill_extruder == 1") { test(1); } + WHEN("infill_extruder == 2") { test(2); } + WHEN("infill_extruder == 3") { test(3); } + WHEN("infill_extruder == 4") { test(4); } + WHEN("infill_extruder == 5") { test(5); } + } + GIVEN("nested if / if / else / endif") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", { 0.6,0.6,0.6,0.6,0.6 } }, + { "start_gcode", + ";substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}" + "{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}" + "{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end" + } + }); + auto test = [&config](int i) { + config.opt_int("infill_extruder") = i; + int failed = 0; + for (int j = 1; j <= 2; ++ j) { + config.opt_int("perimeter_extruder") = j; + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + if (! Slic3r::Test::contains(gcode, std::string("substitution:block") + std::to_string(i) + std::to_string(j) + ":end")) + ++ failed; + } + THEN(std::string("two level if / else / endif - block for infill_extruder ") + std::to_string(i) + "succeeded") { + REQUIRE(failed == 0); + } + }; + WHEN("infill_extruder == 1") { test(1); } + WHEN("infill_extruder == 2") { test(2); } + WHEN("infill_extruder == 3") { test(3); } + } + GIVEN("printer type in notes") { + auto config = Slic3r::DynamicPrintConfig::new_with({ + { "start_gcode", + ";substitution:{if notes==\"MK2\"}MK2{elsif notes==\"MK3\"}MK3{else}MK1{endif}:end" + } + }); + auto test = [&config](const std::string &printer_name) { + config.set_deserialize_strict("notes", printer_name); + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN(std::string("printer name ") + printer_name + " matched") { + REQUIRE(Slic3r::Test::contains(gcode, std::string("substitution:") + printer_name + ":end")); + } + }; + WHEN("printer MK2") { test("MK2"); } + WHEN("printer MK3") { test("MK3"); } + WHEN("printer MK1") { test("MK1"); } + } + GIVEN("sequential print with between_objects_gcode") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "complete_objects", 1 }, + { "between_objects_gcode", "_MY_CUSTOM_GCODE_" } + }); + std::string gcode = Slic3r::Test::slice( + // 3x 20mm box + { Slic3r::Test::TestMesh::cube_20x20x20, Slic3r::Test::TestMesh::cube_20x20x20, Slic3r::Test::TestMesh::cube_20x20x20 }, + config); + THEN("between_objects_gcode is applied correctly") { + const boost::regex expression("^_MY_CUSTOM_GCODE_"); + const std::ptrdiff_t match_count = + std::distance(boost::sregex_iterator(gcode.begin(), gcode.end(), expression), boost::sregex_iterator()); + REQUIRE(match_count == 2); + } + } +} diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 6be45d2380..6d87560579 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include using namespace std; @@ -26,6 +27,7 @@ const std::unordered_map mesh_names { std::pair(TestMesh::V, "V"), std::pair(TestMesh::_40x10, "40x10"), std::pair(TestMesh::cube_20x20x20, "cube_20x20x20"), + std::pair(TestMesh::cube_2x20x10, "cube_2x20x10"), std::pair(TestMesh::sphere_50mm, "sphere_50mm"), std::pair(TestMesh::bridge, "bridge"), std::pair(TestMesh::bridge_with_hole, "bridge_with_hole"), @@ -49,6 +51,9 @@ TriangleMesh mesh(TestMesh m) case TestMesh::cube_20x20x20: mesh = Slic3r::make_cube(20, 20, 20); break; + case TestMesh::cube_2x20x10: + mesh = Slic3r::make_cube(2, 20, 10); + break; case TestMesh::sphere_50mm: mesh = Slic3r::make_sphere(50, PI / 243.0); break; @@ -224,6 +229,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r object->add_instance(); } arrange_objects(model, InfiniteBed{}, ArrangeParams{ scaled(min_object_distance(config))}); + model.center_instances_around_point({100, 100}); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); print.auto_assign_extruders(mo); @@ -348,6 +354,17 @@ std::string slice(std::initializer_list meshes, std::initializer_l return gcode(print); } +bool contains(const std::string &data, const std::string &pattern) +{ + return data.find(pattern) != data.npos; +} + +bool contains_regex(const std::string &data, const std::string &pattern) +{ + boost::regex re(pattern); + return boost::regex_match(data, re); +} + } } // namespace Slic3r::Test #include diff --git a/tests/fff_print/test_data.hpp b/tests/fff_print/test_data.hpp index 573ae58a5c..fd110caf1b 100644 --- a/tests/fff_print/test_data.hpp +++ b/tests/fff_print/test_data.hpp @@ -21,6 +21,7 @@ enum class TestMesh { V, _40x10, cube_20x20x20, + cube_2x20x10, sphere_50mm, bridge, bridge_with_hole, @@ -80,6 +81,9 @@ std::string slice(std::initializer_list meshes, const DynamicPrint std::string slice(std::initializer_list meshes, std::initializer_list config_items, bool comments = false); std::string slice(std::initializer_list meshes, std::initializer_list config_items, bool comments = false); +bool contains(const std::string &data, const std::string &pattern); +bool contains_regex(const std::string &data, const std::string &pattern); + } } // namespace Slic3r::Test diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index c3af6e8fc5..0ccb27d9ab 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -197,7 +197,7 @@ 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_with({ + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ { "nozzle_diameter", "0.4, 0.4, 0.4, 0.4" }, { "fill_pattern", pattern }, { "top_fill_pattern", pattern }, diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index 81f748e19a..b24b59cc61 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -5,8 +5,6 @@ #include "test_data.hpp" // get access to init_print, etc -#include "libslic3r/Config.hpp" -#include "libslic3r/Model.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/GCodeReader.hpp" #include "libslic3r/Flow.hpp" @@ -16,61 +14,118 @@ using namespace Slic3r::Test; using namespace Slic3r; SCENARIO("Extrusion width specifics", "[Flow]") { - GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") { - // this is a sharedptr - DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize_strict({ - { "brim_width", 2 }, - { "skirts", 1 }, - { "perimeters", 3 }, - { "fill_density", "40%" }, - { "first_layer_height", 0.3 } - }); - WHEN("first layer width set to 2mm") { - Slic3r::Model model; - config.set("first_layer_extrusion_width", 2); - Slic3r::Print print; - Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config); - - std::vector E_per_mm_bottom; - std::string gcode = Test::gcode(print); - Slic3r::GCodeReader parser; - const double layer_height = config.opt_float("layer_height"); - parser.parse_buffer(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) - { - if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer - if (line.extruding(self) && line.dist_XY(self) > 0) { - E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self)); - } - } - }); - THEN(" First layer width applies to everything on first layer.") { - bool pass = false; - double avg_E = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast(E_per_mm_bottom.size()); - - pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0); - REQUIRE(pass == true); - REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion - } - THEN(" First layer width does not apply to upper layer.") { + auto test = [](const DynamicPrintConfig &config) { + Slic3r::GCodeReader parser; + const double layer_height = config.opt_float("layer_height"); + std::vector E_per_mm_bottom; + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config), + [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer + if (line.extruding(self) && line.dist_XY(self) > 0) + E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self)); } + }); + THEN("First layer width applies to everything on first layer.") { + REQUIRE(E_per_mm_bottom.size() > 0); + const double E_per_mm_avg = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast(E_per_mm_bottom.size()); + bool pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [E_per_mm_avg] (const double& v) { return v == Approx(E_per_mm_avg); }) == 0); + REQUIRE(pass); + } + THEN("First layer width does not apply to upper layer.") { + } + }; + GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 1 }, + { "brim_width", 2 }, + { "perimeters", 3 }, + { "fill_density", "40%" }, + { "first_layer_height", 0.3 }, + { "first_layer_extrusion_width", "2" }, + }); + WHEN("Slicing a 20mm cube") { + test(config); + } + } + GIVEN("A config with more options and a 20mm cube ") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 1 }, + { "brim_width", 2 }, + { "perimeters", 3 }, + { "fill_density", "40%" }, + { "layer_height", "0.35" }, + { "first_layer_height", "0.35" }, + { "bottom_solid_layers", 1 }, + { "first_layer_extrusion_width", "2" }, + { "filament_diameter", "3" }, + { "nozzle_diameter", "0.5" } + }); + WHEN("Slicing a 20mm cube") { + test(config); } } } -// needs gcode export + SCENARIO(" Bridge flow specifics.", "[Flow]") { + auto config = DynamicPrintConfig::full_print_config_with({ + { "bridge_speed", 99 }, + { "bridge_flow_ratio", 1 }, + // to prevent speeds from being altered + { "cooling", "0" }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" } + }); + + auto test = [](const DynamicPrintConfig &config) { + GCodeReader parser; + const double bridge_speed = config.opt_float("bridge_speed") * 60.; + std::vector E_per_mm; + parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config), + [&E_per_mm, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.extruding(self) && line.dist_XY(self) > 0) { + if (is_approx(line.new_F(self), bridge_speed)) + E_per_mm.emplace_back(line.dist_E(self) / line.dist_XY(self)); + } + }); + const double nozzle_dmr = config.opt("nozzle_diameter")->get_at(0); + const double filament_dmr = config.opt("filament_diameter")->get_at(0); + const double bridge_mm_per_mm = sqr(nozzle_dmr / filament_dmr) * config.opt_float("bridge_flow_ratio"); + size_t num_errors = std::count_if(E_per_mm.begin(), E_per_mm.end(), + [bridge_mm_per_mm](double v){ return std::abs(v - bridge_mm_per_mm) > 0.01; }); + return num_errors == 0; + }; + GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") { - WHEN("bridge_flow_ratio is set to 1.0") { + WHEN("bridge_flow_ratio is set to 0.5 and extrusion width to default") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", "0" } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } - WHEN("bridge_flow_ratio is set to 0.5") { + WHEN("bridge_flow_ratio is set to 2.0 and extrusion width to default") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 2.0}, { "extrusion_width", "0" } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } - WHEN("bridge_flow_ratio is set to 2.0") { + WHEN("bridge_flow_ratio is set to 0.5 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 0.5}, { "extrusion_width", 0.4 } }); THEN("Output flow is as expected.") { + REQUIRE(test(config)); + } + } + WHEN("bridge_flow_ratio is set to 1.0 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 1.0}, { "extrusion_width", 0.4 } }); + THEN("Output flow is as expected.") { + REQUIRE(test(config)); + } + } + WHEN("bridge_flow_ratio is set to 2 and extrusion_width to 0.4") { + config.set_deserialize_strict({ { "bridge_flow_ratio", 2.}, { "extrusion_width", 0.4 } }); + THEN("Output flow is as expected.") { + REQUIRE(test(config)); } } } diff --git a/tests/fff_print/test_gaps.cpp b/tests/fff_print/test_gaps.cpp new file mode 100644 index 0000000000..a096087ce6 --- /dev/null +++ b/tests/fff_print/test_gaps.cpp @@ -0,0 +1,60 @@ +#include + +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Layer.hpp" + +#include "test_data.hpp" // get access to init_print, etc + +using namespace Slic3r::Test; +using namespace Slic3r; + +SCENARIO("Gaps", "[Gaps]") { + GIVEN("Two hollow squares") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeter_speed", 66 }, + { "external_perimeter_speed", 66 }, + { "small_perimeter_speed", 66 }, + { "gap_fill_speed", 99 }, + { "perimeters", 1 }, + // to prevent speeds from being altered + { "cooling", 0 }, + // to prevent speeds from being altered + { "first_layer_speed", "100%" }, + { "perimeter_extrusion_width", 0.35 }, + { "first_layer_extrusion_width", 0.35 } + }); + + GCodeReader parser; + const double perimeter_speed = config.opt_float("perimeter_speed") * 60; + const double gap_fill_speed = config.opt_float("gap_fill_speed") * 60; + std::string last; // perimeter or gap + Points perimeter_points; + int gap_fills_outside_last_perimeters = 0; + parser.parse_buffer( + Slic3r::Test::slice({ Slic3r::Test::TestMesh::two_hollow_squares }, config), + [&perimeter_points, &gap_fills_outside_last_perimeters, &last, perimeter_speed, gap_fill_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + Point point = line.new_XY_scaled(self); + if (is_approx(f, perimeter_speed)) { + if (last == "gap") + perimeter_points.clear(); + perimeter_points.emplace_back(point); + last = "perimeter"; + } else if (is_approx(f, gap_fill_speed)) { + Polygon convex_hull = Geometry::convex_hull(perimeter_points); + if (! convex_hull.contains(point)) + ++ gap_fills_outside_last_perimeters; + last = "gap"; + } + } + }); + THEN("gap fills are printed before leaving islands") { + REQUIRE(gap_fills_outside_last_perimeters == 0); + } + } +} diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index a139e4c2be..395f758410 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -126,3 +126,62 @@ SCENARIO("Print: Brim generation", "[Print]") { } } } + +SCENARIO("Ported from Perl", "[Print]") { + GIVEN("20mm cube") { + WHEN("Print center is set to 100x100 (test framework default)") { + auto config = Slic3r::DynamicPrintConfig::full_print_config(); + std::string gcode = Slic3r::Test::slice({ TestMesh::cube_20x20x20 }, config); + GCodeReader parser; + Points extrusion_points; + parser.parse_buffer(gcode, [&extrusion_points](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + if (line.cmd_is("G1") && line.extruding(self) && line.dist_XY(self) > 0) + extrusion_points.emplace_back(line.new_XY_scaled(self)); + }); + Vec2d center = unscaled(BoundingBox(extrusion_points).center()); + THEN("print is centered around print_center") { + REQUIRE(is_approx(center.x(), 100.)); + REQUIRE(is_approx(center.y(), 100.)); + } + } + } + GIVEN("Model with multiple objects") { + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", { 0.4, 0.4, 0.4, 0.4 } } + }); + Print print; + Model model; + Slic3r::Test::init_print({ TestMesh::cube_20x20x20 }, print, model, config); + + // User sets a per-region option, also testing a deep copy of Model. + Model model2(model); + model2.objects.front()->config.set_deserialize_strict("fill_density", "100%"); + WHEN("fill_density overridden") { + print.apply(model2, config); + THEN("region config inherits model object config") { + REQUIRE(print.get_print_region(0).config().fill_density == 100); + } + } + + model2.objects.front()->config.erase("fill_density"); + WHEN("fill_density resetted") { + print.apply(model2, config); + THEN("region config is resetted") { + REQUIRE(print.get_print_region(0).config().fill_density == 20); + } + } + + WHEN("extruder is assigned") { + model2.objects.front()->config.set("extruder", 3); + model2.objects.front()->config.set("perimeter_extruder", 2); + print.apply(model2, config); + THEN("extruder setting is correctly expanded") { + REQUIRE(print.get_print_region(0).config().infill_extruder == 3); + } + THEN("extruder setting does not override explicitely specified extruders") { + REQUIRE(print.get_print_region(0).config().perimeter_extruder == 2); + } + } + } +} diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 8f508f3233..655517220a 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -38,6 +38,7 @@ TEST_CASE("Skirt height is honored", "[Skirt]") { { "support_material_speed", 99 }, // avoid altering speeds unexpectedly { "cooling", false }, + // avoid altering speeds unexpectedly { "first_layer_speed", "100%" } }); diff --git a/tests/fff_print/test_thin_walls.cpp b/tests/fff_print/test_thin_walls.cpp new file mode 100644 index 0000000000..cd80f3bd66 --- /dev/null +++ b/tests/fff_print/test_thin_walls.cpp @@ -0,0 +1,191 @@ +#include + +#include +#include + +#include "test_data.hpp" // get access to init_print, etc + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/libslic3r.h" + +using namespace Slic3r; + +SCENARIO("Medial Axis", "[ThinWalls]") { + GIVEN("Square with hole") { + auto square = Polygon::new_scale({ {100, 100}, {200, 100}, {200, 200}, {100, 200} }); + auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} }); + ExPolygon expolygon{ square, hole_in_square }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(40.), scaled(0.5)); + THEN("medial axis of a square shape is a single path") { + REQUIRE(res.size() == 1); + } + THEN("polyline forms a closed loop") { + REQUIRE(res.front().first_point() == res.front().last_point()); + } + THEN("medial axis loop has reasonable length") { + REQUIRE(res.front().length() > hole_in_square.length()); + REQUIRE(res.front().length() < square.length()); + } + } + } + GIVEN("narrow rectangle") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of a narrow rectangle is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } +#if 0 + //FIXME this test never worked + GIVEN("narrow rectangle with an extra vertex") { + ExPolygon expolygon{ Polygon::new_scale({ + {100, 100}, {120, 100}, {120, 200}, + {105, 200} /* extra point in the short side*/, + {100, 200} + })}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(1.), scaled(0.5)); + THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has still a reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + THEN("extra vertices don't influence medial axis") { + size_t invalid = 0; + for (const Polyline &pl : res) + for (const Point &p : pl.points) + if (std::abs(p.y() - scaled(150.)) < SCALED_EPSILON) + ++ invalid; + REQUIRE(invalid == 0); + } + } + } +#endif + GIVEN("semicircumference") { + ExPolygon expolygon{{ + {1185881,829367},{1421988,1578184},{1722442,2303558},{2084981,2999998},{2506843,3662186},{2984809,4285086},{3515250,4863959},{4094122,5394400}, + {4717018,5872368},{5379210,6294226},{6075653,6656769},{6801033,6957229},{7549842,7193328},{8316383,7363266},{9094809,7465751},{9879211,7500000}, + {10663611,7465750},{11442038,7363265},{12208580,7193327},{12957389,6957228},{13682769,6656768},{14379209,6294227},{15041405,5872366}, + {15664297,5394401},{16243171,4863960},{16758641,4301424},{17251579,3662185},{17673439,3000000},{18035980,2303556},{18336441,1578177}, + {18572539,829368},{18750748,0},{19758422,0},{19727293,236479},{19538467,1088188},{19276136,1920196},{18942292,2726179},{18539460,3499999}, + {18070731,4235755},{17539650,4927877},{16950279,5571067},{16307090,6160437},{15614974,6691519},{14879209,7160248},{14105392,7563079}, + {13299407,7896927},{12467399,8159255},{11615691,8348082},{10750769,8461952},{9879211,8500000},{9007652,8461952},{8142729,8348082}, + {7291022,8159255},{6459015,7896927},{5653029,7563079},{4879210,7160247},{4143447,6691519},{3451331,6160437},{2808141,5571066},{2218773,4927878}, + {1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0} + }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(1.324888), scaled(0.25)); + THEN("medial axis of a semicircumference is a single line") { + REQUIRE(res.size() == 1); + } + THEN("all medial axis segments of a semicircumference have the same orientation") { + int nccw = 0; + int ncw = 0; + for (const Polyline &pl : res) + for (size_t i = 1; i + 1 < pl.size(); ++ i) { + double cross = cross2((pl.points[i] - pl.points[i - 1]).cast(), (pl.points[i + 1] - pl.points[i]).cast()); + if (cross > 0.) + ++ nccw; + else if (cross < 0.) + ++ ncw; + } + REQUIRE((ncw == 0 || nccw == 0)); + } + } + } + GIVEN("narrow trapezoid") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of a narrow trapezoid is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } + GIVEN("L shape") { + ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + THEN("medial axis of an L shape is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + // 20 is the thickness of the expolygon, which is subtracted from the ends + auto len = unscale(res.front().length()) + 20; + REQUIRE(len > 80. * 2.); + REQUIRE(len < 100. * 2.); + } + } + } + GIVEN("whatever shape") { + ExPolygon expolygon{{ + {-203064906,-51459966},{-219312231,-51459966},{-219335477,-51459962},{-219376095,-51459962},{-219412047,-51459966}, + {-219572094,-51459966},{-219624814,-51459962},{-219642183,-51459962},{-219656665,-51459966},{-220815482,-51459966}, + {-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024}, + }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(819998., 102499.75); + THEN("medial axis is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + double perimeter = expolygon.contour.split_at_first_point().length(); + REQUIRE(total_length(res) > perimeter / 2. / 4. * 3.); + } + } + } + GIVEN("narrow triangle") { + ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) }; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(scaled(4.), scaled(0.5)); + THEN("medial axis of a narrow triangle is a single line") { + REQUIRE(res.size() == 1); + } + THEN("medial axis has reasonable length") { + REQUIRE(res.front().length() >= scaled(200.-100. - (120.-100.)) - SCALED_EPSILON); + } + } + } + GIVEN("GH #2474") { + ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }}; + WHEN("Medial axis is extracted") { + Polylines res = expolygon.medial_axis(1871238, 500000); + THEN("medial axis is a single line") { + REQUIRE(res.size() == 1); + } + Polyline &polyline = res.front(); + THEN("medial axis is horizontal and is centered") { + double expected_y = expolygon.contour.bounding_box().center().y(); + double center_y = 0.; + for (auto &p : polyline.points) + center_y += double(p.y()); + REQUIRE(std::abs(center_y / polyline.size() - expected_y) < SCALED_EPSILON); + } + // order polyline from left to right + if (polyline.first_point().x() > polyline.last_point().x()) + polyline.reverse(); + BoundingBox polyline_bb = polyline.bounding_box(); + THEN("expected x_min") { + REQUIRE(polyline.first_point().x() == polyline_bb.min.x()); + } + THEN("expected x_max") { + REQUIRE(polyline.last_point().x() == polyline_bb.max.x()); + } + THEN("medial axis is monotonous in x (not self intersecting)") { + Polyline sorted { polyline }; + std::sort(sorted.begin(), sorted.end()); + REQUIRE(polyline == sorted); + } + } + } +} diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 2482451829..57e4736474 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp test_3mf.cpp test_aabbindirect.cpp + test_kdtreeindirect.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_color.cpp @@ -13,6 +14,7 @@ add_executable(${_TEST_NAME}_tests test_geometry.cpp test_placeholder_parser.cpp test_polygon.cpp + test_polyline.cpp test_mutable_polygon.cpp test_mutable_priority_queue.cpp test_stl.cpp diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 025459d689..50e61b3b04 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -1,5 +1,6 @@ #include +#include "libslic3r/Config.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/LocalesUtils.hpp" @@ -13,20 +14,20 @@ using namespace Slic3r; SCENARIO("Generic config validation performs as expected.", "[Config]") { GIVEN("A config generated from default options") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - WHEN( "perimeter_extrusion_width is set to 250%, a valid value") { + WHEN("perimeter_extrusion_width is set to 250%, a valid value") { config.set_deserialize_strict("perimeter_extrusion_width", "250%"); THEN( "The config is read as valid.") { REQUIRE(config.validate().empty()); } } - WHEN( "perimeter_extrusion_width is set to -10, an invalid value") { + WHEN("perimeter_extrusion_width is set to -10, an invalid value") { config.set("perimeter_extrusion_width", -10); THEN( "Validate returns error") { REQUIRE(! config.validate().empty()); } } - WHEN( "perimeters is set to -10, an invalid value") { + WHEN("perimeters is set to -10, an invalid value") { config.set("perimeters", -10); THEN( "Validate returns error") { REQUIRE(! config.validate().empty()); @@ -36,8 +37,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") { } SCENARIO("Config accessor functions perform as expected.", "[Config]") { - GIVEN("A config generated from default options") { - Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + auto test = [](ConfigBase &config) { WHEN("A boolean option is set to a boolean value") { REQUIRE_NOTHROW(config.set("gcode_comments", true)); THEN("The underlying value is set correctly.") { @@ -70,8 +70,8 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } #if 0 - //FIXME better design accessors for vector elements. - WHEN("An integer-based option is set through the integer interface") { + //FIXME better design accessors for vector elements. + WHEN("An integer-based option is set through the integer interface") { config.set("bed_temperature", 100); THEN("The underlying value is set correctly.") { REQUIRE(config.opt("bed_temperature")->get_at(0) == 100); @@ -193,6 +193,14 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { REQUIRE(config.opt_float("layer_height") == 0.5); } } + }; + GIVEN("DynamicPrintConfig generated from default options") { + auto config = Slic3r::DynamicPrintConfig::full_print_config(); + test(config); + } + GIVEN("FullPrintConfig generated from default options") { + Slic3r::FullPrintConfig config; + test(config); } } diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 34e625e6db..41ef69aaaf 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -162,14 +162,34 @@ TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){ } -TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){ - BoundingBox bb(std::vector({Point(0, 1), Point(10, 2), Point(20, 2)})); - bb.scale(2); - REQUIRE(bb.min == Point(0,2)); - REQUIRE(bb.max == Point(40,4)); +SCENARIO("BoundingBox", "[Geometry]") { + WHEN("Bounding boxes are scaled") { + BoundingBox bb(std::vector({Point(0, 1), Point(10, 2), Point(20, 2)})); + bb.scale(2); + REQUIRE(bb.min == Point(0,2)); + REQUIRE(bb.max == Point(40,4)); + } + WHEN("BoundingBox constructed from points") { + BoundingBox bb(Points{ {100,200}, {100, 200}, {500, -600} }); + THEN("minimum is correct") { + REQUIRE(bb.min == Point{100,-600}); + } + THEN("maximum is correct") { + REQUIRE(bb.max == Point{500,200}); + } + } + WHEN("BoundingBox constructed from a single point") { + BoundingBox bb; + bb.merge({10, 10}); + THEN("minimum equals to the only defined point") { + REQUIRE(bb.min == Point{10,10}); + } + THEN("maximum equals to the only defined point") { + REQUIRE(bb.max == Point{10,10}); + } + } } - TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){ Slic3r::Polyline tmp = { Point(10,10), Point(20,10) }; Slic3r::Polygon area = offset(tmp,5).at(0); diff --git a/tests/libslic3r/test_kdtreeindirect.cpp b/tests/libslic3r/test_kdtreeindirect.cpp new file mode 100644 index 0000000000..28d409bbbf --- /dev/null +++ b/tests/libslic3r/test_kdtreeindirect.cpp @@ -0,0 +1,142 @@ +#include + +#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/Execution/ExecutionSeq.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/PointGrid.hpp" + +using namespace Slic3r; + +//template +//struct Within { // Wrapper for the `within` predicate that counts calls. + +// kdtree::Within pred; + +// Within(G box): pred{box} {} + +// // Number of times the predicate was called +// mutable size_t call_count = 0; + +// std::pair operator() (const Vec3f &p, size_t dim) +// { +// ++call_count; + +// return pred(p, dim); +// } +//}; + +static double volume(const BoundingBox3Base &box) +{ + auto sz = box.size(); + return sz.x() * sz.y() * sz.z(); +} + +static double volume(const Eigen::AlignedBox &box) +{ + return box.volume(); +} + +TEST_CASE("Test kdtree query for a Box", "[KDTreeIndirect]") +{ + auto vol = BoundingBox3Base{{0.f, 0.f, 0.f}, {10.f, 10.f, 10.f}}; + + auto pgrid = point_grid(ex_seq, vol, Vec3f{0.1f, 0.1f, 0.1f}); + + REQUIRE(!pgrid.empty()); + + auto coordfn = [&pgrid] (size_t i, size_t D) { return pgrid.get(i)(int(D)); }; + KDTreeIndirect<3, float, decltype(coordfn)> tree{coordfn, pgrid.point_count()}; + + std::vector out; + + auto qbox = BoundingBox3Base{Vec3f{0.f, 0.f, 0.f}, Vec3f{.5f, .5f, .5f}}; + + size_t call_count = 0; + out = find_nearby_points(tree, qbox.min, qbox.max, [&call_count](size_t) { + call_count++; + return true; + }); + + // Output shall be non-empty + REQUIRE(!out.empty()); + + std::sort(out.begin(), out.end()); + + // No duplicates allowed in the output + auto it = std::unique(out.begin(), out.end()); + REQUIRE(it == out.end()); + + // Test if inside points are in the output and outside points are not. + bool succ = true; + for (size_t i = 0; i < pgrid.point_count(); ++i) { + auto foundit = std::find(out.begin(), out.end(), i); + bool contains = qbox.contains(pgrid.get(i)); + succ = succ && contains ? foundit != out.end() : foundit == out.end(); + + if (!succ) { + std::cout << "invalid point: " << i << " " << pgrid.get(i).transpose() + << std::endl; + break; + } + } + + REQUIRE(succ); + + // Test for the expected cost of the query. + double gridvolume = volume(vol); + double queryvolume = volume(qbox); + double volratio = (queryvolume / gridvolume); + REQUIRE(call_count < 3 * volratio * pgrid.point_count()); + REQUIRE(call_count < pgrid.point_count()); +} + +//TEST_CASE("Test kdtree query for a Sphere", "[KDTreeIndirect]") { +// auto vol = BoundingBox3Base{{0.f, 0.f, 0.f}, {10.f, 10.f, 10.f}}; + +// auto pgrid = point_grid(ex_seq, vol, Vec3f{0.1f, 0.1f, 0.1f}); + +// REQUIRE(!pgrid.empty()); + +// auto coordfn = [&pgrid] (size_t i, size_t D) { return pgrid.get(i)(int(D)); }; +// kdtree::KDTreeIndirect<3, float, decltype(coordfn)> tree{coordfn, pgrid.point_count()}; + +// std::vector out; + +// auto querysphere = kdtree::Sphere{Vec3f{5.f, 5.f, 5.f}, 2.f}; + +// auto pred = Within(querysphere); + +// kdtree::query(tree, pred, std::back_inserter(out)); + +// // Output shall be non-empty +// REQUIRE(!out.empty()); + +// std::sort(out.begin(), out.end()); + +// // No duplicates allowed in the output +// auto it = std::unique(out.begin(), out.end()); +// REQUIRE(it == out.end()); + +// // Test if inside points are in the output and outside points are not. +// bool succ = true; +// for (size_t i = 0; i < pgrid.point_count(); ++i) { +// auto foundit = std::find(out.begin(), out.end(), i); +// bool contains = (querysphere.center - pgrid.get(i)).squaredNorm() < pred.pred.r2; +// succ = succ && contains ? foundit != out.end() : foundit == out.end(); + +// if (!succ) { +// std::cout << "invalid point: " << i << " " << pgrid.get(i).transpose() +// << std::endl; +// break; +// } +// } + +// REQUIRE(succ); + +// // Test for the expected cost of the query. +// double gridvolume = volume(vol); +// double queryvolume = volume(querysphere); +// double volratio = (queryvolume / gridvolume); +// REQUIRE(pred.call_count < 3 * volratio * pgrid.point_count()); +// REQUIRE(pred.call_count < pgrid.point_count()); +//} diff --git a/tests/libslic3r/test_mutable_priority_queue.cpp b/tests/libslic3r/test_mutable_priority_queue.cpp index 40ed5a1586..78c97bc7e1 100644 --- a/tests/libslic3r/test_mutable_priority_queue.cpp +++ b/tests/libslic3r/test_mutable_priority_queue.cpp @@ -343,7 +343,7 @@ TEST_CASE("Mutable priority queue - reshedule first", "[MutableSkipHeapPriorityQ TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]") { struct MyValue{ - int id; + size_t id; float val; }; size_t count = 50000; @@ -356,15 +356,15 @@ TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]" [](MyValue &l, MyValue &r) { return l.val < r.val; }); q.reserve(count); for (size_t id = 0; id < count; id++) { - MyValue mv; - mv.id = id; - mv.val = rand(); + MyValue mv{ id, rand() / 100.f }; q.push(mv); } MyValue it = q.top(); // copy q.pop(); - bool valid = (it.id != 0) && (idxs[0] < 3 * count); - CHECK(valid); + // is valid id (no initial value default value) + CHECK(it.id != 0); + // is first item in queue valid value + CHECK(idxs[0] != std::numeric_limits::max()); } TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") diff --git a/tests/libslic3r/test_polygon.cpp b/tests/libslic3r/test_polygon.cpp index f2c78cacec..7774b44a6a 100644 --- a/tests/libslic3r/test_polygon.cpp +++ b/tests/libslic3r/test_polygon.cpp @@ -148,3 +148,65 @@ SCENARIO("Remove collinear points from Polygon", "[Polygon]") { } } } + +SCENARIO("Simplify polygon", "[Polygon]") +{ + GIVEN("gear") { + auto gear = Polygon::new_scale({ + {144.9694,317.1543}, {145.4181,301.5633}, {146.3466,296.921}, {131.8436,294.1643}, {131.7467,294.1464}, + {121.7238,291.5082}, {117.1631,290.2776}, {107.9198,308.2068}, {100.1735,304.5101}, {104.9896,290.3672}, + {106.6511,286.2133}, {93.453,279.2327}, {81.0065,271.4171}, {67.7886,286.5055}, {60.7927,280.1127}, + {69.3928,268.2566}, {72.7271,264.9224}, {61.8152,253.9959}, {52.2273,242.8494}, {47.5799,245.7224}, + {34.6577,252.6559}, {30.3369,245.2236}, {42.1712,236.3251}, {46.1122,233.9605}, {43.2099,228.4876}, + {35.0862,211.5672}, {33.1441,207.0856}, {13.3923,212.1895}, {10.6572,203.3273}, {6.0707,204.8561}, + {7.2775,204.4259}, {29.6713,196.3631}, {25.9815,172.1277}, {25.4589,167.2745}, {19.8337,167.0129}, + {5.0625,166.3346}, {5.0625,156.9425}, {5.3701,156.9282}, {21.8636,156.1628}, {25.3713,156.4613}, + {25.4243,155.9976}, {29.3432,155.8157}, {30.3838,149.3549}, {26.3596,147.8137}, {27.1085,141.2604}, + {29.8466,126.8337}, {24.5841,124.9201}, {10.6664,119.8989}, {13.4454,110.9264}, {33.1886,116.0691}, + {38.817,103.1819}, {45.8311,89.8133}, {30.4286,76.81}, {35.7686,70.0812}, {48.0879,77.6873}, + {51.564,81.1635}, {61.9006,69.1791}, {72.3019,58.7916}, {60.5509,42.5416}, {68.3369,37.1532}, + {77.9524,48.1338}, {80.405,52.2215}, {92.5632,44.5992}, {93.0123,44.3223}, {106.3561,37.2056}, + {100.8631,17.4679}, {108.759,14.3778}, {107.3148,11.1283}, {117.0002,32.8627}, {140.9109,27.3974}, + {145.7004,26.4994}, {145.1346,6.1011}, {154.502,5.4063}, {156.9398,25.6501}, {171.0557,26.2017}, + {181.3139,27.323}, {186.2377,27.8532}, {191.6031,8.5474}, {200.6724,11.2756}, {197.2362,30.2334}, + {220.0789,39.1906}, {224.3261,41.031}, {236.3506,24.4291}, {243.6897,28.6723}, {234.2956,46.7747}, + {245.6562,55.1643}, {257.2523,65.0901}, {261.4374,61.5679}, {273.1709,52.8031}, {278.555,59.5164}, + {268.4334,69.8001}, {264.1615,72.3633}, {268.2763,77.9442}, {278.8488,93.5305}, {281.4596,97.6332}, + {286.4487,95.5191}, {300.2821,90.5903}, {303.4456,98.5849}, {286.4523,107.7253}, {293.7063,131.1779}, + {294.9748,135.8787}, {314.918,133.8172}, {315.6941,143.2589}, {300.9234,146.1746}, {296.6419,147.0309}, + {297.1839,161.7052}, {296.6136,176.3942}, {302.1147,177.4857}, {316.603,180.3608}, {317.1658,176.7341}, + {315.215,189.6589}, {315.1749,189.6548}, {294.9411,187.5222}, {291.13,201.7233}, {286.2615,215.5916}, + {291.1944,218.2545}, {303.9158,225.1271}, {299.2384,233.3694}, {285.7165,227.6001}, {281.7091,225.1956}, + {273.8981,237.6457}, {268.3486,245.2248}, {267.4538,246.4414}, {264.8496,250.0221}, {268.6392,253.896}, + {278.5017,265.2131}, {272.721,271.4403}, {257.2776,258.3579}, {234.4345,276.5687}, {242.6222,294.8315}, + {234.9061,298.5798}, {227.0321,286.2841}, {225.2505,281.8301}, {211.5387,287.8187}, {202.3025,291.0935}, + {197.307,292.831}, {199.808,313.1906}, {191.5298,315.0787}, {187.3082,299.8172}, {186.4201,295.3766}, + {180.595,296.0487}, {161.7854,297.4248}, {156.8058,297.6214}, {154.3395,317.8592} + }); + + WHEN("simplified") { + size_t num_points = gear.size(); + Polygons simplified = gear.simplify(1000.); + THEN("gear simplified to a single polygon") { + REQUIRE(simplified.size() == 1); + } + THEN("gear was reduced using Douglas-Peucker") { + //note printf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); + REQUIRE(simplified.front().size() < num_points); + } + } + } + GIVEN("hole in square") { + // CW oriented + auto hole_in_square = Polygon{ {140, 140}, {140, 160}, {160, 160}, {160, 140} }; + WHEN("simplified") { + Polygons simplified = hole_in_square.simplify(2.); + THEN("hole simplification returns one polygon") { + REQUIRE(simplified.size() == 1); + } + THEN("hole simplification turns cw polygon into ccw polygon") { + REQUIRE(simplified.front().is_counter_clockwise()); + } + } + } +} diff --git a/tests/libslic3r/test_polyline.cpp b/tests/libslic3r/test_polyline.cpp new file mode 100644 index 0000000000..8271554041 --- /dev/null +++ b/tests/libslic3r/test_polyline.cpp @@ -0,0 +1,28 @@ +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" + +using namespace Slic3r; + +SCENARIO("Simplify polyline", "[Polyline]") +{ + GIVEN("polyline 1") { + auto polyline = Polyline{ {0,0},{1,0},{2,0},{2,1},{2,2},{1,2},{0,2},{0,1},{0,0} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(1.); + THEN("simplified correctly") { + REQUIRE(polyline == Polyline{ {0,0}, {2,0}, {2,2}, {0,2}, {0,0} }); + } + } + } + GIVEN("polyline 2") { + auto polyline = Polyline{ {0,0}, {50,50}, {100,0}, {125,-25}, {150,50} }; + WHEN("simplified with Douglas-Peucker") { + polyline.simplify(25.); + THEN("not simplified") { + REQUIRE(polyline == Polyline{ {0,0}, {50,50}, {125,-25}, {150,50} }); + } + } + } +} diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index d98a920372..a58b33ca0f 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -386,7 +386,7 @@ long raster_pxsum(const sla::RasterGrayscaleAA &raster) double raster_white_area(const sla::RasterGrayscaleAA &raster) { - if (raster.resolution().pixels() == 0) return std::nan(""); + if (raster.resolution().pixels() == 0) return NaNd; auto res = raster.resolution(); double a = 0; diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 1a58aab122..c2343b032c 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -49,7 +49,6 @@ set(XS_XSP_FILES ${XSP_DIR}/ExtrusionEntityCollection.xsp ${XSP_DIR}/ExtrusionLoop.xsp ${XSP_DIR}/ExtrusionPath.xsp - ${XSP_DIR}/GCode.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 1675ac1935..87fb267c51 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -158,7 +158,6 @@ for my $class (qw( Slic3r::ExtrusionLoop Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection - Slic3r::GCode Slic3r::Geometry::BoundingBox Slic3r::Layer Slic3r::Layer::Region diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 4e293a2f9f..8484b1d64f 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -7,7 +7,6 @@ REGISTER_CLASS(ExPolygon, "ExPolygon"); REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); -REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(LayerRegion, "Layer::Region"); diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t deleted file mode 100644 index a071a75a28..0000000000 --- a/xs/t/01_trianglemesh.t +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 4; - -my $cube = { - vertices => [ [20,20,0], [20,0,0], [0,0,0], [0,20,0], [20,20,20], [0,20,20], [0,0,20], [20,0,20] ], - facets => [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5] ], -}; - -{ - my $m = Slic3r::TriangleMesh->new; - $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); - my ($vertices, $facets) = ($m->vertices, $m->facets); - - is_deeply $vertices, $cube->{vertices}, 'vertices arrayref roundtrip'; - is_deeply $facets, $cube->{facets}, 'facets arrayref roundtrip'; - - { - my $m2 = $m->clone; - is_deeply $m2->vertices, $cube->{vertices}, 'cloned vertices arrayref roundtrip'; - is_deeply $m2->facets, $cube->{facets}, 'cloned facets arrayref roundtrip'; - $m2->scale(3); # check that it does not affect $m - } -} - -__END__ diff --git a/xs/t/03_point.t b/xs/t/03_point.t index c950998fbb..f888349b3d 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,10 +4,9 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 24; +use Test::More tests => 21; my $point = Slic3r::Point->new(10, 15); -is_deeply [ @$point ], [10, 15], 'point roundtrip'; my $point2 = $point->clone; $point2->scale(2); @@ -16,9 +15,6 @@ is_deeply [ @$point2 ], [20, 30], 'scale'; $point2->translate(10, -15); is_deeply [ @$point2 ], [30, 15], 'translate'; -ok $point->coincides_with($point->clone), 'coincides_with'; -ok !$point->coincides_with($point2), 'coincides_with'; - { my $point3 = Slic3r::Point->new(4300000, -9880845); is $point->[0], $point->x, 'x accessor'; diff --git a/xs/t/04_expolygon.t b/xs/t/04_expolygon.t index 48eaed551a..9132c44b96 100644 --- a/xs/t/04_expolygon.t +++ b/xs/t/04_expolygon.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(first sum); use Slic3r::XS; -use Test::More tests => 15; +use Test::More tests => 7; use constant PI => 4 * atan2(1, 1); @@ -25,21 +25,6 @@ my $hole_in_square = [ # cw my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); ok $expolygon->is_valid, 'is_valid'; -is ref($expolygon->pp), 'ARRAY', 'expolygon pp is unblessed'; -is_deeply $expolygon->pp, [$square, $hole_in_square], 'expolygon roundtrip'; - -is ref($expolygon->arrayref), 'ARRAY', 'expolygon arrayref is unblessed'; -isa_ok $expolygon->[0], 'Slic3r::Polygon::Ref', 'expolygon polygon is blessed'; -isa_ok $expolygon->contour, 'Slic3r::Polygon::Ref', 'expolygon contour is blessed'; -isa_ok $expolygon->holes->[0], 'Slic3r::Polygon::Ref', 'expolygon hole is blessed'; -isa_ok $expolygon->[0][0], 'Slic3r::Point::Ref', 'expolygon point is blessed'; - -{ - my $expolygon2 = $expolygon->clone; - my $polygon = $expolygon2->[0]; - $polygon->scale(2); - is $expolygon2->[0][0][0], $polygon->[0][0], 'polygons are returned by reference'; -} is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone'; diff --git a/xs/t/05_surface.t b/xs/t/05_surface.t index 34feb47346..4d9eb5b891 100644 --- a/xs/t/05_surface.t +++ b/xs/t/05_surface.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 15; +use Test::More tests => 11; my $square = [ # ccw [100, 100], @@ -27,10 +27,6 @@ my $surface = Slic3r::Surface->new( $surface = $surface->clone; -isa_ok $surface->expolygon, 'Slic3r::ExPolygon::Ref', 'expolygon'; -is_deeply [ @{$surface->expolygon->pp} ], [$square, $hole_in_square], 'expolygon roundtrip'; -is scalar(@{$surface->polygons}), 2, 'polygons roundtrip'; - is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type'; $surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM); is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type'; @@ -59,7 +55,6 @@ is $surface->extra_perimeters, 2, 'extra_perimeters'; is scalar(@$collection), 1, 'append to collection'; my $item = $collection->[0]; - isa_ok $item, 'Slic3r::Surface::Ref'; $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference'; } diff --git a/xs/t/06_polygon.t b/xs/t/06_polygon.t deleted file mode 100644 index 7bbcd5356b..0000000000 --- a/xs/t/06_polygon.t +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 3; - -my $square = [ # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; - -my $polygon = Slic3r::Polygon->new(@$square); -is ref($polygon->arrayref), 'ARRAY', 'polygon arrayref is unblessed'; -isa_ok $polygon->[0], 'Slic3r::Point::Ref', 'polygon point is blessed'; -ok ref($polygon->first_point) eq 'Slic3r::Point', 'first_point'; - -__END__ diff --git a/xs/t/07_extrusionpath.t b/xs/t/07_extrusionpath.t index 008b51b001..084b4f03ea 100644 --- a/xs/t/07_extrusionpath.t +++ b/xs/t/07_extrusionpath.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 7; +use Test::More tests => 5; my $points = [ [100, 100], @@ -17,8 +17,6 @@ my $path = Slic3r::ExtrusionPath->new( role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ); -isa_ok $path->polyline, 'Slic3r::Polyline::Ref', 'path polyline'; -is_deeply $path->polyline->pp, $points, 'path points roundtrip'; $path->reverse; is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path'; diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index e0660a9fd9..3abfbd728a 100644 --- a/xs/t/08_extrusionloop.t +++ b/xs/t/08_extrusionloop.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(sum); use Slic3r::XS; -use Test::More tests => 47; +use Test::More tests => 46; { my $square = [ @@ -33,7 +33,6 @@ use Test::More tests => 47; is scalar(@$loop), 1, 'loop contains one path'; { my $path = $loop->[0]; - isa_ok $path, 'Slic3r::ExtrusionPath::Ref'; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; } diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t index 5203ec5ef4..7da74b93da 100644 --- a/xs/t/09_polyline.t +++ b/xs/t/09_polyline.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 15; my $points = [ [100, 100], @@ -14,11 +14,6 @@ my $points = [ my $polyline = Slic3r::Polyline->new(@$points); -is_deeply $polyline->pp, $points, 'polyline roundtrip'; - -is ref($polyline->arrayref), 'ARRAY', 'polyline arrayref is unblessed'; -isa_ok $polyline->[0], 'Slic3r::Point::Ref', 'polyline point is blessed'; - my $lines = $polyline->lines; is_deeply [ map $_->pp, @$lines ], [ [ [100, 100], [200, 100] ], @@ -88,41 +83,4 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; is scalar(@$p2), 4, 'split_at'; } -# disabled because we now use a more efficient but incomplete algorithm -#if (0) { -# my $polyline = Slic3r::Polyline->new( -# map [$_,10], (0,10,20,30,40,50,60) -# ); -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [25,0], [55,0], [55,30], [25,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,10,20,30,50,60) -# ], 'simplify_by_visibility()'; -# } -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [-15,0], [75,0], [75,30], [-15,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,60) -# ], 'simplify_by_visibility()'; -# } -# { -# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( -# [-15,0], [25,0], [25,30], [-15,30], -# )); -# my $p = $polyline->clone; -# $p->simplify_by_visibility($expolygon); -# is_deeply $p->pp, [ -# map [$_,10], (0,20,30,40,50,60) -# ], 'simplify_by_visibility()'; -# } -#} - __END__ diff --git a/xs/t/10_line.t b/xs/t/10_line.t index 8f82e988c6..886573f7b6 100644 --- a/xs/t/10_line.t +++ b/xs/t/10_line.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 40; +use Test::More tests => 35; use constant PI => 4 * atan2(1, 1); use constant EPSILON => 1E-4; @@ -15,21 +15,6 @@ my $points = [ ]; my $line = Slic3r::Line->new(@$points); -is_deeply $line->pp, $points, 'line roundtrip'; - -is ref($line->arrayref), 'ARRAY', 'line arrayref is unblessed'; -isa_ok $line->[0], 'Slic3r::Point::Ref', 'line point is blessed'; - -{ - my $clone = $line->clone; - $clone->reverse; - is_deeply $clone->pp, [ reverse @$points ], 'reverse'; -} - -{ - my $line2 = Slic3r::Line->new($line->a->clone, $line->b->clone); - is_deeply $line2->pp, $points, 'line roundtrip with cloned points'; -} { my $clone = $line->clone; diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t index e7e0b1316a..e028542451 100644 --- a/xs/t/12_extrusionpathcollection.t +++ b/xs/t/12_extrusionpathcollection.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 18; +use Test::More tests => 13; my $points = [ [100, 100], @@ -41,12 +41,6 @@ is scalar(@$collection), 3, 'append ExtrusionPath'; $collection->append($loop); is scalar(@$collection), 4, 'append ExtrusionLoop'; -isa_ok $collection->[1], 'Slic3r::ExtrusionPath::Collection::Ref', 'correct object returned for collection'; -isa_ok $collection->[2], 'Slic3r::ExtrusionPath::Ref', 'correct object returned for path'; -isa_ok $collection->[3], 'Slic3r::ExtrusionLoop::Ref', 'correct object returned for loop'; -is ref($collection->[2]->clone), 'Slic3r::ExtrusionPath', 'correct object returned for cloned path'; -is ref($collection->[3]->clone), 'Slic3r::ExtrusionLoop', 'correct object returned for cloned loop'; - is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { diff --git a/xs/t/17_boundingbox.t b/xs/t/17_boundingbox.t deleted file mode 100644 index 349e0024de..0000000000 --- a/xs/t/17_boundingbox.t +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 5; - -{ - my @points = ( - Slic3r::Point->new(100, 200), - Slic3r::Point->new(500, -600), - ); - my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@points); - isa_ok $bb, 'Slic3r::Geometry::BoundingBox', 'new_from_points'; - is_deeply $bb->min_point->pp, [100,-600], 'min_point'; - is_deeply $bb->max_point->pp, [500,200], 'max_point'; -} - -{ - my $bb = Slic3r::Geometry::BoundingBox->new; - $bb->merge_point(Slic3r::Point->new(10, 10)); - is_deeply $bb->min_point->pp, [10,10], 'min_point equals to the only defined point'; - is_deeply $bb->max_point->pp, [10,10], 'max_point equals to the only defined point'; -} - -__END__ diff --git a/xs/xsp/ExPolygon.xsp b/xs/xsp/ExPolygon.xsp index a57bcfbcb8..50b32544ee 100644 --- a/xs/xsp/ExPolygon.xsp +++ b/xs/xsp/ExPolygon.xsp @@ -29,8 +29,6 @@ %code{% RETVAL = THIS->contains(*point); %}; ExPolygons simplify(double tolerance); Polygons simplify_p(double tolerance); - Polylines medial_axis(double max_width, double min_width) - %code{% THIS->medial_axis(max_width, min_width, &RETVAL); %}; %{ ExPolygon* diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp deleted file mode 100644 index 4c25838946..0000000000 --- a/xs/xsp/GCode.xsp +++ /dev/null @@ -1,53 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/GCode.hpp" -#include "libslic3r/GCode/CoolingBuffer.hpp" -%} - -%name{Slic3r::GCode::CoolingBuffer} class CoolingBuffer { - CoolingBuffer(GCode* gcode) - %code{% RETVAL = new CoolingBuffer(*gcode); %}; - ~CoolingBuffer(); - std::string process_layer(std::string gcode, size_t layer_id) - %code{% RETVAL = THIS->process_layer(std::move(gcode), layer_id, true); %}; - -}; - -%name{Slic3r::GCode} class GCode { - GCode(); - ~GCode(); - void do_export(Print *print, const char *path) - %code%{ - try { - THIS->do_export(print, path); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; - - Ref origin() - %code{% RETVAL = &(THIS->origin()); %}; - void set_origin(Vec2d* pointf) - %code{% THIS->set_origin(*pointf); %}; - Ref last_pos() - %code{% RETVAL = &(THIS->last_pos()); %}; - - unsigned int layer_count() const; - void set_layer_count(unsigned int value); - void set_extruders(std::vector extruders) - %code{% THIS->writer().set_extruders(extruders); THIS->writer().set_extruder(0); %}; - - void apply_print_config(StaticPrintConfig* print_config) - %code{% - if (const PrintConfig* config = dynamic_cast(print_config)) { - THIS->apply_print_config(*config); - } else { - CONFESS("A PrintConfig object was not supplied to apply_print_config()"); - } - %}; - - Ref config() - %code{% RETVAL = const_cast(static_cast(static_cast(&THIS->config()))); %}; -}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 97cbdafe32..6b6f6e9be2 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -60,8 +60,6 @@ size_t object_count() %code%{ RETVAL = THIS->objects().size(); %}; - PrintRegionPtrs* regions() - %code%{ RETVAL = const_cast(&THIS->print_regions_mutable()); %}; void auto_assign_extruders(ModelObject* model_object); std::string output_filepath(std::string path = "") diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 5174bdfa95..42ca74292c 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -125,18 +125,9 @@ Ref O_OBJECT_SLIC3R_T Layer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T -CoolingBuffer* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -GCode* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV -FlowRole T_UV SurfaceType T_UV # we return these types whenever we want the items to be cloned diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 243e80dbcd..bf1e8e2acc 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -92,18 +92,6 @@ %typemap{Layer*}; %typemap{Ref}{simple}; -%typemap{CoolingBuffer*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{GCode*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -//%typemap{GCodePreviewData*}; -//%typemap{Ref}{simple}; -//%typemap{Clone}{simple}; - %typemap{Points}; %typemap{Pointfs}; %typemap{Lines}; @@ -167,9 +155,3 @@ $CVar = (ExtrusionRole)SvUV($PerlVar); %}; }; -%typemap{FlowRole}{parsed}{ - %cpp_type{FlowRole}; - %precall_code{% - $CVar = (FlowRole)SvUV($PerlVar); - %}; -};