This commit is contained in:
enricoturri1966 2022-05-06 07:30:38 +02:00
commit ce98307b9c
54 changed files with 1325 additions and 1235 deletions

View File

@ -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.
;
}
}

View File

@ -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<int> 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<float> 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<double> 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

View File

@ -1950,6 +1950,11 @@ public:
throw BadOptionTypeException("Conversion to a wrong type");
return static_cast<TYPE*>(opt);
}
template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
{ return dynamic_cast<T*>(this->optptr(opt_key, create)); }
template<class T> const T* opt(const t_config_option_key &opt_key) const
{ return dynamic_cast<const T*>(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<int> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<int> 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<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<float> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
SetDeserializeItem(const char *opt_key, const std::initializer_list<double> values, bool append = false) : opt_key(opt_key), opt_value(format(values)), append(append) {}
SetDeserializeItem(const std::string &opt_key, const std::initializer_list<double> 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<int> values);
static std::string format(std::initializer_list<float> values);
static std::string format(std::initializer_list<double> values);
};
// May throw BadOptionTypeException() if the operation fails.
void set_deserialize(std::initializer_list<SetDeserializeItem> 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<ConfigOptionString>(opt_key, create)->value; }
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<ConfigBase*>(this)->opt_string(opt_key); }
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<ConfigBase*>(this)->opt_string(opt_key, idx); }
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(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<typename ENUM>
ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(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<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
{ return dynamic_cast<T*>(this->option(opt_key, create)); }
template<class T> const T* opt(const t_config_option_key &opt_key) const
{ return dynamic_cast<const T*>(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<ConfigOptionString>(opt_key, create)->value; }
const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); }
double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
const double& opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(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<typename ENUM>
ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast<ENUM>(this->option(opt_key)->getInt()); }
bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(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);

View File

@ -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).

View File

@ -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<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;

View File

@ -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<unsigned int>(-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

View File

@ -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<double>().norm());
if (ab_len >= to_be_reduced) {

View File

@ -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;

View File

@ -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;

View File

@ -1,9 +1,15 @@
#ifndef slic3r_Geometry_ConvexHull_hpp_
#define slic3r_Geometry_ConvexHull_hpp_
#include <vector>
#include "../Polygon.hpp"
namespace Slic3r {
class ExPolygon;
using ExPolygons = std::vector<ExPolygon>;
namespace Geometry {
Pointf3s convex_hull(Pointf3s points);

View File

@ -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

View File

@ -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<bool>(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<bool>(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<bool>(value) ? "1" : "";
else if (opt_key == "notify_release") {
if (opt_key == "notify_release") {
int val_int = boost::any_cast<int>(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<bool>(value) ? "1" : "0";
}
if (opt_key == "use_custom_toolbar_size") {
m_icon_size_sizer->ShowItems(boost::any_cast<bool>(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<bool>(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<bool>(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<bool>(value) ? "1" : "";
else
m_values[opt_key] = boost::any_cast<bool>(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<bool>(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<bool>(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<wxButton*>(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,84 @@ 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 (reverted = opt_group->set_value(key, app_config->get(key) == "1"))
break;
}
if (!reverted)
int i=0;
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 +780,19 @@ void PreferencesDialog::layout()
Refresh();
}
void PreferencesDialog::clear_cache()
{
m_values.clear();
m_custom_toolbar_size = -1;
}
void PreferencesDialog::refresh_og(std::shared_ptr<ConfigOptionsGroup> og)
{
og->parent()->Layout();
tabs->Layout();
this->layout();
}
void PreferencesDialog::create_icon_size_slider()
{
const auto app_config = get_app_config();
@ -695,14 +819,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 +834,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<wxWindow*>{ slider, label, val_label }) {
for (wxWindow* win : std::vector<wxWindow*>{ m_icon_size_slider, label, val_label }) {
if (!win) continue;
win->SetFont(wxGetApp().normal_font());
@ -731,26 +858,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<wxString> 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 +869,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<wxString> 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);

View File

@ -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<ConfigOptionsGroup> 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<ConfigOptionsGroup> og);
void create_icon_size_slider();
void create_settings_mode_widget();
void create_settings_text_color_widget();

View File

@ -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';
}

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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__

View File

@ -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]);

View File

@ -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__

185
t/thin.t
View File

@ -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__

View File

@ -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)

View File

@ -0,0 +1,274 @@
#include <catch2/catch.hpp>
#include <numeric>
#include <sstream>
#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<CoolingBuffer> make_cooling_buffer(
GCode &gcode,
const DynamicPrintConfig &config = DynamicPrintConfig{},
const std::vector<unsigned int> &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<CoolingBuffer>(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<double>(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<ConfigOptionFloatOrPercent>("external_perimeter_speed")->value * 60;
std::vector<double> layer_times;
// z => 1
std::map<coord_t, int> 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<coord_t>(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<coord_t>(self.z())];
}
});
THEN("slowdown_below_layer_time is honored") {
// Account for some inaccuracies.
const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("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, '';
}
}
}

View File

@ -0,0 +1,220 @@
#include <catch2/catch.hpp>
#include <numeric>
#include <sstream>
#include "libslic3r/Config.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h"
#include "test_data.hpp"
using namespace Slic3r;
#if 0
SCENARIO("Output file format", "[CustomGCode]")
{
WHEN("output_file_format set") {
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "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") {
output_file.find(std::string("ts_") + )
}
my ($t, $h) = map $config->$_, qw(travel_speed layer_height);
ok $output_file =~ /ts_${t}_/, '';
ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename';
std::string gcode = print.gcode(print);
THEN("print config options are replaced in custom G-code") {
ok $gcode =~ /TRAVEL:$t/, '';
}
THEN("region config options are replaced in custom G-code") {
ok $gcode =~ /HEIGHT:$h/, '';
}
}
}
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 (line.extruding(self)) {
if (! was_extruding)
seam_points.emplace_back(self.xy_scaled());
was_extruding = true;
} else if (! line.cmd_is("M73")) {
// skips remaining time lines (M73)
was_extruding = false;
}
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");
};
{
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';
}
#endif

View File

@ -26,6 +26,7 @@ const std::unordered_map<TestMesh, const char*, TestMeshHash> mesh_names {
std::pair<TestMesh, const char*>(TestMesh::V, "V"),
std::pair<TestMesh, const char*>(TestMesh::_40x10, "40x10"),
std::pair<TestMesh, const char*>(TestMesh::cube_20x20x20, "cube_20x20x20"),
std::pair<TestMesh, const char*>(TestMesh::cube_2x20x10, "cube_2x20x10"),
std::pair<TestMesh, const char*>(TestMesh::sphere_50mm, "sphere_50mm"),
std::pair<TestMesh, const char*>(TestMesh::bridge, "bridge"),
std::pair<TestMesh, const char*>(TestMesh::bridge_with_hole, "bridge_with_hole"),
@ -49,6 +50,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;

View File

@ -21,6 +21,7 @@ enum class TestMesh {
V,
_40x10,
cube_20x20x20,
cube_2x20x10,
sphere_50mm,
bridge,
bridge_with_hole,

View File

@ -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 },

View File

@ -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<double> 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<double>(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<double> 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<double>(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<double> 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<double>(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<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
const double filament_dmr = config.opt<ConfigOptionFloats>("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));
}
}
}

View File

@ -0,0 +1,60 @@
#include <catch2/catch.hpp>
#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);
}
}
}

View File

@ -0,0 +1,191 @@
#include <catch2/catch.hpp>
#include <numeric>
#include <sstream>
#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<double>(40.), scaled<double>(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<double>(20.), scaled<double>(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<double>(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<double>(1.), scaled<double>(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<double>(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<coord_t>(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<double>(1.324888), scaled<double>(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<double>(), (pl.points[i + 1] - pl.points[i]).cast<double>());
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<double>(20.), scaled<double>(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<double>(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<double>(20.), scaled<double>(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<double>(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<double>(4.), scaled<double>(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<double>(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);
}
}
}
}

View File

@ -13,6 +13,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

View File

@ -1,5 +1,6 @@
#include <catch2/catch.hpp>
#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<ConfigOptionInts>("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);
}
}

View File

@ -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>({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>({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);

View File

@ -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());
}
}
}
}

View File

@ -0,0 +1,28 @@
#include <catch2/catch.hpp>
#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} });
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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__

View File

@ -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';

View File

@ -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';

View File

@ -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';
}

View File

@ -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__

View File

@ -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';

View File

@ -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';
}

View File

@ -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__

View File

@ -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;

View File

@ -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';
{

View File

@ -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__

View File

@ -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*

View File

@ -1,53 +0,0 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#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<Vec2d> origin()
%code{% RETVAL = &(THIS->origin()); %};
void set_origin(Vec2d* pointf)
%code{% THIS->set_origin(*pointf); %};
Ref<Point> last_pos()
%code{% RETVAL = &(THIS->last_pos()); %};
unsigned int layer_count() const;
void set_layer_count(unsigned int value);
void set_extruders(std::vector<unsigned int> 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<PrintConfig*>(print_config)) {
THIS->apply_print_config(*config);
} else {
CONFESS("A PrintConfig object was not supplied to apply_print_config()");
}
%};
Ref<StaticPrintConfig> config()
%code{% RETVAL = const_cast<StaticPrintConfig*>(static_cast<const StaticPrintConfig*>(static_cast<const PrintObjectConfig*>(&THIS->config()))); %};
};

View File

@ -125,18 +125,9 @@ Ref<LayerRegion> O_OBJECT_SLIC3R_T
Layer* O_OBJECT_SLIC3R
Ref<Layer> O_OBJECT_SLIC3R_T
CoolingBuffer* O_OBJECT_SLIC3R
Ref<CoolingBuffer> O_OBJECT_SLIC3R_T
Clone<CoolingBuffer> O_OBJECT_SLIC3R_T
GCode* O_OBJECT_SLIC3R
Ref<GCode> O_OBJECT_SLIC3R_T
Clone<GCode> 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

View File

@ -92,18 +92,6 @@
%typemap{Layer*};
%typemap{Ref<Layer>}{simple};
%typemap{CoolingBuffer*};
%typemap{Ref<CoolingBuffer>}{simple};
%typemap{Clone<CoolingBuffer>}{simple};
%typemap{GCode*};
%typemap{Ref<GCode>}{simple};
%typemap{Clone<GCode>}{simple};
//%typemap{GCodePreviewData*};
//%typemap{Ref<GCodePreviewData>}{simple};
//%typemap{Clone<GCodePreviewData>}{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);
%};
};