Further Perl unit test porting to C++ and Perl interface reduction:
Ported cooling, gap fill, thin walls and polyline unit tests.
This commit is contained in:
parent
5a67d0e183
commit
d4b8d4d0f3
@ -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
|
||||
|
@ -1951,6 +1951,11 @@ public:
|
||||
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(),
|
||||
// or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
|
||||
@ -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,6 +2025,30 @@ 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;
|
||||
|
||||
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);
|
||||
@ -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);
|
||||
|
||||
|
@ -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).
|
||||
|
@ -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;
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef slic3r_Geometry_ConvexHull_hpp_
|
||||
#define slic3r_Geometry_ConvexHull_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -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__
|
||||
|
20
t/config.t
20
t/config.t
@ -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__
|
214
t/cooling.t
214
t/cooling.t
@ -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__
|
83
t/flow.t
83
t/flow.t
@ -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__
|
59
t/gaps.t
59
t/gaps.t
@ -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__
|
@ -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]);
|
||||
|
57
t/loops.t
57
t/loops.t
@ -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
185
t/thin.t
@ -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__
|
@ -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)
|
||||
|
274
tests/fff_print/test_cooling.cpp
Normal file
274
tests/fff_print/test_cooling.cpp
Normal 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, '';
|
||||
}
|
||||
}
|
||||
}
|
220
tests/fff_print/test_custom_gcode.cpp
Normal file
220
tests/fff_print/test_custom_gcode.cpp
Normal 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
|
@ -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;
|
||||
|
@ -21,6 +21,7 @@ enum class TestMesh {
|
||||
V,
|
||||
_40x10,
|
||||
cube_20x20x20,
|
||||
cube_2x20x10,
|
||||
sphere_50mm,
|
||||
bridge,
|
||||
bridge_with_hole,
|
||||
|
@ -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 },
|
||||
|
@ -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);
|
||||
auto test = [](const DynamicPrintConfig &config) {
|
||||
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)
|
||||
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) {
|
||||
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 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.") {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
tests/fff_print/test_gaps.cpp
Normal file
60
tests/fff_print/test_gaps.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
191
tests/fff_print/test_thin_walls.cpp
Normal file
191
tests/fff_print/test_thin_walls.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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.") {
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,14 +162,34 @@ TEST_CASE("Splitting a Polygon generates a polyline correctly", "[Geometry]"){
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Bounding boxes are scaled appropriately", "[Geometry]"){
|
||||
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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
tests/libslic3r/test_polyline.cpp
Normal file
28
tests/libslic3r/test_polyline.cpp
Normal 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} });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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__
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -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__
|
@ -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';
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
|
@ -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__
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
||||
{
|
||||
|
@ -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__
|
@ -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*
|
||||
|
@ -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()))); %};
|
||||
};
|
@ -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
|
||||
|
@ -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);
|
||||
%};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user